@norman-else/dsh-claude 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +33 -0
- package/lib/client.js +1692 -655
- package/lib/client.js.map +1 -1
- package/lib/{events-OhBoFNKO.mjs → events-B-FPMzI7.mjs} +7 -2
- package/lib/events-B-FPMzI7.mjs.map +1 -0
- package/lib/index.d.mts +55 -3
- package/lib/index.mjs +815 -167
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-BBoM1Ju1.mjs → presenters-BV42EKkB.mjs} +21 -2
- package/lib/{presenters-BBoM1Ju1.mjs.map → presenters-BV42EKkB.mjs.map} +1 -1
- package/lib/{preset-installer-loenwnLS.mjs → preset-installer-yRGfkGjd.mjs} +2 -2
- package/lib/{preset-installer-loenwnLS.mjs.map → preset-installer-yRGfkGjd.mjs.map} +1 -1
- package/lib/preset-route.mjs +2 -2
- package/package.json +1 -1
- package/lib/events-OhBoFNKO.mjs.map +0 -1
package/lib/client.js
CHANGED
|
@@ -27,6 +27,7 @@ window.__ModuleLoader__.load({
|
|
|
27
27
|
const CLAUDE_ASK_PATH = "/plugins/dsh-claude/ask";
|
|
28
28
|
const CLAUDE_EDITOR_OPEN_PATH = "/plugins/dsh-claude/editor/open";
|
|
29
29
|
const CLAUDE_REWIND_PATH = "/plugins/dsh-claude/rewind";
|
|
30
|
+
const CLAUDE_PLAN_FEEDBACK_PATH = "/plugins/dsh-claude/plan/feedback";
|
|
30
31
|
function isClaudeRenderMode(value) {
|
|
31
32
|
return value === "plugin" || value === "native";
|
|
32
33
|
}
|
|
@@ -34,6 +35,9 @@ window.__ModuleLoader__.load({
|
|
|
34
35
|
function isClaudeProseMode(value) {
|
|
35
36
|
return value === "plain" || value === "enhanced";
|
|
36
37
|
}
|
|
38
|
+
function isClaudeAlertMode(value) {
|
|
39
|
+
return value === "off" || value === "on";
|
|
40
|
+
}
|
|
37
41
|
//#endregion
|
|
38
42
|
//#region src/client/task-projection.ts
|
|
39
43
|
/** Tasks UI is reserved for detached work and genuine Claude subagents. */
|
|
@@ -268,6 +272,10 @@ window.__ModuleLoader__.load({
|
|
|
268
272
|
failedAction = description === void 0 ? `run ${command}` : description;
|
|
269
273
|
break;
|
|
270
274
|
}
|
|
275
|
+
case "ExitPlanMode":
|
|
276
|
+
completed = "Proposed a plan";
|
|
277
|
+
failedAction = "propose a plan";
|
|
278
|
+
break;
|
|
271
279
|
default:
|
|
272
280
|
completed = description ?? `${toolName}${target === void 0 ? "" : ` ${target}`}`;
|
|
273
281
|
failedAction = completed.charAt(0).toLowerCase() + completed.slice(1);
|
|
@@ -1096,6 +1104,163 @@ window.__ModuleLoader__.load({
|
|
|
1096
1104
|
fontSize: 12,
|
|
1097
1105
|
lineHeight: "18px"
|
|
1098
1106
|
};
|
|
1107
|
+
/** Title and state chip: one phrase at the header's left end. */
|
|
1108
|
+
const planHeaderStart = {
|
|
1109
|
+
display: "flex",
|
|
1110
|
+
alignItems: "center",
|
|
1111
|
+
gap: 8,
|
|
1112
|
+
minWidth: 0
|
|
1113
|
+
};
|
|
1114
|
+
/** "2 / 5" next to the title when the session has proposed more than one. */
|
|
1115
|
+
const planCount = {
|
|
1116
|
+
flex: "none",
|
|
1117
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1118
|
+
fontSize: 12,
|
|
1119
|
+
lineHeight: "17px",
|
|
1120
|
+
fontVariantNumeric: "tabular-nums"
|
|
1121
|
+
};
|
|
1122
|
+
/** The review composer: notes filed so far, the pending quote, and the box.
|
|
1123
|
+
* Docked below the plan body so the plan itself keeps the scrolling room. */
|
|
1124
|
+
const planComposer = {
|
|
1125
|
+
flex: "none",
|
|
1126
|
+
display: "flex",
|
|
1127
|
+
flexDirection: "column",
|
|
1128
|
+
gap: 6,
|
|
1129
|
+
maxHeight: "45%",
|
|
1130
|
+
overflowY: "auto",
|
|
1131
|
+
padding: "10px 14px 12px",
|
|
1132
|
+
borderTop: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
|
|
1133
|
+
background: "var(--dsw-alias-bg-layer-1)"
|
|
1134
|
+
};
|
|
1135
|
+
const planNoteList = {
|
|
1136
|
+
display: "flex",
|
|
1137
|
+
flexDirection: "column",
|
|
1138
|
+
gap: 6,
|
|
1139
|
+
margin: 0,
|
|
1140
|
+
padding: 0,
|
|
1141
|
+
listStyle: "none"
|
|
1142
|
+
};
|
|
1143
|
+
const planNote = {
|
|
1144
|
+
position: "relative",
|
|
1145
|
+
padding: "6px 26px 6px 8px",
|
|
1146
|
+
borderRadius: 7,
|
|
1147
|
+
background: "var(--dsw-alias-bg-base)"
|
|
1148
|
+
};
|
|
1149
|
+
/** The quoted passage, marked as someone else's words by the rule down its
|
|
1150
|
+
* left edge rather than by quotation marks it may already contain. */
|
|
1151
|
+
const planNoteQuote = {
|
|
1152
|
+
display: "-webkit-box",
|
|
1153
|
+
WebkitLineClamp: 3,
|
|
1154
|
+
WebkitBoxOrient: "vertical",
|
|
1155
|
+
overflow: "hidden",
|
|
1156
|
+
margin: 0,
|
|
1157
|
+
paddingLeft: 8,
|
|
1158
|
+
borderLeft: "2px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 24%, transparent))",
|
|
1159
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1160
|
+
fontSize: 11,
|
|
1161
|
+
lineHeight: "16px",
|
|
1162
|
+
whiteSpace: "pre-wrap"
|
|
1163
|
+
};
|
|
1164
|
+
const planNoteText = {
|
|
1165
|
+
margin: "4px 0 0",
|
|
1166
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1167
|
+
fontSize: 12,
|
|
1168
|
+
lineHeight: "17px",
|
|
1169
|
+
whiteSpace: "pre-wrap"
|
|
1170
|
+
};
|
|
1171
|
+
const planNoteRemove = {
|
|
1172
|
+
position: "absolute",
|
|
1173
|
+
top: 4,
|
|
1174
|
+
right: 4,
|
|
1175
|
+
width: 18,
|
|
1176
|
+
height: 18,
|
|
1177
|
+
display: "grid",
|
|
1178
|
+
placeItems: "center",
|
|
1179
|
+
padding: 0,
|
|
1180
|
+
border: 0,
|
|
1181
|
+
borderRadius: 5,
|
|
1182
|
+
background: "transparent",
|
|
1183
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1184
|
+
fontSize: 14,
|
|
1185
|
+
lineHeight: 1,
|
|
1186
|
+
cursor: "pointer"
|
|
1187
|
+
};
|
|
1188
|
+
/** The live selection, shown attached to the box it will be filed with. */
|
|
1189
|
+
const planQuoteChip = {
|
|
1190
|
+
position: "relative",
|
|
1191
|
+
padding: "6px 26px 6px 8px",
|
|
1192
|
+
borderRadius: 7,
|
|
1193
|
+
background: "var(--dsw-alias-interactive-bg-hover)"
|
|
1194
|
+
};
|
|
1195
|
+
const planComposerInput = {
|
|
1196
|
+
boxSizing: "border-box",
|
|
1197
|
+
width: "100%",
|
|
1198
|
+
minHeight: 56,
|
|
1199
|
+
padding: "7px 9px",
|
|
1200
|
+
border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
|
|
1201
|
+
borderRadius: 8,
|
|
1202
|
+
background: "var(--dsw-alias-bg-base)",
|
|
1203
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1204
|
+
fontFamily: "inherit",
|
|
1205
|
+
fontSize: 12,
|
|
1206
|
+
lineHeight: "18px",
|
|
1207
|
+
resize: "vertical"
|
|
1208
|
+
};
|
|
1209
|
+
const planComposerActions = {
|
|
1210
|
+
display: "flex",
|
|
1211
|
+
alignItems: "center",
|
|
1212
|
+
justifyContent: "space-between",
|
|
1213
|
+
gap: 8
|
|
1214
|
+
};
|
|
1215
|
+
const planComposerHint = {
|
|
1216
|
+
minWidth: 0,
|
|
1217
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1218
|
+
fontSize: 11,
|
|
1219
|
+
lineHeight: "16px"
|
|
1220
|
+
};
|
|
1221
|
+
const planComposerError = {
|
|
1222
|
+
margin: 0,
|
|
1223
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
1224
|
+
fontSize: 11,
|
|
1225
|
+
lineHeight: "16px"
|
|
1226
|
+
};
|
|
1227
|
+
/** Maximize and close: the control group at the header's right end. */
|
|
1228
|
+
const planHeaderEnd = {
|
|
1229
|
+
flex: "none",
|
|
1230
|
+
display: "flex",
|
|
1231
|
+
alignItems: "center",
|
|
1232
|
+
gap: 2
|
|
1233
|
+
};
|
|
1234
|
+
const planBadge = {
|
|
1235
|
+
flex: "none",
|
|
1236
|
+
padding: "2px 8px",
|
|
1237
|
+
borderRadius: 999,
|
|
1238
|
+
background: "color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)",
|
|
1239
|
+
color: "var(--dsw-alias-state-success-primary)",
|
|
1240
|
+
fontSize: 11,
|
|
1241
|
+
lineHeight: "16px",
|
|
1242
|
+
fontWeight: 600,
|
|
1243
|
+
whiteSpace: "nowrap"
|
|
1244
|
+
};
|
|
1245
|
+
const planBadgePending = {
|
|
1246
|
+
background: "var(--dsw-alias-interactive-bg-hover)",
|
|
1247
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1248
|
+
};
|
|
1249
|
+
const planBadgeRejected = {
|
|
1250
|
+
background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)",
|
|
1251
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
1252
|
+
};
|
|
1253
|
+
/** Says where the decision is made, since this panel deliberately does not
|
|
1254
|
+
* make it: the Host's approval dialog owns the buttons. */
|
|
1255
|
+
const planHint = {
|
|
1256
|
+
margin: "0 0 12px",
|
|
1257
|
+
padding: "8px 10px",
|
|
1258
|
+
borderRadius: 8,
|
|
1259
|
+
background: "var(--dsw-alias-interactive-bg-hover)",
|
|
1260
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1261
|
+
fontSize: 12,
|
|
1262
|
+
lineHeight: "18px"
|
|
1263
|
+
};
|
|
1099
1264
|
const tasksFinishedSection = {
|
|
1100
1265
|
marginTop: 12,
|
|
1101
1266
|
paddingTop: 8,
|
|
@@ -2687,17 +2852,8 @@ window.__ModuleLoader__.load({
|
|
|
2687
2852
|
color: "var(--dsw-alias-label-secondary)",
|
|
2688
2853
|
transition: "transform 120ms ease"
|
|
2689
2854
|
};
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
padding: "2px 8px",
|
|
2693
|
-
border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
|
|
2694
|
-
borderRadius: 6,
|
|
2695
|
-
background: "transparent",
|
|
2696
|
-
color: "var(--dsw-alias-label-secondary)",
|
|
2697
|
-
font: "inherit",
|
|
2698
|
-
fontSize: 12,
|
|
2699
|
-
cursor: "pointer"
|
|
2700
|
-
};
|
|
2855
|
+
/** Sits at the far end of the summary row; the rest of the button is the shared icon-button class. */
|
|
2856
|
+
const diffSummaryAction = { marginLeft: "auto" };
|
|
2701
2857
|
const diffCommentNav = {
|
|
2702
2858
|
display: "inline-flex",
|
|
2703
2859
|
alignItems: "center",
|
|
@@ -3041,6 +3197,9 @@ window.__ModuleLoader__.load({
|
|
|
3041
3197
|
};
|
|
3042
3198
|
const diffFile = { borderBottom: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))" };
|
|
3043
3199
|
const diffFileHeader = {
|
|
3200
|
+
position: "sticky",
|
|
3201
|
+
top: 0,
|
|
3202
|
+
zIndex: 1,
|
|
3044
3203
|
width: "100%",
|
|
3045
3204
|
minHeight: 38,
|
|
3046
3205
|
display: "flex",
|
|
@@ -4393,9 +4552,8 @@ window.__ModuleLoader__.load({
|
|
|
4393
4552
|
feed: applyLine,
|
|
4394
4553
|
subscribe(listener) {
|
|
4395
4554
|
if (disposed) return () => {};
|
|
4396
|
-
const wasIdle = listeners.size === 0;
|
|
4397
4555
|
listeners.add(listener);
|
|
4398
|
-
|
|
4556
|
+
onDemand(true);
|
|
4399
4557
|
return () => {
|
|
4400
4558
|
listeners.delete(listener);
|
|
4401
4559
|
if (listeners.size !== 0) return;
|
|
@@ -4411,6 +4569,13 @@ window.__ModuleLoader__.load({
|
|
|
4411
4569
|
}
|
|
4412
4570
|
};
|
|
4413
4571
|
}
|
|
4572
|
+
/** Whether two lane sets carry the same sessions. Order is the LRU's business,
|
|
4573
|
+
* not the carrier's: the server reads `sessions=` as a set. */
|
|
4574
|
+
function sameLanes(before, after) {
|
|
4575
|
+
if (before.length !== after.length) return false;
|
|
4576
|
+
const held = new Set(before);
|
|
4577
|
+
return after.every((sessionId) => held.has(sessionId));
|
|
4578
|
+
}
|
|
4414
4579
|
/**
|
|
4415
4580
|
* Every session's projection over ONE connection.
|
|
4416
4581
|
*
|
|
@@ -4483,10 +4648,12 @@ window.__ModuleLoader__.load({
|
|
|
4483
4648
|
* a session list would otherwise reopen it once per row. */
|
|
4484
4649
|
#demand(sessionId, active) {
|
|
4485
4650
|
if (this.#disposed) return;
|
|
4651
|
+
const before = this.#lanes();
|
|
4486
4652
|
if (active) {
|
|
4487
4653
|
this.#wanted.delete(sessionId);
|
|
4488
4654
|
this.#wanted.add(sessionId);
|
|
4489
4655
|
} else if (!this.#wanted.delete(sessionId)) return;
|
|
4656
|
+
if (sameLanes(before, this.#lanes())) return;
|
|
4490
4657
|
if (this.#settle !== void 0) clearTimeout(this.#settle);
|
|
4491
4658
|
const timer = setTimeout(() => {
|
|
4492
4659
|
this.#settle = void 0;
|
|
@@ -4630,10 +4797,10 @@ window.__ModuleLoader__.load({
|
|
|
4630
4797
|
".dsh-claude-act-running{animation:dsh-claude-act-pulse 1.2s ease-in-out infinite}",
|
|
4631
4798
|
"@keyframes dsh-claude-act-pulse{0%,100%{opacity:1}50%{opacity:.3}}"
|
|
4632
4799
|
].join("");
|
|
4633
|
-
let cssInjected$
|
|
4634
|
-
function ensureCss$
|
|
4635
|
-
if (cssInjected$
|
|
4636
|
-
cssInjected$
|
|
4800
|
+
let cssInjected$4 = false;
|
|
4801
|
+
function ensureCss$4() {
|
|
4802
|
+
if (cssInjected$4 || typeof document === "undefined") return;
|
|
4803
|
+
cssInjected$4 = true;
|
|
4637
4804
|
const element = document.createElement("style");
|
|
4638
4805
|
element.dataset.dshClaudeActivity = "";
|
|
4639
4806
|
element.textContent = ACTIVITY_CSS;
|
|
@@ -5061,7 +5228,7 @@ window.__ModuleLoader__.load({
|
|
|
5061
5228
|
});
|
|
5062
5229
|
}
|
|
5063
5230
|
function ClaudeActivityNode({ node, useClaudeProjection, t }) {
|
|
5064
|
-
ensureCss$
|
|
5231
|
+
ensureCss$4();
|
|
5065
5232
|
const marker = node.data;
|
|
5066
5233
|
const activities = useClaudeProjection((value) => selectStepActivities(value, marker.turn, marker.step));
|
|
5067
5234
|
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS$1);
|
|
@@ -5527,87 +5694,588 @@ window.__ModuleLoader__.load({
|
|
|
5527
5694
|
});
|
|
5528
5695
|
}
|
|
5529
5696
|
//#endregion
|
|
5530
|
-
//#region src/
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5697
|
+
//#region src/github-url.ts
|
|
5698
|
+
/** Only GitHub's own image hosts; the browser loads these directly, so a URL
|
|
5699
|
+
* the API did not vouch for must never become an outbound request. */
|
|
5700
|
+
function githubAvatarUrl(value) {
|
|
5701
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return void 0;
|
|
5702
|
+
try {
|
|
5703
|
+
const url = new URL(value);
|
|
5704
|
+
const allowed = url.hostname === "github.com" || url.hostname === "githubusercontent.com" || url.hostname.endsWith(".githubusercontent.com");
|
|
5705
|
+
return url.protocol === "https:" && allowed ? url.href : void 0;
|
|
5706
|
+
} catch {
|
|
5707
|
+
return;
|
|
5537
5708
|
}
|
|
5538
|
-
}
|
|
5709
|
+
}
|
|
5710
|
+
//#endregion
|
|
5711
|
+
//#region src/client/pr-feedback-api.ts
|
|
5539
5712
|
function record$5(value) {
|
|
5540
5713
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5541
5714
|
}
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
* The routes answer `{ error, message }`, so `message` is already the sentence
|
|
5549
|
-
* to show. A body carrying only a code — a 405, a bad JSON body — used to read
|
|
5550
|
-
* 'Jira is unavailable.' rather than leaking the code as prose, and it still
|
|
5551
|
-
* does. Transport failures (a starved pool, an elapsed budget, an older Host
|
|
5552
|
-
* without the route) carry their own wording and keep it.
|
|
5553
|
-
*/
|
|
5554
|
-
function jiraFailure(cause) {
|
|
5555
|
-
if (cause instanceof JiraClientError) return cause;
|
|
5556
|
-
if (!(cause instanceof PluginRequestError)) return new JiraClientError(cause instanceof Error ? cause.message : String(cause));
|
|
5557
|
-
return new JiraClientError(cause.message === cause.code ? "Jira is unavailable." : cause.message, cause.code);
|
|
5715
|
+
function feedbackQuery(sessionId, pullNumber, extra) {
|
|
5716
|
+
return {
|
|
5717
|
+
sessionId,
|
|
5718
|
+
number: String(pullNumber),
|
|
5719
|
+
...extra
|
|
5720
|
+
};
|
|
5558
5721
|
}
|
|
5559
|
-
function
|
|
5722
|
+
function answer(value) {
|
|
5560
5723
|
const body = record$5(value);
|
|
5561
|
-
if (body === void 0) throw new
|
|
5562
|
-
return body;
|
|
5563
|
-
}
|
|
5564
|
-
function status(value) {
|
|
5565
|
-
const body = payload(value);
|
|
5566
|
-
if (typeof body.connected !== "boolean") throw new JiraClientError("Invalid Jira status response.");
|
|
5724
|
+
if (body === void 0) throw new Error("Invalid pull request feedback response.");
|
|
5567
5725
|
return body;
|
|
5568
5726
|
}
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
}
|
|
5573
|
-
throw jiraFailure(cause);
|
|
5574
|
-
}
|
|
5727
|
+
/** Every arm of this route shells out to `gh`, so reads and writes alike take
|
|
5728
|
+
* the remote budget. */
|
|
5729
|
+
async function loadJson(path, sessionId, pullNumber, signal, extra) {
|
|
5730
|
+
return answer(await pluginRead(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", signal, { query: feedbackQuery(sessionId, pullNumber, extra) }));
|
|
5575
5731
|
}
|
|
5576
|
-
async function
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
}
|
|
5732
|
+
async function postJson(path, sessionId, pullNumber, input) {
|
|
5733
|
+
return answer(await pluginWrite(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", void 0, {
|
|
5734
|
+
query: feedbackQuery(sessionId, pullNumber),
|
|
5735
|
+
json: input
|
|
5736
|
+
}));
|
|
5582
5737
|
}
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
throw jiraFailure(cause);
|
|
5588
|
-
}
|
|
5738
|
+
function reviewComment(value) {
|
|
5739
|
+
const input = record$5(value);
|
|
5740
|
+
if (input === void 0 || typeof input.id !== "number" || typeof input.path !== "string" || typeof input.author !== "string" || typeof input.body !== "string" || typeof input.url !== "string" || input.avatarUrl !== void 0 && githubAvatarUrl(input.avatarUrl) === void 0 || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || input.createdAt !== void 0 && typeof input.createdAt !== "string" || input.bot !== void 0 && typeof input.bot !== "boolean") return void 0;
|
|
5741
|
+
return input;
|
|
5589
5742
|
}
|
|
5590
|
-
async function
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5743
|
+
async function loadPullRequestThreads(sessionId, pullNumber, signal) {
|
|
5744
|
+
const body = await loadJson("/comments", sessionId, pullNumber, signal);
|
|
5745
|
+
if (!Array.isArray(body.threads)) throw new Error("Invalid pull request comments response.");
|
|
5746
|
+
const threads = [];
|
|
5747
|
+
for (const item of body.threads) {
|
|
5748
|
+
const input = record$5(item);
|
|
5749
|
+
if (input === void 0 || typeof input.id !== "string" || typeof input.path !== "string" || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || !Array.isArray(input.comments)) continue;
|
|
5750
|
+
const comments = input.comments.map(reviewComment).filter((value) => value !== void 0);
|
|
5751
|
+
if (comments.length === 0) continue;
|
|
5752
|
+
threads.push({
|
|
5753
|
+
id: input.id,
|
|
5754
|
+
path: input.path,
|
|
5755
|
+
...typeof input.line === "number" ? { line: input.line } : {},
|
|
5756
|
+
side: input.side,
|
|
5757
|
+
resolved: input.resolved === true,
|
|
5758
|
+
outdated: input.outdated === true,
|
|
5759
|
+
comments
|
|
5760
|
+
});
|
|
5603
5761
|
}
|
|
5762
|
+
return threads;
|
|
5604
5763
|
}
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
}
|
|
5764
|
+
/** Post one reply into the thread that `commentId` belongs to. */
|
|
5765
|
+
async function replyToReviewThread(sessionId, pullNumber, commentId, body) {
|
|
5766
|
+
const comment = reviewComment((await postJson("/reply", sessionId, pullNumber, {
|
|
5767
|
+
commentId,
|
|
5768
|
+
body
|
|
5769
|
+
})).comment);
|
|
5770
|
+
if (comment === void 0) throw new Error("Invalid pull request reply response.");
|
|
5771
|
+
return comment;
|
|
5772
|
+
}
|
|
5773
|
+
/** Resolve or reopen a thread; returns the state GitHub reports afterwards. */
|
|
5774
|
+
async function setReviewThreadResolved(sessionId, pullNumber, threadId, resolved) {
|
|
5775
|
+
const answer = await postJson("/resolve", sessionId, pullNumber, {
|
|
5776
|
+
threadId,
|
|
5777
|
+
resolved
|
|
5778
|
+
});
|
|
5779
|
+
if (typeof answer.resolved !== "boolean") throw new Error("Invalid pull request resolve response.");
|
|
5780
|
+
return answer.resolved;
|
|
5781
|
+
}
|
|
5782
|
+
/** Logins GitHub would notify, for the reply composer's `@` completion. */
|
|
5783
|
+
async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
|
|
5784
|
+
const body = await loadJson("/mentionables", sessionId, pullNumber, signal, { q: query });
|
|
5785
|
+
if (!Array.isArray(body.users)) return [];
|
|
5786
|
+
const users = [];
|
|
5787
|
+
for (const item of body.users) {
|
|
5788
|
+
const input = record$5(item);
|
|
5789
|
+
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
5790
|
+
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
5791
|
+
users.push({
|
|
5792
|
+
login: input.login,
|
|
5793
|
+
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
5794
|
+
});
|
|
5795
|
+
}
|
|
5796
|
+
return users;
|
|
5797
|
+
}
|
|
5798
|
+
async function loadFailingChecks(sessionId, pullNumber, signal) {
|
|
5799
|
+
const body = await loadJson("/checks", sessionId, pullNumber, signal);
|
|
5800
|
+
if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
|
|
5801
|
+
const checks = [];
|
|
5802
|
+
for (const item of body.checks) {
|
|
5803
|
+
const input = record$5(item);
|
|
5804
|
+
if (input === void 0 || typeof input.name !== "string" || input.link !== void 0 && typeof input.link !== "string" || input.description !== void 0 && typeof input.description !== "string" || input.log !== void 0 && typeof input.log !== "string") continue;
|
|
5805
|
+
checks.push(input);
|
|
5806
|
+
}
|
|
5807
|
+
return checks;
|
|
5808
|
+
}
|
|
5809
|
+
/** Draft handed to Claude when the user forwards GitHub review comments. A
|
|
5810
|
+
* resolved thread is a settled conversation: forwarding it would ask Claude to
|
|
5811
|
+
* redo work the reviewers already signed off. */
|
|
5812
|
+
function composeCommentsPrompt(threads) {
|
|
5813
|
+
const open = threads.filter((thread) => !thread.resolved);
|
|
5814
|
+
if (open.length === 0) return "";
|
|
5815
|
+
return `Please address the following GitHub pull request review comments. Make the requested changes, or explain briefly when a comment should not be applied.\n\n${open.map((thread) => {
|
|
5816
|
+
const [first, ...rest] = thread.comments;
|
|
5817
|
+
if (first === void 0) return "";
|
|
5818
|
+
return [`- ${`${thread.path}${thread.line === void 0 ? "" : `:${thread.line}`}`} (@${first.author}): ${first.body.replaceAll("\n", "\n ")}`, ...rest.map((reply) => ` (@${reply.author}): ${reply.body.replaceAll("\n", "\n ")}`)].join("\n");
|
|
5819
|
+
}).filter((block) => block.length > 0).join("\n")}`;
|
|
5820
|
+
}
|
|
5821
|
+
/** Draft handed to Claude when the user forwards failing CI checks. */
|
|
5822
|
+
function composeChecksPrompt(checks) {
|
|
5823
|
+
return `The following CI checks are failing on the current pull request. Investigate the failure logs, fix the underlying problems, and re-run the relevant commands locally when possible.\n\n${checks.map((check) => {
|
|
5824
|
+
return `${`## ${check.name}${check.link === void 0 ? "" : ` (${check.link})`}`}${check.description === void 0 ? "" : `\n${check.description}`}${check.log === void 0 ? "" : `\n\n\`\`\`\n${check.log}\n\`\`\``}`;
|
|
5825
|
+
}).join("\n\n")}`;
|
|
5826
|
+
}
|
|
5827
|
+
/** Draft handed to Claude after an update-branch merge left conflicts behind. */
|
|
5828
|
+
function composeConflictsPrompt(baseBranch, conflicts, method = "merge") {
|
|
5829
|
+
const list = conflicts.map((file) => `- ${file}`).join("\n");
|
|
5830
|
+
if (method === "rebase") return `Rebasing the current branch onto origin/${baseBranch} stopped on conflicts in the files below. Resolve each conflict preserving the intent of both sides, stage the files, run \`git rebase --continue\` until the rebase finishes, then push with \`git push --force-with-lease\`.\n\n${list}`;
|
|
5831
|
+
return `Merging origin/${baseBranch} into the current branch left merge conflicts in the files below. Resolve each conflict preserving the intent of both sides, then commit the merge.\n\n${list}`;
|
|
5832
|
+
}
|
|
5833
|
+
//#endregion
|
|
5834
|
+
//#region src/client/auto-fix.ts
|
|
5835
|
+
const AUTO_FIX_INTERVAL_MS = 3e4;
|
|
5836
|
+
const AUTO_FIX_FOOTER = "This request was generated automatically by the pull request watcher. After making the changes, commit and push to the pull request branch so the checks re-run.";
|
|
5837
|
+
const EMPTY_MEMORY = { handledCommentIds: /* @__PURE__ */ new Set() };
|
|
5838
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
5839
|
+
function session(sessionId) {
|
|
5840
|
+
let entry = sessions.get(sessionId);
|
|
5841
|
+
if (entry === void 0) {
|
|
5842
|
+
entry = {
|
|
5843
|
+
enabled: false,
|
|
5844
|
+
memory: EMPTY_MEMORY
|
|
5845
|
+
};
|
|
5846
|
+
sessions.set(sessionId, entry);
|
|
5847
|
+
}
|
|
5848
|
+
return entry;
|
|
5849
|
+
}
|
|
5850
|
+
function autoFixEnabled(sessionId) {
|
|
5851
|
+
return session(sessionId).enabled;
|
|
5852
|
+
}
|
|
5853
|
+
function setAutoFixEnabled(sessionId, enabled) {
|
|
5854
|
+
session(sessionId).enabled = enabled;
|
|
5855
|
+
}
|
|
5856
|
+
function autoFixMemory(sessionId) {
|
|
5857
|
+
return session(sessionId).memory;
|
|
5858
|
+
}
|
|
5859
|
+
function rememberAutoFix(sessionId, memory) {
|
|
5860
|
+
session(sessionId).memory = memory;
|
|
5861
|
+
}
|
|
5862
|
+
/** One failing CI run yields one fix attempt: run links change when CI re-runs. */
|
|
5863
|
+
function checksSignature(checks) {
|
|
5864
|
+
if (checks.length === 0) return void 0;
|
|
5865
|
+
return checks.map((check) => `${check.name}|${check.link ?? ""}`).sort().join("\n");
|
|
5866
|
+
}
|
|
5867
|
+
function planAutoFix(memory, threads, checks) {
|
|
5868
|
+
const unhandled = threads.filter((thread) => !thread.resolved).filter((thread) => thread.comments.some((comment) => !memory.handledCommentIds.has(comment.id)));
|
|
5869
|
+
const fresh = unhandled.flatMap((thread) => thread.comments);
|
|
5870
|
+
const signature = checksSignature(checks);
|
|
5871
|
+
const checksChanged = signature !== void 0 && signature !== memory.handledChecksSignature;
|
|
5872
|
+
const sections = [];
|
|
5873
|
+
if (unhandled.length > 0) sections.push(composeCommentsPrompt(unhandled));
|
|
5874
|
+
if (checksChanged) sections.push(composeChecksPrompt(checks));
|
|
5875
|
+
if (sections.length === 0) return { memory };
|
|
5876
|
+
const nextSignature = checksChanged ? signature : memory.handledChecksSignature;
|
|
5877
|
+
return {
|
|
5878
|
+
prompt: `${sections.join("\n\n")}\n\n${AUTO_FIX_FOOTER}`,
|
|
5879
|
+
memory: {
|
|
5880
|
+
handledCommentIds: /* @__PURE__ */ new Set([...memory.handledCommentIds, ...fresh.map((comment) => comment.id)]),
|
|
5881
|
+
...nextSignature === void 0 ? {} : { handledChecksSignature: nextSignature }
|
|
5882
|
+
}
|
|
5883
|
+
};
|
|
5884
|
+
}
|
|
5885
|
+
//#endregion
|
|
5886
|
+
//#region src/client/session-preset.ts
|
|
5887
|
+
/**
|
|
5888
|
+
* Resolve one row's preset id, newest seat first.
|
|
5889
|
+
* @param row - a session-list row, or undefined when the id is not listed.
|
|
5890
|
+
* @returns the preset id, or undefined when neither source carries one.
|
|
5891
|
+
*/
|
|
5892
|
+
function sessionRowPreset(row) {
|
|
5893
|
+
return row?.agentPreset ?? row?.projectionValues?.agentPreset ?? void 0;
|
|
5894
|
+
}
|
|
5895
|
+
//#endregion
|
|
5896
|
+
//#region src/client/ClaudePullRequestsPanel.tsx
|
|
5897
|
+
const NO_WORKSPACE_STATE = {};
|
|
5898
|
+
const NO_WORKSPACES = {
|
|
5899
|
+
subscribe: () => () => {},
|
|
5900
|
+
getSnapshot: () => NO_WORKSPACE_STATE
|
|
5901
|
+
};
|
|
5902
|
+
const OVERVIEW_REFRESH_MS = 3e4;
|
|
5903
|
+
/** What a running session is blocked on: the latest permission or question
|
|
5904
|
+
* activity that is still in its started phase. */
|
|
5905
|
+
function overviewAttention(activities) {
|
|
5906
|
+
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
5907
|
+
const activity = activities[index];
|
|
5908
|
+
if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
|
|
5909
|
+
return activity.phase === "started" ? activity.kind : void 0;
|
|
5910
|
+
}
|
|
5911
|
+
}
|
|
5912
|
+
/** Claude sessions worth listing: rows still in the host list (byId keeps
|
|
5913
|
+
* deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
|
|
5914
|
+
* running first. */
|
|
5915
|
+
function claudeSessionRows(state, archivedSessionIds = []) {
|
|
5916
|
+
const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
|
|
5917
|
+
const archived = new Set(archivedSessionIds);
|
|
5918
|
+
return rows.filter((row) => row !== void 0 && sessionRowPreset(row) === "claude" && row.blank !== true && row.origin !== "subagent" && !archived.has(row.id) && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
|
|
5919
|
+
}
|
|
5920
|
+
function Badge({ label, tone = "neutral" }) {
|
|
5921
|
+
const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
|
|
5922
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
5923
|
+
style: {
|
|
5924
|
+
...repositoryItem,
|
|
5925
|
+
...toneStyle
|
|
5926
|
+
},
|
|
5927
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5928
|
+
style: repositoryItemDot,
|
|
5929
|
+
"aria-hidden": "true"
|
|
5930
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5931
|
+
style: repositoryItemLabel,
|
|
5932
|
+
children: label
|
|
5933
|
+
})]
|
|
5934
|
+
});
|
|
5935
|
+
}
|
|
5936
|
+
function repositoryName$1(remote) {
|
|
5937
|
+
return remote?.split("/").at(-1);
|
|
5938
|
+
}
|
|
5939
|
+
function OverviewAttention({ source, running, t }) {
|
|
5940
|
+
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
5941
|
+
const attention = running ? overviewAttention(snapshot.activities) : void 0;
|
|
5942
|
+
const usage = snapshot.contextUsage;
|
|
5943
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
5944
|
+
attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
|
|
5945
|
+
label: t("overviewNeedsPermission"),
|
|
5946
|
+
tone: "warning"
|
|
5947
|
+
}) : null,
|
|
5948
|
+
attention === "question" ? /* @__PURE__ */ jsx(Badge, {
|
|
5949
|
+
label: t("overviewNeedsAnswer"),
|
|
5950
|
+
tone: "warning"
|
|
5951
|
+
}) : null,
|
|
5952
|
+
usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
|
|
5953
|
+
] });
|
|
5954
|
+
}
|
|
5955
|
+
function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
|
|
5956
|
+
const sessionStore = useMemo(() => ({
|
|
5957
|
+
subscribe: (listener) => sessions.subscribe(listener),
|
|
5958
|
+
getSnapshot: () => sessions.getSnapshot()
|
|
5959
|
+
}), [sessions]);
|
|
5960
|
+
const snapshot = useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
|
|
5961
|
+
const workspaceStore = useMemo(() => {
|
|
5962
|
+
const source = workspaces ?? NO_WORKSPACES;
|
|
5963
|
+
return {
|
|
5964
|
+
subscribe: (listener) => source.subscribe(listener),
|
|
5965
|
+
getSnapshot: () => source.getSnapshot()
|
|
5966
|
+
};
|
|
5967
|
+
}, [workspaces]);
|
|
5968
|
+
const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
|
|
5969
|
+
const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
|
|
5970
|
+
const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
|
|
5971
|
+
const [statuses, setStatuses] = useState({});
|
|
5972
|
+
useEffect(() => {
|
|
5973
|
+
const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
|
|
5974
|
+
if (cwds.length === 0) return;
|
|
5975
|
+
const controller = new AbortController();
|
|
5976
|
+
const refresh = () => {
|
|
5977
|
+
for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
|
|
5978
|
+
if (!controller.signal.aborted) setStatuses((previous) => ({
|
|
5979
|
+
...previous,
|
|
5980
|
+
[cwd]: status
|
|
5981
|
+
}));
|
|
5982
|
+
}, () => void 0);
|
|
5983
|
+
};
|
|
5984
|
+
refresh();
|
|
5985
|
+
const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
|
|
5986
|
+
return () => {
|
|
5987
|
+
controller.abort();
|
|
5988
|
+
clearInterval(timer);
|
|
5989
|
+
};
|
|
5990
|
+
}, [cwdKey, loadStatus]);
|
|
5991
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5992
|
+
className: detailsCardClass,
|
|
5993
|
+
style: tasksPanel,
|
|
5994
|
+
children: [
|
|
5995
|
+
/* @__PURE__ */ jsxs("style", {
|
|
5996
|
+
"data-dsh-claude-overview-styles": true,
|
|
5997
|
+
children: [detailsCardCss, panelIconButtonCss]
|
|
5998
|
+
}),
|
|
5999
|
+
/* @__PURE__ */ jsxs("header", {
|
|
6000
|
+
style: tasksHeader,
|
|
6001
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
6002
|
+
style: tasksHeading,
|
|
6003
|
+
children: t("overviewTitle")
|
|
6004
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
6005
|
+
style: tasksTurnMeta,
|
|
6006
|
+
children: t("overviewBody")
|
|
6007
|
+
})] }), /* @__PURE__ */ jsx("button", {
|
|
6008
|
+
type: "button",
|
|
6009
|
+
className: panelIconButtonClass,
|
|
6010
|
+
"aria-label": t("diffClose"),
|
|
6011
|
+
onClick: closeDetails,
|
|
6012
|
+
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
6013
|
+
})]
|
|
6014
|
+
}),
|
|
6015
|
+
/* @__PURE__ */ jsx("div", {
|
|
6016
|
+
style: overviewBody,
|
|
6017
|
+
children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
6018
|
+
style: overviewEmpty,
|
|
6019
|
+
children: t("overviewEmpty")
|
|
6020
|
+
}) : rows.map((row) => {
|
|
6021
|
+
const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
|
|
6022
|
+
const pullRequest = repository?.pullRequest;
|
|
6023
|
+
const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
|
|
6024
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
6025
|
+
type: "button",
|
|
6026
|
+
style: overviewRow,
|
|
6027
|
+
onClick: () => {
|
|
6028
|
+
openSession(row.id);
|
|
6029
|
+
},
|
|
6030
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
6031
|
+
style: overviewRowTop,
|
|
6032
|
+
children: [
|
|
6033
|
+
row.running === true ? /* @__PURE__ */ jsx("span", {
|
|
6034
|
+
style: overviewRunningDot,
|
|
6035
|
+
"aria-label": t("overviewRunning")
|
|
6036
|
+
}) : null,
|
|
6037
|
+
/* @__PURE__ */ jsx("span", {
|
|
6038
|
+
style: overviewTitle,
|
|
6039
|
+
children: row.displayTitle ?? row.id
|
|
6040
|
+
}),
|
|
6041
|
+
pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
|
|
6042
|
+
label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
|
|
6043
|
+
tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
|
|
6044
|
+
})
|
|
6045
|
+
]
|
|
6046
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
6047
|
+
style: overviewMeta,
|
|
6048
|
+
children: [
|
|
6049
|
+
repositoryName$1(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName$1(repository?.remote) }),
|
|
6050
|
+
/* @__PURE__ */ jsx("span", {
|
|
6051
|
+
style: overviewBranch,
|
|
6052
|
+
children: branch
|
|
6053
|
+
}),
|
|
6054
|
+
pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
6055
|
+
label: t(`repositoryChecks_${pullRequest.checks}`),
|
|
6056
|
+
tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
|
|
6057
|
+
}) : null,
|
|
6058
|
+
pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
6059
|
+
label: t(`repositoryReview_${pullRequest.review}`),
|
|
6060
|
+
tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
|
|
6061
|
+
}) : null,
|
|
6062
|
+
autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
|
|
6063
|
+
label: t("overviewAutoFix"),
|
|
6064
|
+
tone: "success"
|
|
6065
|
+
}) : null,
|
|
6066
|
+
projectionFor === void 0 ? null : /* @__PURE__ */ jsx(OverviewAttention, {
|
|
6067
|
+
source: projectionFor(row.id),
|
|
6068
|
+
running: row.running === true,
|
|
6069
|
+
t
|
|
6070
|
+
})
|
|
6071
|
+
]
|
|
6072
|
+
})]
|
|
6073
|
+
}, row.id);
|
|
6074
|
+
})
|
|
6075
|
+
})
|
|
6076
|
+
]
|
|
6077
|
+
});
|
|
6078
|
+
}
|
|
6079
|
+
//#endregion
|
|
6080
|
+
//#region src/client/session-alerts.ts
|
|
6081
|
+
/** Desktop notifications for the sessions the user is not looking at.
|
|
6082
|
+
*
|
|
6083
|
+
* The session board already knows which session is blocked on an approval or
|
|
6084
|
+
* a question and which has gone quiet — but only while it is open, which
|
|
6085
|
+
* makes the user the poller. Running several worktree sessions at once is the
|
|
6086
|
+
* workflow this plugin is built for, and it is the one thing that gets worse
|
|
6087
|
+
* the more of them there are.
|
|
6088
|
+
*
|
|
6089
|
+
* Everything here is derived from the two feeds the board already reads, so a
|
|
6090
|
+
* standing watcher costs one more subscriber on the shared projection
|
|
6091
|
+
* carrier rather than a stream per session.
|
|
6092
|
+
*/
|
|
6093
|
+
/** ponytail: module state, like the auto-fix toggle next door. The Settings
|
|
6094
|
+
* panel and the boot read both write it, and the watcher reads it per alert,
|
|
6095
|
+
* so switching alerts off lands without a reload. */
|
|
6096
|
+
let alertsEnabled = true;
|
|
6097
|
+
function claudeAlertsEnabled() {
|
|
6098
|
+
return alertsEnabled;
|
|
6099
|
+
}
|
|
6100
|
+
function setClaudeAlertsEnabled(enabled) {
|
|
6101
|
+
alertsEnabled = enabled;
|
|
6102
|
+
}
|
|
6103
|
+
/** The alert one observation earns, or undefined when nothing happened that is
|
|
6104
|
+
* worth interrupting the user for.
|
|
6105
|
+
*
|
|
6106
|
+
* A session observed for the first time earns nothing: the watcher starts
|
|
6107
|
+
* with every session unknown, and announcing the state each one merely
|
|
6108
|
+
* happens to be in would greet a restart with a burst. */
|
|
6109
|
+
function sessionAlert(previous, next) {
|
|
6110
|
+
if (previous === void 0) return void 0;
|
|
6111
|
+
if (next.attention !== void 0 && next.attention !== previous.attention) return next.attention;
|
|
6112
|
+
if (previous.running && !next.running) return "idle";
|
|
6113
|
+
}
|
|
6114
|
+
/** Deliver one alert as a desktop notification.
|
|
6115
|
+
*
|
|
6116
|
+
* Best effort throughout: a Host without the Notification API, or a user who
|
|
6117
|
+
* has refused permission, simply gets no alerts. The tag collapses repeat
|
|
6118
|
+
* alerts for one session into a single banner rather than a stack. */
|
|
6119
|
+
function postSessionAlert(alert) {
|
|
6120
|
+
if (typeof Notification === "undefined" || Notification.permission === "denied") return;
|
|
6121
|
+
const show = () => {
|
|
6122
|
+
try {
|
|
6123
|
+
const notification = new Notification(alert.title, {
|
|
6124
|
+
body: alert.body,
|
|
6125
|
+
tag: `dsh-claude:${alert.sessionId}`
|
|
6126
|
+
});
|
|
6127
|
+
notification.onclick = () => {
|
|
6128
|
+
globalThis.focus?.();
|
|
6129
|
+
alert.open();
|
|
6130
|
+
};
|
|
6131
|
+
} catch {}
|
|
6132
|
+
};
|
|
6133
|
+
if (Notification.permission === "granted") show();
|
|
6134
|
+
else Notification.requestPermission().then((result) => {
|
|
6135
|
+
if (result === "granted") show();
|
|
6136
|
+
}, () => void 0);
|
|
6137
|
+
}
|
|
6138
|
+
const BODY_KEY = {
|
|
6139
|
+
permission: "alertNeedsPermission",
|
|
6140
|
+
question: "alertNeedsAnswer",
|
|
6141
|
+
idle: "alertTurnFinished"
|
|
6142
|
+
};
|
|
6143
|
+
/** Watch every Claude session and announce the ones that need the user.
|
|
6144
|
+
* Returns the unsubscriber. */
|
|
6145
|
+
function startClaudeSessionAlerts(deps) {
|
|
6146
|
+
const post = deps.post ?? postSessionAlert;
|
|
6147
|
+
const known = /* @__PURE__ */ new Map();
|
|
6148
|
+
const projections = /* @__PURE__ */ new Map();
|
|
6149
|
+
let disposed = false;
|
|
6150
|
+
const announce = (row, kind) => {
|
|
6151
|
+
if (!(deps.enabled ?? claudeAlertsEnabled)()) return;
|
|
6152
|
+
post({
|
|
6153
|
+
sessionId: row.id,
|
|
6154
|
+
title: row.displayTitle ?? deps.t("alertFallbackTitle"),
|
|
6155
|
+
body: deps.t(BODY_KEY[kind]),
|
|
6156
|
+
open: () => {
|
|
6157
|
+
deps.open(row.id);
|
|
6158
|
+
}
|
|
6159
|
+
});
|
|
6160
|
+
};
|
|
6161
|
+
const evaluate = () => {
|
|
6162
|
+
if (disposed) return;
|
|
6163
|
+
const snapshot = deps.sessions.getSnapshot();
|
|
6164
|
+
const rows = claudeSessionRows(snapshot);
|
|
6165
|
+
const live = new Set(rows.map((row) => row.id));
|
|
6166
|
+
for (const [sessionId, unsubscribe] of [...projections]) {
|
|
6167
|
+
if (live.has(sessionId)) continue;
|
|
6168
|
+
unsubscribe();
|
|
6169
|
+
projections.delete(sessionId);
|
|
6170
|
+
known.delete(sessionId);
|
|
6171
|
+
}
|
|
6172
|
+
for (const row of rows) {
|
|
6173
|
+
const source = deps.projectionFor(row.id);
|
|
6174
|
+
if (!projections.has(row.id)) projections.set(row.id, source.subscribe(evaluate));
|
|
6175
|
+
const running = row.running === true;
|
|
6176
|
+
const next = {
|
|
6177
|
+
running,
|
|
6178
|
+
attention: running ? overviewAttention(source.getSnapshot().activities) : void 0
|
|
6179
|
+
};
|
|
6180
|
+
const previous = known.get(row.id);
|
|
6181
|
+
known.set(row.id, next);
|
|
6182
|
+
if (row.id === snapshot.current) continue;
|
|
6183
|
+
const kind = sessionAlert(previous, next);
|
|
6184
|
+
if (kind !== void 0) announce(row, kind);
|
|
6185
|
+
}
|
|
6186
|
+
};
|
|
6187
|
+
const unsubscribe = deps.sessions.subscribe(evaluate);
|
|
6188
|
+
evaluate();
|
|
6189
|
+
return () => {
|
|
6190
|
+
disposed = true;
|
|
6191
|
+
unsubscribe();
|
|
6192
|
+
for (const dispose of projections.values()) dispose();
|
|
6193
|
+
projections.clear();
|
|
6194
|
+
known.clear();
|
|
6195
|
+
};
|
|
6196
|
+
}
|
|
6197
|
+
//#endregion
|
|
6198
|
+
//#region src/client/jira-api.ts
|
|
6199
|
+
var JiraClientError = class extends Error {
|
|
6200
|
+
code;
|
|
6201
|
+
constructor(message, code) {
|
|
6202
|
+
super(message);
|
|
6203
|
+
this.name = "JiraClientError";
|
|
6204
|
+
if (code !== void 0) this.code = code;
|
|
6205
|
+
}
|
|
6206
|
+
};
|
|
6207
|
+
function record$4(value) {
|
|
6208
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6209
|
+
}
|
|
6210
|
+
/**
|
|
6211
|
+
* Every Jira failure the panels catch is a `JiraClientError`, whatever the
|
|
6212
|
+
* transport threw: `ClaudeHeroRepositoryControls` branches on `code` to tell
|
|
6213
|
+
* 'not-connected' apart from a real outage, and the settings card renders the
|
|
6214
|
+
* message verbatim.
|
|
6215
|
+
*
|
|
6216
|
+
* The routes answer `{ error, message }`, so `message` is already the sentence
|
|
6217
|
+
* to show. A body carrying only a code — a 405, a bad JSON body — used to read
|
|
6218
|
+
* 'Jira is unavailable.' rather than leaking the code as prose, and it still
|
|
6219
|
+
* does. Transport failures (a starved pool, an elapsed budget, an older Host
|
|
6220
|
+
* without the route) carry their own wording and keep it.
|
|
6221
|
+
*/
|
|
6222
|
+
function jiraFailure(cause) {
|
|
6223
|
+
if (cause instanceof JiraClientError) return cause;
|
|
6224
|
+
if (!(cause instanceof PluginRequestError)) return new JiraClientError(cause instanceof Error ? cause.message : String(cause));
|
|
6225
|
+
return new JiraClientError(cause.message === cause.code ? "Jira is unavailable." : cause.message, cause.code);
|
|
6226
|
+
}
|
|
6227
|
+
function payload(value) {
|
|
6228
|
+
const body = record$4(value);
|
|
6229
|
+
if (body === void 0) throw new JiraClientError("Invalid Jira response.");
|
|
6230
|
+
return body;
|
|
6231
|
+
}
|
|
6232
|
+
function status(value) {
|
|
6233
|
+
const body = payload(value);
|
|
6234
|
+
if (typeof body.connected !== "boolean") throw new JiraClientError("Invalid Jira status response.");
|
|
6235
|
+
return body;
|
|
6236
|
+
}
|
|
6237
|
+
async function loadJiraStatus(signal) {
|
|
6238
|
+
try {
|
|
6239
|
+
return status(await pluginRead(`${CLAUDE_JIRA_PATH}/status`, "remote", signal));
|
|
6240
|
+
} catch (cause) {
|
|
6241
|
+
throw jiraFailure(cause);
|
|
6242
|
+
}
|
|
6243
|
+
}
|
|
6244
|
+
async function connectJira(input) {
|
|
6245
|
+
try {
|
|
6246
|
+
return status(await pluginWrite(`${CLAUDE_JIRA_PATH}/connect`, "remote", void 0, { json: input }));
|
|
6247
|
+
} catch (cause) {
|
|
6248
|
+
throw jiraFailure(cause);
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
async function disconnectJira() {
|
|
6252
|
+
try {
|
|
6253
|
+
await pluginWrite(`${CLAUDE_JIRA_PATH}/disconnect`, "remote");
|
|
6254
|
+
} catch (cause) {
|
|
6255
|
+
throw jiraFailure(cause);
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
async function searchJiraTickets(query, signal) {
|
|
6259
|
+
try {
|
|
6260
|
+
const body = payload(await pluginRead(`${CLAUDE_JIRA_PATH}/search`, "remote", signal, { query: { query } }));
|
|
6261
|
+
if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
|
|
6262
|
+
const tickets = [];
|
|
6263
|
+
for (const item of body.tickets) {
|
|
6264
|
+
const ticket = record$4(item);
|
|
6265
|
+
if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
|
|
6266
|
+
tickets.push(ticket);
|
|
6267
|
+
}
|
|
6268
|
+
return tickets;
|
|
6269
|
+
} catch (cause) {
|
|
6270
|
+
throw jiraFailure(cause);
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
async function assignJiraTicket(key) {
|
|
6274
|
+
try {
|
|
6275
|
+
await pluginWrite(`${CLAUDE_JIRA_PATH}/assign`, "remote", void 0, { json: { key } });
|
|
6276
|
+
} catch (cause) {
|
|
6277
|
+
throw jiraFailure(cause);
|
|
6278
|
+
}
|
|
5611
6279
|
}
|
|
5612
6280
|
/** Draft seeded into the composer when a session starts from a ticket. */
|
|
5613
6281
|
function ticketPrompt(ticket) {
|
|
@@ -5841,6 +6509,12 @@ window.__ModuleLoader__.load({
|
|
|
5841
6509
|
const value = settings.find((setting) => setting.key === "prose")?.value;
|
|
5842
6510
|
return isClaudeProseMode(value) ? value : DEFAULT_CLAUDE_PROSE_MODE;
|
|
5843
6511
|
}
|
|
6512
|
+
/** The alert mode a settings payload carries, read the same way and for the
|
|
6513
|
+
* same reason as {@link proseModeOf}. */
|
|
6514
|
+
function alertModeOf(settings) {
|
|
6515
|
+
const value = settings.find((setting) => setting.key === "alerts")?.value;
|
|
6516
|
+
return isClaudeAlertMode(value) ? value : "on";
|
|
6517
|
+
}
|
|
5844
6518
|
/** Settings whose row only makes sense under a particular value of another.
|
|
5845
6519
|
* Filtering here rather than server-side keeps the descriptor list flat: the
|
|
5846
6520
|
* server has no view of what the Client can paint. Fails OPEN — a payload
|
|
@@ -5866,6 +6540,10 @@ window.__ModuleLoader__.load({
|
|
|
5866
6540
|
label: "prose",
|
|
5867
6541
|
hint: "proseEffect"
|
|
5868
6542
|
},
|
|
6543
|
+
alerts: {
|
|
6544
|
+
label: "alerts",
|
|
6545
|
+
hint: "alertsEffect"
|
|
6546
|
+
},
|
|
5869
6547
|
worktreeBranchPrefix: {
|
|
5870
6548
|
label: "worktreeBranchPrefix",
|
|
5871
6549
|
hint: "worktreeBranchPrefixEffect"
|
|
@@ -5886,7 +6564,9 @@ window.__ModuleLoader__.load({
|
|
|
5886
6564
|
"renderer:plugin": "rendererPlugin",
|
|
5887
6565
|
"renderer:native": "rendererNative",
|
|
5888
6566
|
"prose:plain": "prosePlain",
|
|
5889
|
-
"prose:enhanced": "proseEnhanced"
|
|
6567
|
+
"prose:enhanced": "proseEnhanced",
|
|
6568
|
+
"alerts:off": "alertsOff",
|
|
6569
|
+
"alerts:on": "alertsOn"
|
|
5890
6570
|
};
|
|
5891
6571
|
function settingOptionLabel(settingKey, option, t) {
|
|
5892
6572
|
const key = SETTING_OPTION_COPY[`${settingKey}:${option.value}`];
|
|
@@ -6126,6 +6806,7 @@ window.__ModuleLoader__.load({
|
|
|
6126
6806
|
if (!isGlobalSettingsView(payload)) throw new Error("Invalid global settings response");
|
|
6127
6807
|
setGlobalSettings(payload);
|
|
6128
6808
|
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
6809
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
6129
6810
|
} catch (cause) {
|
|
6130
6811
|
setGlobalSettingsError(cardFailure(cause));
|
|
6131
6812
|
} finally {
|
|
@@ -6449,30 +7130,555 @@ window.__ModuleLoader__.load({
|
|
|
6449
7130
|
children: updateBusy === "check" ? t("checkingUpdates") : t("checkUpdates")
|
|
6450
7131
|
}), /* @__PURE__ */ jsx("button", {
|
|
6451
7132
|
type: "button",
|
|
6452
|
-
style: primaryButton,
|
|
6453
|
-
onClick: () => {
|
|
6454
|
-
requestUpdate("update");
|
|
7133
|
+
style: primaryButton,
|
|
7134
|
+
onClick: () => {
|
|
7135
|
+
requestUpdate("update");
|
|
7136
|
+
},
|
|
7137
|
+
disabled: updateBusy !== void 0 || updateStatus?.canUpdate !== true,
|
|
7138
|
+
children: updateBusy === "update" ? t("updatingPlugin") : t("updatePlugin")
|
|
7139
|
+
})]
|
|
7140
|
+
})
|
|
7141
|
+
]
|
|
7142
|
+
}),
|
|
7143
|
+
/* @__PURE__ */ jsxs("section", {
|
|
7144
|
+
style: settingsCard,
|
|
7145
|
+
children: [/* @__PURE__ */ jsx("h3", {
|
|
7146
|
+
style: settingsSectionHeading,
|
|
7147
|
+
children: t("security")
|
|
7148
|
+
}), /* @__PURE__ */ jsx("p", {
|
|
7149
|
+
style: settingsBody,
|
|
7150
|
+
children: t("securityBody")
|
|
7151
|
+
})]
|
|
7152
|
+
})
|
|
7153
|
+
]
|
|
7154
|
+
});
|
|
7155
|
+
}
|
|
7156
|
+
//#endregion
|
|
7157
|
+
//#region src/client/plan-feedback-api.ts
|
|
7158
|
+
/** Send one plan back for changes. */
|
|
7159
|
+
async function sendPlanForChanges(sessionId, toolUseId, notes) {
|
|
7160
|
+
try {
|
|
7161
|
+
await pluginWrite(CLAUDE_PLAN_FEEDBACK_PATH, "fast", void 0, {
|
|
7162
|
+
query: { sessionId },
|
|
7163
|
+
json: {
|
|
7164
|
+
toolUseId,
|
|
7165
|
+
notes
|
|
7166
|
+
}
|
|
7167
|
+
});
|
|
7168
|
+
} catch (error) {
|
|
7169
|
+
const settled = error instanceof PluginRequestError && error.reason === "http" && error.status === 409;
|
|
7170
|
+
throw new Error(settled ? "planSettled" : "planFeedbackFailed");
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
//#endregion
|
|
7174
|
+
//#region src/client/ClaudePlanPanel.tsx
|
|
7175
|
+
const PLAN_TOOL = "ExitPlanMode";
|
|
7176
|
+
/** Every plan this session handed over, oldest first, each with where its
|
|
7177
|
+
* approval stands.
|
|
7178
|
+
*
|
|
7179
|
+
* The permission bridge writes one `started` record carrying the plan and,
|
|
7180
|
+
* once the user decides, a second record under the same `toolUseId` whose
|
|
7181
|
+
* phase says which way it went. Both arrive on the ordinary activity stream,
|
|
7182
|
+
* so the panel needs no channel of its own — it reads the transcript the
|
|
7183
|
+
* session already has. */
|
|
7184
|
+
function planReviews(activities) {
|
|
7185
|
+
const plans = /* @__PURE__ */ new Map();
|
|
7186
|
+
const states = /* @__PURE__ */ new Map();
|
|
7187
|
+
for (const activity of activities) {
|
|
7188
|
+
const { toolUseId } = activity;
|
|
7189
|
+
if (activity.kind !== "permission" || toolUseId === void 0) continue;
|
|
7190
|
+
if (activity.toolName === PLAN_TOOL && activity.phase === "started" && activity.text !== void 0 && activity.text.length > 0) {
|
|
7191
|
+
plans.set(toolUseId, activity.text);
|
|
7192
|
+
if (!states.has(toolUseId)) states.set(toolUseId, "pending");
|
|
7193
|
+
}
|
|
7194
|
+
if (activity.phase === "completed") states.set(toolUseId, "approved");
|
|
7195
|
+
else if (activity.phase === "denied" || activity.phase === "failed") states.set(toolUseId, "rejected");
|
|
7196
|
+
}
|
|
7197
|
+
return [...plans].map(([toolUseId, plan]) => ({
|
|
7198
|
+
toolUseId,
|
|
7199
|
+
plan,
|
|
7200
|
+
state: states.get(toolUseId) ?? "pending"
|
|
7201
|
+
}));
|
|
7202
|
+
}
|
|
7203
|
+
/** The newest plan, for readers that only care what is on the table now. */
|
|
7204
|
+
function latestPlanReview(activities) {
|
|
7205
|
+
return planReviews(activities).at(-1);
|
|
7206
|
+
}
|
|
7207
|
+
/** A plan's own first heading, so a list of several can name them.
|
|
7208
|
+
*
|
|
7209
|
+
* Falls back to the opening line: a plan without a heading is unusual but
|
|
7210
|
+
* still has to be pickable. Fenced blocks are not scanned — a `#` comment in
|
|
7211
|
+
* the first code block of a heading-less plan is a worse label than the first
|
|
7212
|
+
* line, but not a wrong one, and the cost of getting it exactly right is a
|
|
7213
|
+
* fence-state machine for a fallback. */
|
|
7214
|
+
function planTitle(plan) {
|
|
7215
|
+
for (const raw of plan.split("\n")) {
|
|
7216
|
+
const line = raw.trim();
|
|
7217
|
+
if (line.length === 0) continue;
|
|
7218
|
+
const heading = /^#{1,6}\s+(.+?)\s*#*$/u.exec(line);
|
|
7219
|
+
if (heading?.[1] !== void 0) return heading[1].slice(0, MAX_TITLE_CHARS);
|
|
7220
|
+
return line.slice(0, MAX_TITLE_CHARS);
|
|
7221
|
+
}
|
|
7222
|
+
return "";
|
|
7223
|
+
}
|
|
7224
|
+
const MAX_TITLE_CHARS = 80;
|
|
7225
|
+
const MAX_QUOTE_CHARS = 1e3;
|
|
7226
|
+
/** The passage under the current selection, when it lies inside the plan body.
|
|
7227
|
+
*
|
|
7228
|
+
* Reads the live selection rather than mirroring the DOM: the body is the
|
|
7229
|
+
* Host's Markdown output, which this package renders but does not own, and
|
|
7230
|
+
* the only stable thing about it is that it is inside this element. */
|
|
7231
|
+
function quotedSelection(selection, body) {
|
|
7232
|
+
if (selection === null || body === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
|
|
7233
|
+
const range = selection.getRangeAt(0);
|
|
7234
|
+
if (!body.contains(range.startContainer) || !body.contains(range.endContainer)) return void 0;
|
|
7235
|
+
const text = selection.toString().trim();
|
|
7236
|
+
return text.length === 0 ? void 0 : text.slice(0, MAX_QUOTE_CHARS);
|
|
7237
|
+
}
|
|
7238
|
+
/** The newest review as one primitive, for readers that only need to know
|
|
7239
|
+
* whether it changed. A snapshot hook keeps its value only while the
|
|
7240
|
+
* selection compares equal, and a fresh object per snapshot would defeat
|
|
7241
|
+
* that. Empty string when the session has proposed nothing. */
|
|
7242
|
+
function planReviewKey(activities) {
|
|
7243
|
+
const review = latestPlanReview(activities);
|
|
7244
|
+
return review === void 0 ? "" : `${review.state}:${review.toolUseId}`;
|
|
7245
|
+
}
|
|
7246
|
+
/** Split what {@link planReviewKey} joined. */
|
|
7247
|
+
function parsePlanReviewKey(key) {
|
|
7248
|
+
const cut = key.indexOf(":");
|
|
7249
|
+
if (cut < 0) return void 0;
|
|
7250
|
+
const state = key.slice(0, cut);
|
|
7251
|
+
if (state !== "pending" && state !== "approved" && state !== "rejected") return void 0;
|
|
7252
|
+
return {
|
|
7253
|
+
state,
|
|
7254
|
+
toolUseId: key.slice(cut + 1)
|
|
7255
|
+
};
|
|
7256
|
+
}
|
|
7257
|
+
/** Restore-from-maximized: four corners pulling inward. Mirrors the diff
|
|
7258
|
+
* panel's own, which the primitives set has no counterpart for. */
|
|
7259
|
+
function RestorePanelIcon$1() {
|
|
7260
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
7261
|
+
width: "16",
|
|
7262
|
+
height: "16",
|
|
7263
|
+
viewBox: "0 0 16 16",
|
|
7264
|
+
fill: "currentColor",
|
|
7265
|
+
"aria-hidden": "true",
|
|
7266
|
+
children: /* @__PURE__ */ jsx("path", { d: "M1.5 5h3V2h1.4v4.4H1.5V5Zm9.9-3h1.4v3h3v1.4h-4.4V2ZM1.5 9.6h4.4V14H4.5v-3h-3V9.6Zm9.9 0h4.4V11h-3v3h-1.4V9.6Z" })
|
|
7267
|
+
});
|
|
7268
|
+
}
|
|
7269
|
+
/** Card and rows reproduce the primitives' menu surface, like the session
|
|
7270
|
+
* menu next door: r7, inverted hairline, shadow-lv3, 2px inset. */
|
|
7271
|
+
const PICKER_CSS = [
|
|
7272
|
+
".dsh-claude-plan-picker-root{position:relative;display:inline-flex;min-width:0}",
|
|
7273
|
+
".dsh-claude-plan-picker{display:inline-flex;align-items:center;gap:4px;min-width:0;padding:2px 6px;",
|
|
7274
|
+
"margin:-2px -6px;border:0;border-radius:7px;background:transparent;color:inherit;font:inherit;cursor:pointer;",
|
|
7275
|
+
"transition:background .12s ease}",
|
|
7276
|
+
".dsh-claude-plan-picker:hover,.dsh-claude-plan-picker[aria-expanded=\"true\"]{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7277
|
+
".dsh-claude-plan-picker:focus-visible{outline:none;background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7278
|
+
".dsh-claude-plan-picker>svg{flex:none;transition:transform .12s ease}",
|
|
7279
|
+
".dsh-claude-plan-picker[aria-expanded=\"true\"]>svg{transform:rotate(180deg)}",
|
|
7280
|
+
".dsh-claude-plan-picker-card{box-sizing:border-box;position:absolute;top:calc(100% + 6px);left:-6px;z-index:100;",
|
|
7281
|
+
"display:flex;flex-direction:column;gap:1px;padding:2px;min-width:240px;max-width:min(420px,70vw);",
|
|
7282
|
+
"max-height:min(320px,50vh);overflow-y:auto;border:1px solid var(--dsw-alias-border-inverted);",
|
|
7283
|
+
"border-radius:7px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3)}",
|
|
7284
|
+
".dsh-claude-plan-picker-item{display:flex;align-items:center;gap:8px;width:100%;min-height:26px;",
|
|
7285
|
+
"padding:5px 8px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-primary);",
|
|
7286
|
+
"font:inherit;font-size:12px;line-height:17px;text-align:left;cursor:pointer}",
|
|
7287
|
+
".dsh-claude-plan-picker-item:hover,.dsh-claude-plan-picker-item:focus-visible{outline:none;",
|
|
7288
|
+
"background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7289
|
+
".dsh-claude-plan-picker-item[aria-current=\"true\"]{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7290
|
+
".dsh-claude-plan-picker-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
7291
|
+
".dsh-claude-plan-picker-ordinal{flex:none;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}"
|
|
7292
|
+
].join("");
|
|
7293
|
+
function ChevronDownIcon() {
|
|
7294
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
7295
|
+
width: "12",
|
|
7296
|
+
height: "12",
|
|
7297
|
+
viewBox: "0 0 12 12",
|
|
7298
|
+
fill: "none",
|
|
7299
|
+
"aria-hidden": "true",
|
|
7300
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
7301
|
+
d: "m3 4.75 3 3 3-3",
|
|
7302
|
+
stroke: "currentColor",
|
|
7303
|
+
strokeWidth: "1.5",
|
|
7304
|
+
strokeLinecap: "round",
|
|
7305
|
+
strokeLinejoin: "round"
|
|
7306
|
+
})
|
|
7307
|
+
});
|
|
7308
|
+
}
|
|
7309
|
+
const STATE_LABEL = {
|
|
7310
|
+
pending: "planPending",
|
|
7311
|
+
approved: "planApproved",
|
|
7312
|
+
rejected: "planRejected"
|
|
7313
|
+
};
|
|
7314
|
+
/** The plan behind an `ExitPlanMode` approval, as the document it was written
|
|
7315
|
+
* as. The decision itself stays with the Host's approval dialog; this panel
|
|
7316
|
+
* is where the plan is actually read, under the reader's own prose palette. */
|
|
7317
|
+
function ClaudePlanPanel({ useClaudeProjection, t, sessionId, closeDetails, maximized, toggleMaximized, submitChanges = sendPlanForChanges }) {
|
|
7318
|
+
const markdownLabels = useClaudeMarkdownLabels(t);
|
|
7319
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
7320
|
+
const reviews = planReviews(useClaudeProjection((projection) => projection.activities));
|
|
7321
|
+
const [chosen, setChosen] = useState();
|
|
7322
|
+
const [open, setOpen] = useState(false);
|
|
7323
|
+
const picker = useRef(null);
|
|
7324
|
+
useDismissOnOutsidePointer(picker, open, (next) => {
|
|
7325
|
+
if (!next) setOpen(false);
|
|
7326
|
+
});
|
|
7327
|
+
const pendingId = reviews.find((item) => item.state === "pending")?.toolUseId;
|
|
7328
|
+
useEffect(() => {
|
|
7329
|
+
if (pendingId !== void 0) setChosen(pendingId);
|
|
7330
|
+
}, [pendingId]);
|
|
7331
|
+
const review = reviews.find((item) => item.toolUseId === chosen) ?? reviews.at(-1);
|
|
7332
|
+
const [notes, setNotes] = useState([]);
|
|
7333
|
+
const [draft, setDraft] = useState("");
|
|
7334
|
+
const [quote, setQuote] = useState();
|
|
7335
|
+
const [sending, setSending] = useState(false);
|
|
7336
|
+
const [failure, setFailure] = useState();
|
|
7337
|
+
const body = useRef(null);
|
|
7338
|
+
const composer = useRef(null);
|
|
7339
|
+
useEffect(() => {
|
|
7340
|
+
if (typeof document === "undefined") return;
|
|
7341
|
+
const read = () => {
|
|
7342
|
+
const selected = quotedSelection(document.getSelection(), body.current);
|
|
7343
|
+
if (selected !== void 0) setQuote(selected);
|
|
7344
|
+
};
|
|
7345
|
+
document.addEventListener("selectionchange", read);
|
|
7346
|
+
return () => {
|
|
7347
|
+
document.removeEventListener("selectionchange", read);
|
|
7348
|
+
};
|
|
7349
|
+
}, []);
|
|
7350
|
+
useEffect(() => {
|
|
7351
|
+
setNotes([]);
|
|
7352
|
+
setDraft("");
|
|
7353
|
+
setQuote(void 0);
|
|
7354
|
+
setFailure(void 0);
|
|
7355
|
+
}, [review?.toolUseId]);
|
|
7356
|
+
const addNote = useCallback(() => {
|
|
7357
|
+
const text = draft.trim();
|
|
7358
|
+
if (text.length === 0) return;
|
|
7359
|
+
setNotes((current) => [...current, quote === void 0 ? { text } : {
|
|
7360
|
+
quote,
|
|
7361
|
+
text
|
|
7362
|
+
}]);
|
|
7363
|
+
setDraft("");
|
|
7364
|
+
setQuote(void 0);
|
|
7365
|
+
}, [draft, quote]);
|
|
7366
|
+
const send = useCallback(() => {
|
|
7367
|
+
if (review === void 0 || sending) return;
|
|
7368
|
+
const text = draft.trim();
|
|
7369
|
+
const pending = text.length === 0 ? notes : [...notes, quote === void 0 ? { text } : {
|
|
7370
|
+
quote,
|
|
7371
|
+
text
|
|
7372
|
+
}];
|
|
7373
|
+
if (pending.length === 0) return;
|
|
7374
|
+
setSending(true);
|
|
7375
|
+
setFailure(void 0);
|
|
7376
|
+
submitChanges(sessionId, review.toolUseId, pending).then(() => {
|
|
7377
|
+
setNotes([]);
|
|
7378
|
+
setDraft("");
|
|
7379
|
+
setQuote(void 0);
|
|
7380
|
+
setSending(false);
|
|
7381
|
+
}, (error) => {
|
|
7382
|
+
setFailure(error instanceof Error && error.message === "planSettled" ? "planSettled" : "planFeedbackFailed");
|
|
7383
|
+
setSending(false);
|
|
7384
|
+
});
|
|
7385
|
+
}, [
|
|
7386
|
+
draft,
|
|
7387
|
+
notes,
|
|
7388
|
+
quote,
|
|
7389
|
+
review,
|
|
7390
|
+
sending,
|
|
7391
|
+
sessionId,
|
|
7392
|
+
submitChanges
|
|
7393
|
+
]);
|
|
7394
|
+
useEffect(() => {
|
|
7395
|
+
if (!owned || review === void 0) closeDetails();
|
|
7396
|
+
}, [
|
|
7397
|
+
closeDetails,
|
|
7398
|
+
owned,
|
|
7399
|
+
review === void 0
|
|
7400
|
+
]);
|
|
7401
|
+
if (!owned) return null;
|
|
7402
|
+
const index = review === void 0 ? -1 : reviews.findIndex((item) => item.toolUseId === review.toolUseId);
|
|
7403
|
+
const badge = (state) => ({
|
|
7404
|
+
...planBadge,
|
|
7405
|
+
...state === "pending" ? planBadgePending : state === "rejected" ? planBadgeRejected : {}
|
|
7406
|
+
});
|
|
7407
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7408
|
+
className: detailsCardClass,
|
|
7409
|
+
style: {
|
|
7410
|
+
...tasksPanel,
|
|
7411
|
+
...maximized ? diffPanelMaximized : {}
|
|
7412
|
+
},
|
|
7413
|
+
children: [
|
|
7414
|
+
/* @__PURE__ */ jsxs("style", {
|
|
7415
|
+
"data-dsh-claude-panel-icon-styles": true,
|
|
7416
|
+
children: [
|
|
7417
|
+
detailsCardCss,
|
|
7418
|
+
panelIconButtonCss,
|
|
7419
|
+
PICKER_CSS
|
|
7420
|
+
]
|
|
7421
|
+
}),
|
|
7422
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7423
|
+
style: tasksHeader,
|
|
7424
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7425
|
+
style: planHeaderStart,
|
|
7426
|
+
children: [reviews.length < 2 ? /* @__PURE__ */ jsx("span", {
|
|
7427
|
+
style: tasksHeading,
|
|
7428
|
+
children: t("planPanelTitle")
|
|
7429
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
7430
|
+
className: "dsh-claude-plan-picker-root",
|
|
7431
|
+
ref: picker,
|
|
7432
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
7433
|
+
type: "button",
|
|
7434
|
+
className: "dsh-claude-plan-picker",
|
|
7435
|
+
"aria-expanded": open,
|
|
7436
|
+
"aria-haspopup": "listbox",
|
|
7437
|
+
onClick: () => {
|
|
7438
|
+
setOpen((value) => !value);
|
|
7439
|
+
},
|
|
7440
|
+
children: [
|
|
7441
|
+
/* @__PURE__ */ jsx("span", {
|
|
7442
|
+
style: tasksHeading,
|
|
7443
|
+
children: t("planPanelTitle")
|
|
7444
|
+
}),
|
|
7445
|
+
/* @__PURE__ */ jsx("span", {
|
|
7446
|
+
style: planCount,
|
|
7447
|
+
children: t("planNth", {
|
|
7448
|
+
index: index + 1,
|
|
7449
|
+
total: reviews.length
|
|
7450
|
+
})
|
|
7451
|
+
}),
|
|
7452
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, {})
|
|
7453
|
+
]
|
|
7454
|
+
}), !open ? null : /* @__PURE__ */ jsx("div", {
|
|
7455
|
+
className: "dsh-claude-plan-picker-card",
|
|
7456
|
+
role: "listbox",
|
|
7457
|
+
"aria-label": t("planHistory"),
|
|
7458
|
+
children: [...reviews].reverse().map((item, offset) => /* @__PURE__ */ jsxs("button", {
|
|
7459
|
+
type: "button",
|
|
7460
|
+
role: "option",
|
|
7461
|
+
className: "dsh-claude-plan-picker-item",
|
|
7462
|
+
"aria-current": item.toolUseId === review?.toolUseId,
|
|
7463
|
+
"aria-selected": item.toolUseId === review?.toolUseId,
|
|
7464
|
+
onClick: () => {
|
|
7465
|
+
setChosen(item.toolUseId);
|
|
7466
|
+
setOpen(false);
|
|
7467
|
+
},
|
|
7468
|
+
children: [
|
|
7469
|
+
/* @__PURE__ */ jsx("span", {
|
|
7470
|
+
className: "dsh-claude-plan-picker-ordinal",
|
|
7471
|
+
children: reviews.length - offset
|
|
7472
|
+
}),
|
|
7473
|
+
/* @__PURE__ */ jsx("span", {
|
|
7474
|
+
className: "dsh-claude-plan-picker-title",
|
|
7475
|
+
children: planTitle(item.plan)
|
|
7476
|
+
}),
|
|
7477
|
+
/* @__PURE__ */ jsx("span", {
|
|
7478
|
+
style: badge(item.state),
|
|
7479
|
+
children: t(STATE_LABEL[item.state])
|
|
7480
|
+
})
|
|
7481
|
+
]
|
|
7482
|
+
}, item.toolUseId))
|
|
7483
|
+
})]
|
|
7484
|
+
}), review === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
7485
|
+
style: badge(review.state),
|
|
7486
|
+
children: t(STATE_LABEL[review.state])
|
|
7487
|
+
})]
|
|
7488
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
7489
|
+
style: planHeaderEnd,
|
|
7490
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
7491
|
+
type: "button",
|
|
7492
|
+
className: panelIconButtonClass,
|
|
7493
|
+
"aria-label": maximized ? t("planRestore") : t("planMaximize"),
|
|
7494
|
+
onClick: toggleMaximized,
|
|
7495
|
+
children: maximized ? /* @__PURE__ */ jsx(RestorePanelIcon$1, {}) : /* @__PURE__ */ jsx(IconFullscreenOutline16, {})
|
|
7496
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7497
|
+
type: "button",
|
|
7498
|
+
className: panelIconButtonClass,
|
|
7499
|
+
"aria-label": t("planClose"),
|
|
7500
|
+
onClick: closeDetails,
|
|
7501
|
+
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
7502
|
+
})]
|
|
7503
|
+
})]
|
|
7504
|
+
}),
|
|
7505
|
+
/* @__PURE__ */ jsx("div", {
|
|
7506
|
+
style: tasksBody,
|
|
7507
|
+
ref: body,
|
|
7508
|
+
children: review === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
7509
|
+
style: tasksGroupEmpty,
|
|
7510
|
+
children: t("planEmpty")
|
|
7511
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [review.state === "pending" ? /* @__PURE__ */ jsx("p", {
|
|
7512
|
+
style: planHint,
|
|
7513
|
+
children: t("planPendingHint")
|
|
7514
|
+
}) : null, /* @__PURE__ */ jsx(ClaudeMarkdown, {
|
|
7515
|
+
text: review.plan,
|
|
7516
|
+
labels: markdownLabels
|
|
7517
|
+
}, review.toolUseId)] })
|
|
7518
|
+
}),
|
|
7519
|
+
review?.state !== "pending" ? null : /* @__PURE__ */ jsxs("div", {
|
|
7520
|
+
style: planComposer,
|
|
7521
|
+
children: [
|
|
7522
|
+
notes.length === 0 ? null : /* @__PURE__ */ jsx("ul", {
|
|
7523
|
+
style: planNoteList,
|
|
7524
|
+
children: notes.map((note, index) => /* @__PURE__ */ jsxs("li", {
|
|
7525
|
+
style: planNote,
|
|
7526
|
+
children: [
|
|
7527
|
+
note.quote === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
7528
|
+
style: planNoteQuote,
|
|
7529
|
+
children: note.quote
|
|
7530
|
+
}),
|
|
7531
|
+
/* @__PURE__ */ jsx("p", {
|
|
7532
|
+
style: planNoteText,
|
|
7533
|
+
children: note.text
|
|
7534
|
+
}),
|
|
7535
|
+
/* @__PURE__ */ jsx("button", {
|
|
7536
|
+
type: "button",
|
|
7537
|
+
style: planNoteRemove,
|
|
7538
|
+
"aria-label": t("planNoteRemove"),
|
|
7539
|
+
onClick: () => {
|
|
7540
|
+
setNotes((current) => current.filter((_, at) => at !== index));
|
|
7541
|
+
},
|
|
7542
|
+
children: "×"
|
|
7543
|
+
})
|
|
7544
|
+
]
|
|
7545
|
+
}, index))
|
|
7546
|
+
}),
|
|
7547
|
+
quote === void 0 ? null : /* @__PURE__ */ jsxs("div", {
|
|
7548
|
+
style: planQuoteChip,
|
|
7549
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7550
|
+
style: planNoteQuote,
|
|
7551
|
+
children: quote
|
|
7552
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7553
|
+
type: "button",
|
|
7554
|
+
style: planNoteRemove,
|
|
7555
|
+
"aria-label": t("planQuoteClear"),
|
|
7556
|
+
onClick: () => {
|
|
7557
|
+
setQuote(void 0);
|
|
7558
|
+
},
|
|
7559
|
+
children: "×"
|
|
7560
|
+
})]
|
|
7561
|
+
}),
|
|
7562
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
7563
|
+
ref: composer,
|
|
7564
|
+
value: draft,
|
|
7565
|
+
placeholder: t(quote === void 0 ? "planNotePlaceholder" : "planNoteQuotedPlaceholder"),
|
|
7566
|
+
style: planComposerInput,
|
|
7567
|
+
onChange: (event) => {
|
|
7568
|
+
setDraft(event.currentTarget.value);
|
|
7569
|
+
},
|
|
7570
|
+
onKeyDown: (event) => {
|
|
7571
|
+
if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
|
|
7572
|
+
event.preventDefault();
|
|
7573
|
+
if (event.metaKey || event.ctrlKey) send();
|
|
7574
|
+
else addNote();
|
|
7575
|
+
}
|
|
7576
|
+
}),
|
|
7577
|
+
failure === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
7578
|
+
role: "alert",
|
|
7579
|
+
style: planComposerError,
|
|
7580
|
+
children: t(failure)
|
|
7581
|
+
}),
|
|
7582
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7583
|
+
style: planComposerActions,
|
|
7584
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7585
|
+
style: planComposerHint,
|
|
7586
|
+
children: t("planNoteHint")
|
|
7587
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7588
|
+
type: "button",
|
|
7589
|
+
style: {
|
|
7590
|
+
...askButton,
|
|
7591
|
+
...askPrimaryButton
|
|
6455
7592
|
},
|
|
6456
|
-
disabled:
|
|
6457
|
-
|
|
7593
|
+
disabled: sending || notes.length === 0 && draft.trim().length === 0,
|
|
7594
|
+
onClick: send,
|
|
7595
|
+
children: t(sending ? "planSending" : "planSendForChanges")
|
|
6458
7596
|
})]
|
|
6459
7597
|
})
|
|
6460
7598
|
]
|
|
6461
|
-
}),
|
|
6462
|
-
/* @__PURE__ */ jsxs("section", {
|
|
6463
|
-
style: settingsCard,
|
|
6464
|
-
children: [/* @__PURE__ */ jsx("h3", {
|
|
6465
|
-
style: settingsSectionHeading,
|
|
6466
|
-
children: t("security")
|
|
6467
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
6468
|
-
style: settingsBody,
|
|
6469
|
-
children: t("securityBody")
|
|
6470
|
-
})]
|
|
6471
7599
|
})
|
|
6472
7600
|
]
|
|
6473
7601
|
});
|
|
6474
7602
|
}
|
|
6475
7603
|
//#endregion
|
|
7604
|
+
//#region src/client/ClaudePlanHeaderAction.tsx
|
|
7605
|
+
/** Same resting-quiet treatment as the diff action next to it, plus a dot for
|
|
7606
|
+
* a plan still waiting on its approval dialog. */
|
|
7607
|
+
const ACTION_CSS$1 = [
|
|
7608
|
+
".dsh-claude-header-plan{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;",
|
|
7609
|
+
"width:32px;height:32px;padding:0;border:0;border-radius:9px;background:transparent;",
|
|
7610
|
+
"color:var(--dsw-alias-label-secondary);cursor:pointer;",
|
|
7611
|
+
"transition:background .12s ease,color .12s ease}",
|
|
7612
|
+
".dsh-claude-header-plan:hover,.dsh-claude-header-plan:focus-visible,.dsh-claude-header-plan[aria-pressed=\"true\"]{",
|
|
7613
|
+
"background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
7614
|
+
".dsh-claude-header-plan:active{background:var(--dsw-alias-interactive-bg-active)}",
|
|
7615
|
+
".dsh-claude-header-plan:focus-visible{outline:none}",
|
|
7616
|
+
".dsh-claude-header-plan>*{flex:none}",
|
|
7617
|
+
".dsh-claude-header-plan>svg.dsh-claude-header-plan-glyph{",
|
|
7618
|
+
"width:18px;height:18px;display:block;overflow:visible;opacity:1;",
|
|
7619
|
+
"fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}",
|
|
7620
|
+
".dsh-claude-header-plan[data-pending]::after{content:\"\";position:absolute;top:6px;right:6px;",
|
|
7621
|
+
"width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-warning-primary,#e2a03f);",
|
|
7622
|
+
"box-shadow:0 0 0 2px var(--dsw-alias-bg-base)}"
|
|
7623
|
+
].join("");
|
|
7624
|
+
let cssInjected$3 = false;
|
|
7625
|
+
function ensureCss$3() {
|
|
7626
|
+
if (cssInjected$3 || typeof document === "undefined") return;
|
|
7627
|
+
cssInjected$3 = true;
|
|
7628
|
+
const element = document.createElement("style");
|
|
7629
|
+
element.dataset.dshClaudeHeaderPlan = "";
|
|
7630
|
+
element.textContent = ACTION_CSS$1;
|
|
7631
|
+
document.head.appendChild(element);
|
|
7632
|
+
}
|
|
7633
|
+
/** A written page, not a checklist: a plan is a document to read, and ticks
|
|
7634
|
+
* next to lines are what the task board means. Drawn inline for the same
|
|
7635
|
+
* reason the diff glyph is — the primitives set ships neither. */
|
|
7636
|
+
function PlanGlyph() {
|
|
7637
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
7638
|
+
className: "dsh-claude-header-plan-glyph",
|
|
7639
|
+
width: "18",
|
|
7640
|
+
height: "18",
|
|
7641
|
+
viewBox: "0 0 18 18",
|
|
7642
|
+
fill: "none",
|
|
7643
|
+
"aria-hidden": "true",
|
|
7644
|
+
focusable: "false",
|
|
7645
|
+
children: [
|
|
7646
|
+
/* @__PURE__ */ jsx("path", { d: "M10.5 2.25H5a1.75 1.75 0 0 0-1.75 1.75v10A1.75 1.75 0 0 0 5 15.75h8A1.75 1.75 0 0 0 14.75 14V6.5Z" }),
|
|
7647
|
+
/* @__PURE__ */ jsx("path", { d: "M10.5 2.25V6.5h4.25" }),
|
|
7648
|
+
/* @__PURE__ */ jsx("path", { d: "M6.25 9h5.5M6.25 12h3.5" })
|
|
7649
|
+
]
|
|
7650
|
+
});
|
|
7651
|
+
}
|
|
7652
|
+
function ClaudePlanHeaderAction({ t, sessionId, togglePlan, planOpen, useClaudeProjection }) {
|
|
7653
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
7654
|
+
const review = parsePlanReviewKey(useClaudeProjection((projection) => planReviewKey(projection.activities)));
|
|
7655
|
+
const open = useSyncExternalStore(planOpen.subscribe, planOpen.getSnapshot, planOpen.getSnapshot);
|
|
7656
|
+
const opened = useRef();
|
|
7657
|
+
useEffect(() => {
|
|
7658
|
+
if (review?.state !== "pending" || opened.current === review.toolUseId) return;
|
|
7659
|
+
opened.current = review.toolUseId;
|
|
7660
|
+
if (!open) togglePlan();
|
|
7661
|
+
});
|
|
7662
|
+
if (!owned || review === void 0) return null;
|
|
7663
|
+
ensureCss$3();
|
|
7664
|
+
const label = t(open ? "planClose" : "planOpen");
|
|
7665
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
7666
|
+
label,
|
|
7667
|
+
side: "bottom",
|
|
7668
|
+
delayMs: 250,
|
|
7669
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
7670
|
+
type: "button",
|
|
7671
|
+
className: "dsh-claude-header-plan",
|
|
7672
|
+
"aria-label": label,
|
|
7673
|
+
"aria-pressed": open,
|
|
7674
|
+
"data-pending": review.state === "pending" || void 0,
|
|
7675
|
+
"data-session": sessionId,
|
|
7676
|
+
onClick: togglePlan,
|
|
7677
|
+
children: /* @__PURE__ */ jsx(PlanGlyph, {})
|
|
7678
|
+
})
|
|
7679
|
+
});
|
|
7680
|
+
}
|
|
7681
|
+
//#endregion
|
|
6476
7682
|
//#region src/client/repository-action-api.ts
|
|
6477
7683
|
var RepositoryActionClientError = class extends Error {
|
|
6478
7684
|
code;
|
|
@@ -6484,7 +7690,7 @@ window.__ModuleLoader__.load({
|
|
|
6484
7690
|
if (commit !== void 0) this.commit = commit;
|
|
6485
7691
|
}
|
|
6486
7692
|
};
|
|
6487
|
-
function record$
|
|
7693
|
+
function record$3(value) {
|
|
6488
7694
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6489
7695
|
}
|
|
6490
7696
|
/** The dialog branches on `code`, so a route refusal keeps arriving as this
|
|
@@ -6497,20 +7703,20 @@ window.__ModuleLoader__.load({
|
|
|
6497
7703
|
return error instanceof PluginRequestError ? new RepositoryActionClientError(error.message, error.code) : error;
|
|
6498
7704
|
}
|
|
6499
7705
|
function preview(value) {
|
|
6500
|
-
const input = record$
|
|
7706
|
+
const input = record$3(value);
|
|
6501
7707
|
if (input === void 0 || typeof input.root !== "string" || typeof input.branch !== "string" || typeof input.head !== "string" || typeof input.fingerprint !== "string" || !Array.isArray(input.files) || typeof input.patch !== "string" || typeof input.truncated !== "boolean" || typeof input.hasStaged !== "boolean" || typeof input.hasUnstaged !== "boolean" || typeof input.hasUntracked !== "boolean" || input.upstream !== void 0 && typeof input.upstream !== "string" || !Array.isArray(input.unpushedCommits) || typeof input.unpushedTruncated !== "boolean") throw new Error("Invalid repository action preview.");
|
|
6502
7708
|
for (const file of input.files) {
|
|
6503
|
-
const item = record$
|
|
7709
|
+
const item = record$3(file);
|
|
6504
7710
|
if (item === void 0 || typeof item.path !== "string" || typeof item.staged !== "boolean" || typeof item.unstaged !== "boolean" || typeof item.untracked !== "boolean") throw new Error("Invalid repository action file.");
|
|
6505
7711
|
}
|
|
6506
7712
|
for (const commit of input.unpushedCommits) {
|
|
6507
|
-
const item = record$
|
|
7713
|
+
const item = record$3(commit);
|
|
6508
7714
|
if (item === void 0 || typeof item.hash !== "string" || typeof item.subject !== "string") throw new Error("Invalid repository action commit.");
|
|
6509
7715
|
}
|
|
6510
7716
|
return input;
|
|
6511
7717
|
}
|
|
6512
7718
|
function result(value) {
|
|
6513
|
-
const input = record$
|
|
7719
|
+
const input = record$3(value);
|
|
6514
7720
|
if (input === void 0 || typeof input.commit !== "string" || typeof input.pushed !== "boolean" || input.pullRequestUrl !== void 0 && typeof input.pullRequestUrl !== "string" || input.conflicts !== void 0 && (!Array.isArray(input.conflicts) || input.conflicts.some((item) => typeof item !== "string"))) throw new Error("Invalid repository action result.");
|
|
6515
7721
|
return input;
|
|
6516
7722
|
}
|
|
@@ -6524,7 +7730,7 @@ window.__ModuleLoader__.load({
|
|
|
6524
7730
|
}
|
|
6525
7731
|
async function generateCommitMessage(sessionId, fingerprint, signal) {
|
|
6526
7732
|
try {
|
|
6527
|
-
const value = record$
|
|
7733
|
+
const value = record$3(await pluginWrite(`${CLAUDE_REPOSITORY_ACTION_PATH}/message`, "remote", signal, {
|
|
6528
7734
|
query: { sessionId },
|
|
6529
7735
|
json: { fingerprint }
|
|
6530
7736
|
}));
|
|
@@ -6587,16 +7793,16 @@ window.__ModuleLoader__.load({
|
|
|
6587
7793
|
/** The draft only has to describe the work; the host truncates it again before
|
|
6588
7794
|
* summarizing, and the setup route caps the whole body at 16 KiB. */
|
|
6589
7795
|
const MAX_INTENT_CHARS = 2e3;
|
|
6590
|
-
function record$
|
|
7796
|
+
function record$2(value) {
|
|
6591
7797
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6592
7798
|
}
|
|
6593
7799
|
function setupResult(value) {
|
|
6594
|
-
const item = record$
|
|
7800
|
+
const item = record$2(value);
|
|
6595
7801
|
if (item === void 0 || item.mode !== "checkout" && item.mode !== "worktree" || typeof item.root !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || item.leaseId !== void 0 && typeof item.leaseId !== "string") return void 0;
|
|
6596
7802
|
return item;
|
|
6597
7803
|
}
|
|
6598
7804
|
function parseRepositorySetupEvent(line, onProgress) {
|
|
6599
|
-
const event = record$
|
|
7805
|
+
const event = record$2(JSON.parse(line));
|
|
6600
7806
|
if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
|
|
6601
7807
|
onProgress(event.stage);
|
|
6602
7808
|
return;
|
|
@@ -6704,203 +7910,14 @@ window.__ModuleLoader__.load({
|
|
|
6704
7910
|
/** Compact age of an ISO timestamp, in the shape the panels already use
|
|
6705
7911
|
* ("<1h", "4h", "3d", "2mo"). `now` is a parameter so callers that re-render
|
|
6706
7912
|
* on a clock — and tests — stay deterministic. */
|
|
6707
|
-
function relativeAge(value, now = Date.now()) {
|
|
6708
|
-
if (value === void 0) return void 0;
|
|
6709
|
-
const elapsedHours = Math.max(0, Math.floor((now - Date.parse(value)) / 36e5));
|
|
6710
|
-
if (!Number.isFinite(elapsedHours)) return void 0;
|
|
6711
|
-
if (elapsedHours < 1) return "<1h";
|
|
6712
|
-
if (elapsedHours < 24) return `${elapsedHours}h`;
|
|
6713
|
-
const days = Math.floor(elapsedHours / 24);
|
|
6714
|
-
return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
|
|
6715
|
-
}
|
|
6716
|
-
//#endregion
|
|
6717
|
-
//#region src/github-url.ts
|
|
6718
|
-
/** Only GitHub's own image hosts; the browser loads these directly, so a URL
|
|
6719
|
-
* the API did not vouch for must never become an outbound request. */
|
|
6720
|
-
function githubAvatarUrl(value) {
|
|
6721
|
-
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return void 0;
|
|
6722
|
-
try {
|
|
6723
|
-
const url = new URL(value);
|
|
6724
|
-
const allowed = url.hostname === "github.com" || url.hostname === "githubusercontent.com" || url.hostname.endsWith(".githubusercontent.com");
|
|
6725
|
-
return url.protocol === "https:" && allowed ? url.href : void 0;
|
|
6726
|
-
} catch {
|
|
6727
|
-
return;
|
|
6728
|
-
}
|
|
6729
|
-
}
|
|
6730
|
-
//#endregion
|
|
6731
|
-
//#region src/client/pr-feedback-api.ts
|
|
6732
|
-
function record$2(value) {
|
|
6733
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6734
|
-
}
|
|
6735
|
-
function feedbackQuery(sessionId, pullNumber, extra) {
|
|
6736
|
-
return {
|
|
6737
|
-
sessionId,
|
|
6738
|
-
number: String(pullNumber),
|
|
6739
|
-
...extra
|
|
6740
|
-
};
|
|
6741
|
-
}
|
|
6742
|
-
function answer(value) {
|
|
6743
|
-
const body = record$2(value);
|
|
6744
|
-
if (body === void 0) throw new Error("Invalid pull request feedback response.");
|
|
6745
|
-
return body;
|
|
6746
|
-
}
|
|
6747
|
-
/** Every arm of this route shells out to `gh`, so reads and writes alike take
|
|
6748
|
-
* the remote budget. */
|
|
6749
|
-
async function loadJson(path, sessionId, pullNumber, signal, extra) {
|
|
6750
|
-
return answer(await pluginRead(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", signal, { query: feedbackQuery(sessionId, pullNumber, extra) }));
|
|
6751
|
-
}
|
|
6752
|
-
async function postJson(path, sessionId, pullNumber, input) {
|
|
6753
|
-
return answer(await pluginWrite(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", void 0, {
|
|
6754
|
-
query: feedbackQuery(sessionId, pullNumber),
|
|
6755
|
-
json: input
|
|
6756
|
-
}));
|
|
6757
|
-
}
|
|
6758
|
-
function reviewComment(value) {
|
|
6759
|
-
const input = record$2(value);
|
|
6760
|
-
if (input === void 0 || typeof input.id !== "number" || typeof input.path !== "string" || typeof input.author !== "string" || typeof input.body !== "string" || typeof input.url !== "string" || input.avatarUrl !== void 0 && githubAvatarUrl(input.avatarUrl) === void 0 || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || input.createdAt !== void 0 && typeof input.createdAt !== "string" || input.bot !== void 0 && typeof input.bot !== "boolean") return void 0;
|
|
6761
|
-
return input;
|
|
6762
|
-
}
|
|
6763
|
-
async function loadPullRequestThreads(sessionId, pullNumber, signal) {
|
|
6764
|
-
const body = await loadJson("/comments", sessionId, pullNumber, signal);
|
|
6765
|
-
if (!Array.isArray(body.threads)) throw new Error("Invalid pull request comments response.");
|
|
6766
|
-
const threads = [];
|
|
6767
|
-
for (const item of body.threads) {
|
|
6768
|
-
const input = record$2(item);
|
|
6769
|
-
if (input === void 0 || typeof input.id !== "string" || typeof input.path !== "string" || input.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number" || !Array.isArray(input.comments)) continue;
|
|
6770
|
-
const comments = input.comments.map(reviewComment).filter((value) => value !== void 0);
|
|
6771
|
-
if (comments.length === 0) continue;
|
|
6772
|
-
threads.push({
|
|
6773
|
-
id: input.id,
|
|
6774
|
-
path: input.path,
|
|
6775
|
-
...typeof input.line === "number" ? { line: input.line } : {},
|
|
6776
|
-
side: input.side,
|
|
6777
|
-
resolved: input.resolved === true,
|
|
6778
|
-
outdated: input.outdated === true,
|
|
6779
|
-
comments
|
|
6780
|
-
});
|
|
6781
|
-
}
|
|
6782
|
-
return threads;
|
|
6783
|
-
}
|
|
6784
|
-
/** Post one reply into the thread that `commentId` belongs to. */
|
|
6785
|
-
async function replyToReviewThread(sessionId, pullNumber, commentId, body) {
|
|
6786
|
-
const comment = reviewComment((await postJson("/reply", sessionId, pullNumber, {
|
|
6787
|
-
commentId,
|
|
6788
|
-
body
|
|
6789
|
-
})).comment);
|
|
6790
|
-
if (comment === void 0) throw new Error("Invalid pull request reply response.");
|
|
6791
|
-
return comment;
|
|
6792
|
-
}
|
|
6793
|
-
/** Resolve or reopen a thread; returns the state GitHub reports afterwards. */
|
|
6794
|
-
async function setReviewThreadResolved(sessionId, pullNumber, threadId, resolved) {
|
|
6795
|
-
const answer = await postJson("/resolve", sessionId, pullNumber, {
|
|
6796
|
-
threadId,
|
|
6797
|
-
resolved
|
|
6798
|
-
});
|
|
6799
|
-
if (typeof answer.resolved !== "boolean") throw new Error("Invalid pull request resolve response.");
|
|
6800
|
-
return answer.resolved;
|
|
6801
|
-
}
|
|
6802
|
-
/** Logins GitHub would notify, for the reply composer's `@` completion. */
|
|
6803
|
-
async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
|
|
6804
|
-
const body = await loadJson("/mentionables", sessionId, pullNumber, signal, { q: query });
|
|
6805
|
-
if (!Array.isArray(body.users)) return [];
|
|
6806
|
-
const users = [];
|
|
6807
|
-
for (const item of body.users) {
|
|
6808
|
-
const input = record$2(item);
|
|
6809
|
-
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
6810
|
-
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
6811
|
-
users.push({
|
|
6812
|
-
login: input.login,
|
|
6813
|
-
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
6814
|
-
});
|
|
6815
|
-
}
|
|
6816
|
-
return users;
|
|
6817
|
-
}
|
|
6818
|
-
async function loadFailingChecks(sessionId, pullNumber, signal) {
|
|
6819
|
-
const body = await loadJson("/checks", sessionId, pullNumber, signal);
|
|
6820
|
-
if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
|
|
6821
|
-
const checks = [];
|
|
6822
|
-
for (const item of body.checks) {
|
|
6823
|
-
const input = record$2(item);
|
|
6824
|
-
if (input === void 0 || typeof input.name !== "string" || input.link !== void 0 && typeof input.link !== "string" || input.description !== void 0 && typeof input.description !== "string" || input.log !== void 0 && typeof input.log !== "string") continue;
|
|
6825
|
-
checks.push(input);
|
|
6826
|
-
}
|
|
6827
|
-
return checks;
|
|
6828
|
-
}
|
|
6829
|
-
/** Draft handed to Claude when the user forwards GitHub review comments. A
|
|
6830
|
-
* resolved thread is a settled conversation: forwarding it would ask Claude to
|
|
6831
|
-
* redo work the reviewers already signed off. */
|
|
6832
|
-
function composeCommentsPrompt(threads) {
|
|
6833
|
-
const open = threads.filter((thread) => !thread.resolved);
|
|
6834
|
-
if (open.length === 0) return "";
|
|
6835
|
-
return `Please address the following GitHub pull request review comments. Make the requested changes, or explain briefly when a comment should not be applied.\n\n${open.map((thread) => {
|
|
6836
|
-
const [first, ...rest] = thread.comments;
|
|
6837
|
-
if (first === void 0) return "";
|
|
6838
|
-
return [`- ${`${thread.path}${thread.line === void 0 ? "" : `:${thread.line}`}`} (@${first.author}): ${first.body.replaceAll("\n", "\n ")}`, ...rest.map((reply) => ` (@${reply.author}): ${reply.body.replaceAll("\n", "\n ")}`)].join("\n");
|
|
6839
|
-
}).filter((block) => block.length > 0).join("\n")}`;
|
|
6840
|
-
}
|
|
6841
|
-
/** Draft handed to Claude when the user forwards failing CI checks. */
|
|
6842
|
-
function composeChecksPrompt(checks) {
|
|
6843
|
-
return `The following CI checks are failing on the current pull request. Investigate the failure logs, fix the underlying problems, and re-run the relevant commands locally when possible.\n\n${checks.map((check) => {
|
|
6844
|
-
return `${`## ${check.name}${check.link === void 0 ? "" : ` (${check.link})`}`}${check.description === void 0 ? "" : `\n${check.description}`}${check.log === void 0 ? "" : `\n\n\`\`\`\n${check.log}\n\`\`\``}`;
|
|
6845
|
-
}).join("\n\n")}`;
|
|
6846
|
-
}
|
|
6847
|
-
/** Draft handed to Claude after an update-branch merge left conflicts behind. */
|
|
6848
|
-
function composeConflictsPrompt(baseBranch, conflicts, method = "merge") {
|
|
6849
|
-
const list = conflicts.map((file) => `- ${file}`).join("\n");
|
|
6850
|
-
if (method === "rebase") return `Rebasing the current branch onto origin/${baseBranch} stopped on conflicts in the files below. Resolve each conflict preserving the intent of both sides, stage the files, run \`git rebase --continue\` until the rebase finishes, then push with \`git push --force-with-lease\`.\n\n${list}`;
|
|
6851
|
-
return `Merging origin/${baseBranch} into the current branch left merge conflicts in the files below. Resolve each conflict preserving the intent of both sides, then commit the merge.\n\n${list}`;
|
|
6852
|
-
}
|
|
6853
|
-
//#endregion
|
|
6854
|
-
//#region src/client/auto-fix.ts
|
|
6855
|
-
const AUTO_FIX_INTERVAL_MS = 3e4;
|
|
6856
|
-
const AUTO_FIX_FOOTER = "This request was generated automatically by the pull request watcher. After making the changes, commit and push to the pull request branch so the checks re-run.";
|
|
6857
|
-
const EMPTY_MEMORY = { handledCommentIds: /* @__PURE__ */ new Set() };
|
|
6858
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
6859
|
-
function session(sessionId) {
|
|
6860
|
-
let entry = sessions.get(sessionId);
|
|
6861
|
-
if (entry === void 0) {
|
|
6862
|
-
entry = {
|
|
6863
|
-
enabled: false,
|
|
6864
|
-
memory: EMPTY_MEMORY
|
|
6865
|
-
};
|
|
6866
|
-
sessions.set(sessionId, entry);
|
|
6867
|
-
}
|
|
6868
|
-
return entry;
|
|
6869
|
-
}
|
|
6870
|
-
function autoFixEnabled(sessionId) {
|
|
6871
|
-
return session(sessionId).enabled;
|
|
6872
|
-
}
|
|
6873
|
-
function setAutoFixEnabled(sessionId, enabled) {
|
|
6874
|
-
session(sessionId).enabled = enabled;
|
|
6875
|
-
}
|
|
6876
|
-
function autoFixMemory(sessionId) {
|
|
6877
|
-
return session(sessionId).memory;
|
|
6878
|
-
}
|
|
6879
|
-
function rememberAutoFix(sessionId, memory) {
|
|
6880
|
-
session(sessionId).memory = memory;
|
|
6881
|
-
}
|
|
6882
|
-
/** One failing CI run yields one fix attempt: run links change when CI re-runs. */
|
|
6883
|
-
function checksSignature(checks) {
|
|
6884
|
-
if (checks.length === 0) return void 0;
|
|
6885
|
-
return checks.map((check) => `${check.name}|${check.link ?? ""}`).sort().join("\n");
|
|
6886
|
-
}
|
|
6887
|
-
function planAutoFix(memory, threads, checks) {
|
|
6888
|
-
const unhandled = threads.filter((thread) => !thread.resolved).filter((thread) => thread.comments.some((comment) => !memory.handledCommentIds.has(comment.id)));
|
|
6889
|
-
const fresh = unhandled.flatMap((thread) => thread.comments);
|
|
6890
|
-
const signature = checksSignature(checks);
|
|
6891
|
-
const checksChanged = signature !== void 0 && signature !== memory.handledChecksSignature;
|
|
6892
|
-
const sections = [];
|
|
6893
|
-
if (unhandled.length > 0) sections.push(composeCommentsPrompt(unhandled));
|
|
6894
|
-
if (checksChanged) sections.push(composeChecksPrompt(checks));
|
|
6895
|
-
if (sections.length === 0) return { memory };
|
|
6896
|
-
const nextSignature = checksChanged ? signature : memory.handledChecksSignature;
|
|
6897
|
-
return {
|
|
6898
|
-
prompt: `${sections.join("\n\n")}\n\n${AUTO_FIX_FOOTER}`,
|
|
6899
|
-
memory: {
|
|
6900
|
-
handledCommentIds: /* @__PURE__ */ new Set([...memory.handledCommentIds, ...fresh.map((comment) => comment.id)]),
|
|
6901
|
-
...nextSignature === void 0 ? {} : { handledChecksSignature: nextSignature }
|
|
6902
|
-
}
|
|
6903
|
-
};
|
|
7913
|
+
function relativeAge(value, now = Date.now()) {
|
|
7914
|
+
if (value === void 0) return void 0;
|
|
7915
|
+
const elapsedHours = Math.max(0, Math.floor((now - Date.parse(value)) / 36e5));
|
|
7916
|
+
if (!Number.isFinite(elapsedHours)) return void 0;
|
|
7917
|
+
if (elapsedHours < 1) return "<1h";
|
|
7918
|
+
if (elapsedHours < 24) return `${elapsedHours}h`;
|
|
7919
|
+
const days = Math.floor(elapsedHours / 24);
|
|
7920
|
+
return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
|
|
6904
7921
|
}
|
|
6905
7922
|
//#endregion
|
|
6906
7923
|
//#region src/client/boot-check.ts
|
|
@@ -7094,7 +8111,7 @@ window.__ModuleLoader__.load({
|
|
|
7094
8111
|
]
|
|
7095
8112
|
});
|
|
7096
8113
|
}
|
|
7097
|
-
function repositoryName
|
|
8114
|
+
function repositoryName(remote) {
|
|
7098
8115
|
return remote?.split("/").at(-1);
|
|
7099
8116
|
}
|
|
7100
8117
|
function PullRequestHoverCard({ repository, t }) {
|
|
@@ -7122,7 +8139,7 @@ window.__ModuleLoader__.load({
|
|
|
7122
8139
|
/* @__PURE__ */ jsxs("span", {
|
|
7123
8140
|
style: repositoryPrHoverRepo,
|
|
7124
8141
|
children: [
|
|
7125
|
-
repositoryName
|
|
8142
|
+
repositoryName(repository.remote),
|
|
7126
8143
|
" #",
|
|
7127
8144
|
pullRequest.number,
|
|
7128
8145
|
pullRequest.baseBranch === void 0 ? "" : ` → ${pullRequest.baseBranch}`
|
|
@@ -7900,7 +8917,7 @@ window.__ModuleLoader__.load({
|
|
|
7900
8917
|
}),
|
|
7901
8918
|
repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
7902
8919
|
style: repositoryRemote,
|
|
7903
|
-
children: repositoryName
|
|
8920
|
+
children: repositoryName(repository.remote)
|
|
7904
8921
|
}),
|
|
7905
8922
|
/* @__PURE__ */ jsx(Tooltip, {
|
|
7906
8923
|
label: branch,
|
|
@@ -12273,7 +13290,7 @@ window.__ModuleLoader__.load({
|
|
|
12273
13290
|
];
|
|
12274
13291
|
const marked = new Z({
|
|
12275
13292
|
gfm: true,
|
|
12276
|
-
breaks:
|
|
13293
|
+
breaks: true,
|
|
12277
13294
|
async: false
|
|
12278
13295
|
});
|
|
12279
13296
|
/** Parse one comment body into HTML. Bot machinery written as HTML comments
|
|
@@ -12568,7 +13585,7 @@ window.__ModuleLoader__.load({
|
|
|
12568
13585
|
* the thread, plus the two things a reviewer expects to do with it — answer it
|
|
12569
13586
|
* and close it. A resolved thread collapses, because the diff is about what
|
|
12570
13587
|
* still needs attention. */
|
|
12571
|
-
function ReviewThreadCard({ thread, t, now, anchorKey, active = false, suggest, onReply, onResolvedChange }) {
|
|
13588
|
+
function ReviewThreadCard({ thread, t, now, anchorKey, active = false, suggest, onReply, onResolvedChange, onSendToAi }) {
|
|
12572
13589
|
const [expanded, setExpanded] = useState(false);
|
|
12573
13590
|
const [replying, setReplying] = useState(false);
|
|
12574
13591
|
const [draft, setDraft] = useState("");
|
|
@@ -12700,21 +13717,31 @@ window.__ModuleLoader__.load({
|
|
|
12700
13717
|
]
|
|
12701
13718
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
12702
13719
|
style: diffCommentActions,
|
|
12703
|
-
children: [
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
13720
|
+
children: [
|
|
13721
|
+
onSendToAi === void 0 || thread.resolved ? null : /* @__PURE__ */ jsx("button", {
|
|
13722
|
+
type: "button",
|
|
13723
|
+
style: diffCommentActionButton,
|
|
13724
|
+
disabled: busy,
|
|
13725
|
+
onClick: onSendToAi,
|
|
13726
|
+
children: t("reviewThreadSendToAi")
|
|
13727
|
+
}),
|
|
13728
|
+
/* @__PURE__ */ jsx("button", {
|
|
13729
|
+
type: "button",
|
|
13730
|
+
style: diffCommentActionButton,
|
|
13731
|
+
disabled: busy,
|
|
13732
|
+
onClick: () => {
|
|
13733
|
+
setReplying(true);
|
|
13734
|
+
},
|
|
13735
|
+
children: t("reviewThreadReply")
|
|
13736
|
+
}),
|
|
13737
|
+
/* @__PURE__ */ jsx("button", {
|
|
13738
|
+
type: "button",
|
|
13739
|
+
style: diffCommentActionButton,
|
|
13740
|
+
disabled: busy,
|
|
13741
|
+
onClick: toggleResolved,
|
|
13742
|
+
children: thread.resolved ? t("reviewThreadUnresolve") : t("reviewThreadResolve")
|
|
13743
|
+
})
|
|
13744
|
+
]
|
|
12718
13745
|
})
|
|
12719
13746
|
]
|
|
12720
13747
|
});
|
|
@@ -13019,7 +14046,7 @@ window.__ModuleLoader__.load({
|
|
|
13019
14046
|
children: /* @__PURE__ */ jsx("path", { d: "M14 9.5a2 2 0 0 1-2 2H6l-3.5 2.5V4a2 2 0 0 1 2-2h7.5a2 2 0 0 1 2 2z" })
|
|
13020
14047
|
});
|
|
13021
14048
|
}
|
|
13022
|
-
function DiffFileSection({ file, root, open, t, comments, ghThreads, editorAnchor, editorNode, now, activeTargetKey, suggestMention, onOpenEditor, onOpenChange, onRemoveComment, onReplyToThread, onThreadResolvedChange }) {
|
|
14049
|
+
function DiffFileSection({ file, root, open, t, comments, ghThreads, editorAnchor, editorNode, now, activeTargetKey, suggestMention, onOpenEditor, onOpenChange, onRemoveComment, onReplyToThread, onThreadResolvedChange, onSendThreadToAi }) {
|
|
13023
14050
|
const [revealed, setRevealed] = useState(() => /* @__PURE__ */ new Map());
|
|
13024
14051
|
const [total, setTotal] = useState();
|
|
13025
14052
|
const [expanding, setExpanding] = useState(false);
|
|
@@ -13174,7 +14201,10 @@ window.__ModuleLoader__.load({
|
|
|
13174
14201
|
suggest: suggestMention,
|
|
13175
14202
|
now,
|
|
13176
14203
|
onReply: (body) => onReplyToThread(thread, body),
|
|
13177
|
-
onResolvedChange: (resolved) => onThreadResolvedChange(thread, resolved)
|
|
14204
|
+
onResolvedChange: (resolved) => onThreadResolvedChange(thread, resolved),
|
|
14205
|
+
onSendToAi: onSendThreadToAi === void 0 ? void 0 : () => {
|
|
14206
|
+
onSendThreadToAi(thread);
|
|
14207
|
+
}
|
|
13178
14208
|
}, thread.id)),
|
|
13179
14209
|
editorOpen ? editorNode : null
|
|
13180
14210
|
] }, `${index}:${entry.line}`);
|
|
@@ -13728,14 +14758,20 @@ window.__ModuleLoader__.load({
|
|
|
13728
14758
|
style: diffDelete,
|
|
13729
14759
|
children: ["−", diff.deletions]
|
|
13730
14760
|
}),
|
|
13731
|
-
files.length === 0 ? null : /* @__PURE__ */ jsx(
|
|
13732
|
-
|
|
13733
|
-
|
|
13734
|
-
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
13738
|
-
|
|
14761
|
+
files.length === 0 ? null : /* @__PURE__ */ jsx(Tooltip, {
|
|
14762
|
+
label: allFilesOpen ? t("diffCollapseAll") : t("diffExpandAll"),
|
|
14763
|
+
side: "bottom",
|
|
14764
|
+
delayMs: 250,
|
|
14765
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
14766
|
+
type: "button",
|
|
14767
|
+
className: panelIconButtonClass,
|
|
14768
|
+
style: diffSummaryAction,
|
|
14769
|
+
"aria-label": allFilesOpen ? t("diffCollapseAll") : t("diffExpandAll"),
|
|
14770
|
+
onClick: () => {
|
|
14771
|
+
setOpenFiles(new Map(files.map((file) => [file.path, !allFilesOpen])));
|
|
14772
|
+
},
|
|
14773
|
+
children: allFilesOpen ? /* @__PURE__ */ jsx(IconChevronDownOutline14, {}) : /* @__PURE__ */ jsx(IconChevronRightOutline14, {})
|
|
14774
|
+
})
|
|
13739
14775
|
})
|
|
13740
14776
|
]
|
|
13741
14777
|
}),
|
|
@@ -13763,6 +14799,9 @@ window.__ModuleLoader__.load({
|
|
|
13763
14799
|
activeTargetKey,
|
|
13764
14800
|
onReplyToThread: replyToThread,
|
|
13765
14801
|
onThreadResolvedChange: changeThreadResolved,
|
|
14802
|
+
onSendThreadToAi: submitPrompt === void 0 ? void 0 : (thread) => {
|
|
14803
|
+
submitPrompt(composeCommentsPrompt([thread]));
|
|
14804
|
+
},
|
|
13766
14805
|
editorAnchor: commentEditor !== void 0 && commentEditor.path === file.path ? commentEditor : void 0,
|
|
13767
14806
|
editorNode: commentEditorNode,
|
|
13768
14807
|
onOpenEditor: (anchor) => openCommentEditor(file.path, anchor),
|
|
@@ -13923,7 +14962,7 @@ window.__ModuleLoader__.load({
|
|
|
13923
14962
|
] });
|
|
13924
14963
|
}
|
|
13925
14964
|
//#endregion
|
|
13926
|
-
//#region src/client/
|
|
14965
|
+
//#region src/client/ClaudePanelOverlay.tsx
|
|
13927
14966
|
const useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
13928
14967
|
function shouldRestoreFromEscape(event, root = document) {
|
|
13929
14968
|
return event.key === "Escape" && root.querySelector("[role=\"dialog\"][aria-modal=\"true\"]") === null;
|
|
@@ -13971,7 +15010,7 @@ window.__ModuleLoader__.load({
|
|
|
13971
15010
|
window.removeEventListener("resize", update);
|
|
13972
15011
|
};
|
|
13973
15012
|
}
|
|
13974
|
-
function
|
|
15013
|
+
function ClaudePanelOverlay({ children, onRestore }) {
|
|
13975
15014
|
const ref = useRef(null);
|
|
13976
15015
|
const [bounds, setBounds] = useState();
|
|
13977
15016
|
useClientLayoutEffect(() => {
|
|
@@ -13990,7 +15029,7 @@ window.__ModuleLoader__.load({
|
|
|
13990
15029
|
}, [onRestore]);
|
|
13991
15030
|
return /* @__PURE__ */ jsx("div", {
|
|
13992
15031
|
ref,
|
|
13993
|
-
"data-dsh-claude-
|
|
15032
|
+
"data-dsh-claude-panel-overlay": true,
|
|
13994
15033
|
style: {
|
|
13995
15034
|
position: "absolute",
|
|
13996
15035
|
left: bounds?.left ?? 0,
|
|
@@ -14059,304 +15098,110 @@ window.__ModuleLoader__.load({
|
|
|
14059
15098
|
label: hint ?? label,
|
|
14060
15099
|
side: "bottom",
|
|
14061
15100
|
delayMs: 400,
|
|
14062
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
14063
|
-
type: "button",
|
|
14064
|
-
className: panelIconButtonClass,
|
|
14065
|
-
"aria-label": label,
|
|
14066
|
-
disabled,
|
|
14067
|
-
onClick,
|
|
14068
|
-
children: icon
|
|
14069
|
-
})
|
|
14070
|
-
});
|
|
14071
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
14072
|
-
style: repositoryBarFrame,
|
|
14073
|
-
"data-claude-queue-dock": "",
|
|
14074
|
-
children: [/* @__PURE__ */ jsx("style", {
|
|
14075
|
-
"data-dsh-claude-queue-styles": true,
|
|
14076
|
-
children: panelIconButtonCss
|
|
14077
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
14078
|
-
style: queueBar,
|
|
14079
|
-
children: [queue.length > 1 ? /* @__PURE__ */ jsxs("button", {
|
|
14080
|
-
type: "button",
|
|
14081
|
-
style: queueHeader,
|
|
14082
|
-
"aria-controls": listId,
|
|
14083
|
-
"aria-expanded": expanded,
|
|
14084
|
-
disabled: interacting,
|
|
14085
|
-
onClick: () => {
|
|
14086
|
-
setCollapsed((value) => !value);
|
|
14087
|
-
},
|
|
14088
|
-
children: [
|
|
14089
|
-
/* @__PURE__ */ jsx("span", {
|
|
14090
|
-
style: queueLead,
|
|
14091
|
-
"aria-hidden": "true",
|
|
14092
|
-
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
14093
|
-
}),
|
|
14094
|
-
/* @__PURE__ */ jsx("span", {
|
|
14095
|
-
style: queueCount,
|
|
14096
|
-
children: t("queueCount", { n: queue.length })
|
|
14097
|
-
}),
|
|
14098
|
-
/* @__PURE__ */ jsx("span", {
|
|
14099
|
-
style: queueLead,
|
|
14100
|
-
"aria-hidden": "true",
|
|
14101
|
-
children: expanded ? /* @__PURE__ */ jsx(IconChevronDownOutline14, {}) : /* @__PURE__ */ jsx(IconChevronUpOutline14, {})
|
|
14102
|
-
})
|
|
14103
|
-
]
|
|
14104
|
-
}) : null, /* @__PURE__ */ jsx("ul", {
|
|
14105
|
-
id: listId,
|
|
14106
|
-
style: queueList,
|
|
14107
|
-
hidden: !listVisible,
|
|
14108
|
-
children: listVisible ? queue.map((row, index) => /* @__PURE__ */ jsxs("li", {
|
|
14109
|
-
style: {
|
|
14110
|
-
...queueRow,
|
|
14111
|
-
...index > 0 ? queueRowDivider : {}
|
|
14112
|
-
},
|
|
14113
|
-
children: [
|
|
14114
|
-
queue.length === 1 ? /* @__PURE__ */ jsx("span", {
|
|
14115
|
-
style: queueLead,
|
|
14116
|
-
"aria-hidden": "true",
|
|
14117
|
-
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
14118
|
-
}) : null,
|
|
14119
|
-
editing?.id === row.id ? /* @__PURE__ */ jsx("input", {
|
|
14120
|
-
autoFocus: true,
|
|
14121
|
-
style: queueEditor,
|
|
14122
|
-
"aria-label": t("queueEdit"),
|
|
14123
|
-
value: editing.text,
|
|
14124
|
-
onChange: (event) => {
|
|
14125
|
-
setEditing({
|
|
14126
|
-
id: row.id,
|
|
14127
|
-
text: event.currentTarget.value
|
|
14128
|
-
});
|
|
14129
|
-
},
|
|
14130
|
-
onKeyDown: (event) => {
|
|
14131
|
-
if (event.key === "Escape") setEditing(void 0);
|
|
14132
|
-
else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
14133
|
-
event.preventDefault();
|
|
14134
|
-
saveEdit();
|
|
14135
|
-
}
|
|
14136
|
-
}
|
|
14137
|
-
}) : /* @__PURE__ */ jsx("span", {
|
|
14138
|
-
style: queuePreview,
|
|
14139
|
-
children: row.preview
|
|
14140
|
-
}),
|
|
14141
|
-
mutable ? /* @__PURE__ */ jsx("span", {
|
|
14142
|
-
style: queueActions,
|
|
14143
|
-
children: editing?.id === row.id ? /* @__PURE__ */ jsxs(Fragment$1, { children: [action(t("queueSave"), /* @__PURE__ */ jsx(IconCheckOutline16, { size: 14 }), () => {
|
|
14144
|
-
saveEdit();
|
|
14145
|
-
}, busy !== void 0 || editing.text.trim() === ""), action(t("queueCancelEdit"), /* @__PURE__ */ jsx(IconCloseOutline16, { size: 14 }), () => {
|
|
14146
|
-
setEditing(void 0);
|
|
14147
|
-
}, busy !== void 0)] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
14148
|
-
action(t("queueEdit"), /* @__PURE__ */ jsx(IconEditOutline16, { size: 14 }), () => {
|
|
14149
|
-
if (row.text !== null) setEditing({
|
|
14150
|
-
id: row.id,
|
|
14151
|
-
text: row.text
|
|
14152
|
-
});
|
|
14153
|
-
}, busy !== void 0 || row.text === null, row.text === null ? t("queueEditUnsupported") : void 0),
|
|
14154
|
-
action(t("queueRemove"), /* @__PURE__ */ jsx(IconTrashOutline16, { size: 14 }), () => {
|
|
14155
|
-
apply(row.id, { kind: "remove" }, t("queueRemoveFailed"));
|
|
14156
|
-
}, busy !== void 0),
|
|
14157
|
-
action(t("queueSteer"), /* @__PURE__ */ jsx(IconSendOutline14, {}), () => {
|
|
14158
|
-
apply(row.id, { kind: "steer" }, t("queueSteerFailed"));
|
|
14159
|
-
}, busy !== void 0 || !running, running ? void 0 : t("queueSteerUnavailable"))
|
|
14160
|
-
] })
|
|
14161
|
-
}) : null
|
|
14162
|
-
]
|
|
14163
|
-
}, row.id)) : null
|
|
14164
|
-
})]
|
|
14165
|
-
})]
|
|
14166
|
-
});
|
|
14167
|
-
}
|
|
14168
|
-
//#endregion
|
|
14169
|
-
//#region src/client/session-preset.ts
|
|
14170
|
-
/**
|
|
14171
|
-
* Resolve one row's preset id, newest seat first.
|
|
14172
|
-
* @param row - a session-list row, or undefined when the id is not listed.
|
|
14173
|
-
* @returns the preset id, or undefined when neither source carries one.
|
|
14174
|
-
*/
|
|
14175
|
-
function sessionRowPreset(row) {
|
|
14176
|
-
return row?.agentPreset ?? row?.projectionValues?.agentPreset ?? void 0;
|
|
14177
|
-
}
|
|
14178
|
-
//#endregion
|
|
14179
|
-
//#region src/client/ClaudePullRequestsPanel.tsx
|
|
14180
|
-
const NO_WORKSPACE_STATE = {};
|
|
14181
|
-
const NO_WORKSPACES = {
|
|
14182
|
-
subscribe: () => () => {},
|
|
14183
|
-
getSnapshot: () => NO_WORKSPACE_STATE
|
|
14184
|
-
};
|
|
14185
|
-
const OVERVIEW_REFRESH_MS = 3e4;
|
|
14186
|
-
/** What a running session is blocked on: the latest permission or question
|
|
14187
|
-
* activity that is still in its started phase. */
|
|
14188
|
-
function overviewAttention(activities) {
|
|
14189
|
-
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
14190
|
-
const activity = activities[index];
|
|
14191
|
-
if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
|
|
14192
|
-
return activity.phase === "started" ? activity.kind : void 0;
|
|
14193
|
-
}
|
|
14194
|
-
}
|
|
14195
|
-
/** Claude sessions worth listing: rows still in the host list (byId keeps
|
|
14196
|
-
* deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
|
|
14197
|
-
* running first. */
|
|
14198
|
-
function claudeSessionRows(state, archivedSessionIds = []) {
|
|
14199
|
-
const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
|
|
14200
|
-
const archived = new Set(archivedSessionIds);
|
|
14201
|
-
return rows.filter((row) => row !== void 0 && sessionRowPreset(row) === "claude" && row.blank !== true && row.origin !== "subagent" && !archived.has(row.id) && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
|
|
14202
|
-
}
|
|
14203
|
-
function Badge({ label, tone = "neutral" }) {
|
|
14204
|
-
const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
|
|
14205
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
14206
|
-
style: {
|
|
14207
|
-
...repositoryItem,
|
|
14208
|
-
...toneStyle
|
|
14209
|
-
},
|
|
14210
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
14211
|
-
style: repositoryItemDot,
|
|
14212
|
-
"aria-hidden": "true"
|
|
14213
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
14214
|
-
style: repositoryItemLabel,
|
|
14215
|
-
children: label
|
|
14216
|
-
})]
|
|
14217
|
-
});
|
|
14218
|
-
}
|
|
14219
|
-
function repositoryName(remote) {
|
|
14220
|
-
return remote?.split("/").at(-1);
|
|
14221
|
-
}
|
|
14222
|
-
function OverviewAttention({ source, running, t }) {
|
|
14223
|
-
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
14224
|
-
const attention = running ? overviewAttention(snapshot.activities) : void 0;
|
|
14225
|
-
const usage = snapshot.contextUsage;
|
|
14226
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
14227
|
-
attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
|
|
14228
|
-
label: t("overviewNeedsPermission"),
|
|
14229
|
-
tone: "warning"
|
|
14230
|
-
}) : null,
|
|
14231
|
-
attention === "question" ? /* @__PURE__ */ jsx(Badge, {
|
|
14232
|
-
label: t("overviewNeedsAnswer"),
|
|
14233
|
-
tone: "warning"
|
|
14234
|
-
}) : null,
|
|
14235
|
-
usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
|
|
14236
|
-
] });
|
|
14237
|
-
}
|
|
14238
|
-
function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
|
|
14239
|
-
const sessionStore = useMemo(() => ({
|
|
14240
|
-
subscribe: (listener) => sessions.subscribe(listener),
|
|
14241
|
-
getSnapshot: () => sessions.getSnapshot()
|
|
14242
|
-
}), [sessions]);
|
|
14243
|
-
const snapshot = useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
|
|
14244
|
-
const workspaceStore = useMemo(() => {
|
|
14245
|
-
const source = workspaces ?? NO_WORKSPACES;
|
|
14246
|
-
return {
|
|
14247
|
-
subscribe: (listener) => source.subscribe(listener),
|
|
14248
|
-
getSnapshot: () => source.getSnapshot()
|
|
14249
|
-
};
|
|
14250
|
-
}, [workspaces]);
|
|
14251
|
-
const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
|
|
14252
|
-
const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
|
|
14253
|
-
const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
|
|
14254
|
-
const [statuses, setStatuses] = useState({});
|
|
14255
|
-
useEffect(() => {
|
|
14256
|
-
const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
|
|
14257
|
-
if (cwds.length === 0) return;
|
|
14258
|
-
const controller = new AbortController();
|
|
14259
|
-
const refresh = () => {
|
|
14260
|
-
for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
|
|
14261
|
-
if (!controller.signal.aborted) setStatuses((previous) => ({
|
|
14262
|
-
...previous,
|
|
14263
|
-
[cwd]: status
|
|
14264
|
-
}));
|
|
14265
|
-
}, () => void 0);
|
|
14266
|
-
};
|
|
14267
|
-
refresh();
|
|
14268
|
-
const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
|
|
14269
|
-
return () => {
|
|
14270
|
-
controller.abort();
|
|
14271
|
-
clearInterval(timer);
|
|
14272
|
-
};
|
|
14273
|
-
}, [cwdKey, loadStatus]);
|
|
15101
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
15102
|
+
type: "button",
|
|
15103
|
+
className: panelIconButtonClass,
|
|
15104
|
+
"aria-label": label,
|
|
15105
|
+
disabled,
|
|
15106
|
+
onClick,
|
|
15107
|
+
children: icon
|
|
15108
|
+
})
|
|
15109
|
+
});
|
|
14274
15110
|
return /* @__PURE__ */ jsxs("div", {
|
|
14275
|
-
|
|
14276
|
-
|
|
14277
|
-
children: [
|
|
14278
|
-
|
|
14279
|
-
|
|
14280
|
-
|
|
14281
|
-
|
|
14282
|
-
/* @__PURE__ */ jsxs("
|
|
14283
|
-
|
|
14284
|
-
|
|
14285
|
-
|
|
14286
|
-
|
|
14287
|
-
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
}
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14302
|
-
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14321
|
-
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
|
|
14325
|
-
|
|
14326
|
-
|
|
14327
|
-
|
|
14328
|
-
|
|
14329
|
-
|
|
14330
|
-
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
|
|
14341
|
-
|
|
14342
|
-
|
|
14343
|
-
|
|
14344
|
-
|
|
14345
|
-
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14351
|
-
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
|
|
14357
|
-
|
|
14358
|
-
|
|
14359
|
-
|
|
15111
|
+
style: repositoryBarFrame,
|
|
15112
|
+
"data-claude-queue-dock": "",
|
|
15113
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
15114
|
+
"data-dsh-claude-queue-styles": true,
|
|
15115
|
+
children: panelIconButtonCss
|
|
15116
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
15117
|
+
style: queueBar,
|
|
15118
|
+
children: [queue.length > 1 ? /* @__PURE__ */ jsxs("button", {
|
|
15119
|
+
type: "button",
|
|
15120
|
+
style: queueHeader,
|
|
15121
|
+
"aria-controls": listId,
|
|
15122
|
+
"aria-expanded": expanded,
|
|
15123
|
+
disabled: interacting,
|
|
15124
|
+
onClick: () => {
|
|
15125
|
+
setCollapsed((value) => !value);
|
|
15126
|
+
},
|
|
15127
|
+
children: [
|
|
15128
|
+
/* @__PURE__ */ jsx("span", {
|
|
15129
|
+
style: queueLead,
|
|
15130
|
+
"aria-hidden": "true",
|
|
15131
|
+
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
15132
|
+
}),
|
|
15133
|
+
/* @__PURE__ */ jsx("span", {
|
|
15134
|
+
style: queueCount,
|
|
15135
|
+
children: t("queueCount", { n: queue.length })
|
|
15136
|
+
}),
|
|
15137
|
+
/* @__PURE__ */ jsx("span", {
|
|
15138
|
+
style: queueLead,
|
|
15139
|
+
"aria-hidden": "true",
|
|
15140
|
+
children: expanded ? /* @__PURE__ */ jsx(IconChevronDownOutline14, {}) : /* @__PURE__ */ jsx(IconChevronUpOutline14, {})
|
|
15141
|
+
})
|
|
15142
|
+
]
|
|
15143
|
+
}) : null, /* @__PURE__ */ jsx("ul", {
|
|
15144
|
+
id: listId,
|
|
15145
|
+
style: queueList,
|
|
15146
|
+
hidden: !listVisible,
|
|
15147
|
+
children: listVisible ? queue.map((row, index) => /* @__PURE__ */ jsxs("li", {
|
|
15148
|
+
style: {
|
|
15149
|
+
...queueRow,
|
|
15150
|
+
...index > 0 ? queueRowDivider : {}
|
|
15151
|
+
},
|
|
15152
|
+
children: [
|
|
15153
|
+
queue.length === 1 ? /* @__PURE__ */ jsx("span", {
|
|
15154
|
+
style: queueLead,
|
|
15155
|
+
"aria-hidden": "true",
|
|
15156
|
+
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
15157
|
+
}) : null,
|
|
15158
|
+
editing?.id === row.id ? /* @__PURE__ */ jsx("input", {
|
|
15159
|
+
autoFocus: true,
|
|
15160
|
+
style: queueEditor,
|
|
15161
|
+
"aria-label": t("queueEdit"),
|
|
15162
|
+
value: editing.text,
|
|
15163
|
+
onChange: (event) => {
|
|
15164
|
+
setEditing({
|
|
15165
|
+
id: row.id,
|
|
15166
|
+
text: event.currentTarget.value
|
|
15167
|
+
});
|
|
15168
|
+
},
|
|
15169
|
+
onKeyDown: (event) => {
|
|
15170
|
+
if (event.key === "Escape") setEditing(void 0);
|
|
15171
|
+
else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
15172
|
+
event.preventDefault();
|
|
15173
|
+
saveEdit();
|
|
15174
|
+
}
|
|
15175
|
+
}
|
|
15176
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
15177
|
+
style: queuePreview,
|
|
15178
|
+
children: row.preview
|
|
15179
|
+
}),
|
|
15180
|
+
mutable ? /* @__PURE__ */ jsx("span", {
|
|
15181
|
+
style: queueActions,
|
|
15182
|
+
children: editing?.id === row.id ? /* @__PURE__ */ jsxs(Fragment$1, { children: [action(t("queueSave"), /* @__PURE__ */ jsx(IconCheckOutline16, { size: 14 }), () => {
|
|
15183
|
+
saveEdit();
|
|
15184
|
+
}, busy !== void 0 || editing.text.trim() === ""), action(t("queueCancelEdit"), /* @__PURE__ */ jsx(IconCloseOutline16, { size: 14 }), () => {
|
|
15185
|
+
setEditing(void 0);
|
|
15186
|
+
}, busy !== void 0)] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
15187
|
+
action(t("queueEdit"), /* @__PURE__ */ jsx(IconEditOutline16, { size: 14 }), () => {
|
|
15188
|
+
if (row.text !== null) setEditing({
|
|
15189
|
+
id: row.id,
|
|
15190
|
+
text: row.text
|
|
15191
|
+
});
|
|
15192
|
+
}, busy !== void 0 || row.text === null, row.text === null ? t("queueEditUnsupported") : void 0),
|
|
15193
|
+
action(t("queueRemove"), /* @__PURE__ */ jsx(IconTrashOutline16, { size: 14 }), () => {
|
|
15194
|
+
apply(row.id, { kind: "remove" }, t("queueRemoveFailed"));
|
|
15195
|
+
}, busy !== void 0),
|
|
15196
|
+
action(t("queueSteer"), /* @__PURE__ */ jsx(IconSendOutline14, {}), () => {
|
|
15197
|
+
apply(row.id, { kind: "steer" }, t("queueSteerFailed"));
|
|
15198
|
+
}, busy !== void 0 || !running, running ? void 0 : t("queueSteerUnavailable"))
|
|
15199
|
+
] })
|
|
15200
|
+
}) : null
|
|
15201
|
+
]
|
|
15202
|
+
}, row.id)) : null
|
|
15203
|
+
})]
|
|
15204
|
+
})]
|
|
14360
15205
|
});
|
|
14361
15206
|
}
|
|
14362
15207
|
//#endregion
|
|
@@ -15096,13 +15941,19 @@ window.__ModuleLoader__.load({
|
|
|
15096
15941
|
//#endregion
|
|
15097
15942
|
//#region src/client/rewind-api.ts
|
|
15098
15943
|
/** Drop one user message and everything after it: the rows are hidden from
|
|
15099
|
-
* this session's transcript and Claude resumes before that turn.
|
|
15100
|
-
|
|
15944
|
+
* this session's transcript and Claude resumes before that turn.
|
|
15945
|
+
*
|
|
15946
|
+
* With `restoreFiles`, the checkout is put back to the tree that turn was
|
|
15947
|
+
* admitted against. The answer reports whether that half actually landed:
|
|
15948
|
+
* a turn from before this session captured trees, or a tree git has since
|
|
15949
|
+
* collected, still rewinds the conversation and leaves the files alone. */
|
|
15950
|
+
async function rewindSession(sessionId, seq, restoreFiles = false) {
|
|
15101
15951
|
try {
|
|
15102
|
-
await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
15952
|
+
return { filesRestored: (await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
15103
15953
|
sessionId,
|
|
15104
|
-
seq
|
|
15105
|
-
|
|
15954
|
+
seq,
|
|
15955
|
+
restoreFiles
|
|
15956
|
+
} }))?.filesRestored === true };
|
|
15106
15957
|
} catch (error) {
|
|
15107
15958
|
if (!(error instanceof PluginRequestError)) throw error;
|
|
15108
15959
|
throw new Error(error.code ?? error.reason);
|
|
@@ -15171,6 +16022,8 @@ window.__ModuleLoader__.load({
|
|
|
15171
16022
|
const [target, setTarget] = useState();
|
|
15172
16023
|
const [submitting, setSubmitting] = useState(false);
|
|
15173
16024
|
const [error, setError] = useState();
|
|
16025
|
+
const [restoreFiles, setRestoreFiles] = useState(true);
|
|
16026
|
+
const { toast, report } = useActionToast();
|
|
15174
16027
|
const ranges = projection.rewind?.ranges ?? EMPTY_RANGES;
|
|
15175
16028
|
const owned = projection.owned;
|
|
15176
16029
|
const unavailable = snapshot.running;
|
|
@@ -15238,6 +16091,7 @@ window.__ModuleLoader__.load({
|
|
|
15238
16091
|
setTarget(void 0);
|
|
15239
16092
|
setSubmitting(false);
|
|
15240
16093
|
setError(void 0);
|
|
16094
|
+
setRestoreFiles(true);
|
|
15241
16095
|
}, [sessionId]);
|
|
15242
16096
|
if (sessionId === void 0 || !owned) return /* @__PURE__ */ jsx("span", {
|
|
15243
16097
|
"data-dsh-claude-rewind-armed": "armed",
|
|
@@ -15252,10 +16106,11 @@ window.__ModuleLoader__.load({
|
|
|
15252
16106
|
if (target === void 0 || submitting) return;
|
|
15253
16107
|
setSubmitting(true);
|
|
15254
16108
|
setError(void 0);
|
|
15255
|
-
rewindSession(sessionId, target.seq).then(() => {
|
|
16109
|
+
rewindSession(sessionId, target.seq, restoreFiles).then(({ filesRestored }) => {
|
|
15256
16110
|
setSubmitting(false);
|
|
15257
16111
|
setTarget(void 0);
|
|
15258
16112
|
if (target.text !== "") setDraft?.(sessionId, target.text);
|
|
16113
|
+
if (restoreFiles && !filesRestored) report(t("rewindFilesUnavailable"));
|
|
15259
16114
|
}, (reason) => {
|
|
15260
16115
|
setSubmitting(false);
|
|
15261
16116
|
const code = reason instanceof Error && reason.message !== "" ? reason.message : "unknown";
|
|
@@ -15263,6 +16118,7 @@ window.__ModuleLoader__.load({
|
|
|
15263
16118
|
});
|
|
15264
16119
|
};
|
|
15265
16120
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
16121
|
+
toast,
|
|
15266
16122
|
/* @__PURE__ */ jsx("style", {
|
|
15267
16123
|
"data-dsh-claude-rewind-styles": true,
|
|
15268
16124
|
children: `${rewindActionCss}${rewindHiddenCss(hiddenKeys)}`
|
|
@@ -15327,9 +16183,20 @@ window.__ModuleLoader__.load({
|
|
|
15327
16183
|
style: rewindModalMessage,
|
|
15328
16184
|
children: target.text.slice(0, 2e3)
|
|
15329
16185
|
}),
|
|
16186
|
+
/* @__PURE__ */ jsxs("label", {
|
|
16187
|
+
style: diffModalCheckbox,
|
|
16188
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
16189
|
+
type: "checkbox",
|
|
16190
|
+
checked: restoreFiles,
|
|
16191
|
+
disabled: submitting,
|
|
16192
|
+
onChange: (event) => {
|
|
16193
|
+
setRestoreFiles(event.currentTarget.checked);
|
|
16194
|
+
}
|
|
16195
|
+
}), t("rewindRestoreFiles")]
|
|
16196
|
+
}),
|
|
15330
16197
|
/* @__PURE__ */ jsx("p", {
|
|
15331
16198
|
style: diffModalStatus,
|
|
15332
|
-
children: t("rewindHint")
|
|
16199
|
+
children: restoreFiles ? t("rewindRestoreFilesHint") : t("rewindHint")
|
|
15333
16200
|
}),
|
|
15334
16201
|
error === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
15335
16202
|
style: diffModalError,
|
|
@@ -16658,19 +17525,19 @@ window.__ModuleLoader__.load({
|
|
|
16658
17525
|
});
|
|
16659
17526
|
}
|
|
16660
17527
|
//#endregion
|
|
16661
|
-
//#region src/client/
|
|
16662
|
-
var
|
|
17528
|
+
//#region src/client/panel-open-store.ts
|
|
17529
|
+
var PanelOpenStore = class {
|
|
16663
17530
|
#sessionId;
|
|
16664
17531
|
#listeners = /* @__PURE__ */ new Set();
|
|
16665
|
-
/** Mark
|
|
17532
|
+
/** Mark this panel open for one session, replacing any previous holder. */
|
|
16666
17533
|
open(sessionId) {
|
|
16667
17534
|
this.#set(sessionId);
|
|
16668
17535
|
}
|
|
16669
|
-
/** Mark
|
|
17536
|
+
/** Mark this panel closed. */
|
|
16670
17537
|
close() {
|
|
16671
17538
|
this.#set(void 0);
|
|
16672
17539
|
}
|
|
16673
|
-
/** Whether
|
|
17540
|
+
/** Whether this panel is currently open for this session. */
|
|
16674
17541
|
isOpen(sessionId) {
|
|
16675
17542
|
return this.#sessionId === sessionId;
|
|
16676
17543
|
}
|
|
@@ -17206,6 +18073,27 @@ window.__ModuleLoader__.load({
|
|
|
17206
18073
|
yes: "是",
|
|
17207
18074
|
no: "否",
|
|
17208
18075
|
diffOpen: "查看分支改动",
|
|
18076
|
+
planPanelTitle: "Plan",
|
|
18077
|
+
planClose: "关闭方案面板",
|
|
18078
|
+
planOpen: "查看方案",
|
|
18079
|
+
planMaximize: "最大化方案面板",
|
|
18080
|
+
planRestore: "还原方案面板",
|
|
18081
|
+
planNth: "{index} / {total}",
|
|
18082
|
+
planHistory: "本会话的方案",
|
|
18083
|
+
planNotePlaceholder: "写下要改的地方,Enter 记一条,⌘/Ctrl+Enter 发送",
|
|
18084
|
+
planNoteQuotedPlaceholder: "针对选中的这段,要改成什么?",
|
|
18085
|
+
planNoteHint: "在正文里选中一段可以引用它",
|
|
18086
|
+
planNoteRemove: "删掉这条意见",
|
|
18087
|
+
planQuoteClear: "取消引用",
|
|
18088
|
+
planSendForChanges: "发送并让 AI 修改",
|
|
18089
|
+
planSending: "发送中…",
|
|
18090
|
+
planSettled: "这个方案已经在审批弹框里被决定了,意见没能发出。",
|
|
18091
|
+
planFeedbackFailed: "意见发送失败,请重试。",
|
|
18092
|
+
planPending: "待审批",
|
|
18093
|
+
planApproved: "已批准",
|
|
18094
|
+
planRejected: "已拒绝",
|
|
18095
|
+
planEmpty: "本会话还没有待查看的方案。",
|
|
18096
|
+
planPendingHint: "在 DSH 的审批弹框里批准或拒绝这个方案。",
|
|
17209
18097
|
diffClose: "关闭 Diff 面板",
|
|
17210
18098
|
sessionMenu: "会话菜单",
|
|
17211
18099
|
sessionMenuOpenIn: "打开方式",
|
|
@@ -17351,6 +18239,7 @@ window.__ModuleLoader__.load({
|
|
|
17351
18239
|
reviewCommentCancel: "取消",
|
|
17352
18240
|
reviewCommentRemove: "删除评论",
|
|
17353
18241
|
reviewThreadReply: "回复",
|
|
18242
|
+
reviewThreadSendToAi: "交给 AI 修复",
|
|
17354
18243
|
reviewThreadBot: "Bot",
|
|
17355
18244
|
reviewThreadAgo: "{age}前",
|
|
17356
18245
|
reviewThreadReplyTo: "回复 @{author}",
|
|
@@ -17386,6 +18275,17 @@ window.__ModuleLoader__.load({
|
|
|
17386
18275
|
rewindFailed: "回退失败({code})。",
|
|
17387
18276
|
rewindBusy: "这个会话正在运行,请等本轮结束后再回退。",
|
|
17388
18277
|
rewindStale: "宿主进程里的插件还是旧版本(没有回退接口)。重启 DSH 后再试。",
|
|
18278
|
+
alerts: "会话提醒",
|
|
18279
|
+
alertsOff: "关闭",
|
|
18280
|
+
alertsOn: "开启",
|
|
18281
|
+
alertsEffect: "当另一个 Claude 会话需要你——等待权限确认、等待回答,或者这一轮跑完了——弹出系统通知。正在看的那个会话不会提醒。点击通知会切到对应会话。首次使用时系统会询问通知权限;拒绝后不再提醒。改动立即生效。",
|
|
18282
|
+
alertNeedsPermission: "等待你确认权限",
|
|
18283
|
+
alertNeedsAnswer: "等待你回答",
|
|
18284
|
+
alertTurnFinished: "这一轮跑完了",
|
|
18285
|
+
alertFallbackTitle: "Claude 会话",
|
|
18286
|
+
rewindRestoreFiles: "同时把文件改回这一轮开始前的样子",
|
|
18287
|
+
rewindRestoreFilesHint: "被删除的内容会从这个会话中隐藏,Claude 也会忘记它们;工作区会回到这一轮开始前的状态——这之后新建的文件会被删除,被改动的文件会被还原,被 .gitignore 忽略的文件不受影响。消息原文会放回输入框,方便修改后重新发送。",
|
|
18288
|
+
rewindFilesUnavailable: "对话已回退,但文件没有还原:这一轮没有留下工作区快照。",
|
|
17389
18289
|
turnUsage: "本回合用量",
|
|
17390
18290
|
turnUsageTokens: "{count} tok",
|
|
17391
18291
|
turnUsageCache: "缓存命中 {percent}%",
|
|
@@ -17583,6 +18483,27 @@ window.__ModuleLoader__.load({
|
|
|
17583
18483
|
yes: "Yes",
|
|
17584
18484
|
no: "No",
|
|
17585
18485
|
diffOpen: "View branch changes",
|
|
18486
|
+
planPanelTitle: "Plan",
|
|
18487
|
+
planClose: "Close plan panel",
|
|
18488
|
+
planOpen: "View plan",
|
|
18489
|
+
planMaximize: "Maximize plan panel",
|
|
18490
|
+
planRestore: "Restore plan panel",
|
|
18491
|
+
planNth: "{index} / {total}",
|
|
18492
|
+
planHistory: "Plans in this session",
|
|
18493
|
+
planNotePlaceholder: "What should change? Enter files a note, ⌘/Ctrl+Enter sends",
|
|
18494
|
+
planNoteQuotedPlaceholder: "What should change about the selected passage?",
|
|
18495
|
+
planNoteHint: "Select a passage above to quote it",
|
|
18496
|
+
planNoteRemove: "Remove this note",
|
|
18497
|
+
planQuoteClear: "Clear the quote",
|
|
18498
|
+
planSendForChanges: "Send for changes",
|
|
18499
|
+
planSending: "Sending…",
|
|
18500
|
+
planSettled: "This plan was already decided in the approval dialog; the notes were not sent.",
|
|
18501
|
+
planFeedbackFailed: "The notes could not be sent. Try again.",
|
|
18502
|
+
planPending: "Awaiting approval",
|
|
18503
|
+
planApproved: "Approved",
|
|
18504
|
+
planRejected: "Rejected",
|
|
18505
|
+
planEmpty: "This session has no plan to read yet.",
|
|
18506
|
+
planPendingHint: "Approve or reject this plan in the DSH approval dialog.",
|
|
17586
18507
|
diffClose: "Close diff panel",
|
|
17587
18508
|
sessionMenu: "Session menu",
|
|
17588
18509
|
sessionMenuOpenIn: "Open in",
|
|
@@ -17728,6 +18649,7 @@ window.__ModuleLoader__.load({
|
|
|
17728
18649
|
reviewCommentCancel: "Cancel",
|
|
17729
18650
|
reviewCommentRemove: "Remove comment",
|
|
17730
18651
|
reviewThreadReply: "Reply",
|
|
18652
|
+
reviewThreadSendToAi: "Send to AI",
|
|
17731
18653
|
reviewThreadBot: "Bot",
|
|
17732
18654
|
reviewThreadAgo: "{age} ago",
|
|
17733
18655
|
reviewThreadReplyTo: "Reply to @{author}",
|
|
@@ -17763,6 +18685,17 @@ window.__ModuleLoader__.load({
|
|
|
17763
18685
|
rewindFailed: "The rewind failed ({code}).",
|
|
17764
18686
|
rewindBusy: "This session is running; wait for the turn to finish before rewinding.",
|
|
17765
18687
|
rewindStale: "The Host process is still running the previous plugin build, which has no rewind route. Restart DSH and try again.",
|
|
18688
|
+
alerts: "Session alerts",
|
|
18689
|
+
alertsOff: "Off",
|
|
18690
|
+
alertsOn: "On",
|
|
18691
|
+
alertsEffect: "Raises a desktop notification when another Claude session needs you — waiting on an approval, waiting on an answer, or finished its turn. The session on screen never raises one. Clicking a notification brings that session up. The system asks for notification permission the first time; refusing it turns alerts off. Takes effect immediately.",
|
|
18692
|
+
alertNeedsPermission: "Waiting on your approval",
|
|
18693
|
+
alertNeedsAnswer: "Waiting on your answer",
|
|
18694
|
+
alertTurnFinished: "Finished its turn",
|
|
18695
|
+
alertFallbackTitle: "Claude session",
|
|
18696
|
+
rewindRestoreFiles: "Also put the files back to where this turn found them",
|
|
18697
|
+
rewindRestoreFilesHint: "The removed entries are hidden from this session and Claude forgets them, and the checkout returns to the state this turn started from — files created since are deleted, changed files are restored, and files ignored by .gitignore are left alone. The message text goes back to the composer so you can edit and resend it.",
|
|
18698
|
+
rewindFilesUnavailable: "The conversation was rewound, but the files were left alone: that turn recorded no working-tree snapshot.",
|
|
17766
18699
|
turnUsage: "Turn usage",
|
|
17767
18700
|
turnUsageTokens: "{count} tok",
|
|
17768
18701
|
turnUsageCache: "Cache hit {percent}%",
|
|
@@ -17774,7 +18707,7 @@ window.__ModuleLoader__.load({
|
|
|
17774
18707
|
function MaximizedDiff({ source, t, sessionId, closeDetails, restore, submitPrompt }) {
|
|
17775
18708
|
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
17776
18709
|
const useClaudeProjection = (selector) => selector(snapshot);
|
|
17777
|
-
return /* @__PURE__ */ jsx(
|
|
18710
|
+
return /* @__PURE__ */ jsx(ClaudePanelOverlay, {
|
|
17778
18711
|
onRestore: restore,
|
|
17779
18712
|
children: /* @__PURE__ */ jsx(ClaudeDiffPanel, {
|
|
17780
18713
|
useClaudeProjection,
|
|
@@ -17788,6 +18721,21 @@ window.__ModuleLoader__.load({
|
|
|
17788
18721
|
});
|
|
17789
18722
|
}
|
|
17790
18723
|
const name = "dsh-claude-client";
|
|
18724
|
+
function MaximizedPlan({ source, t, sessionId, closeDetails, restore }) {
|
|
18725
|
+
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
18726
|
+
const useClaudeProjection = (selector) => selector(snapshot);
|
|
18727
|
+
return /* @__PURE__ */ jsx(ClaudePanelOverlay, {
|
|
18728
|
+
onRestore: restore,
|
|
18729
|
+
children: /* @__PURE__ */ jsx(ClaudePlanPanel, {
|
|
18730
|
+
useClaudeProjection,
|
|
18731
|
+
t,
|
|
18732
|
+
sessionId,
|
|
18733
|
+
maximized: true,
|
|
18734
|
+
closeDetails,
|
|
18735
|
+
toggleMaximized: restore
|
|
18736
|
+
})
|
|
18737
|
+
});
|
|
18738
|
+
}
|
|
17791
18739
|
const inject = [
|
|
17792
18740
|
"slots",
|
|
17793
18741
|
"locale",
|
|
@@ -17829,7 +18777,9 @@ window.__ModuleLoader__.load({
|
|
|
17829
18777
|
const t = ctx.locale.bind(namespace);
|
|
17830
18778
|
ctx.effect(() => restyleHostChrome(), "dsh-claude: Host chrome restyling");
|
|
17831
18779
|
pluginRead(CLAUDE_GLOBAL_SETTINGS_PATH, "fast").then((payload) => {
|
|
17832
|
-
if (isGlobalSettingsView(payload))
|
|
18780
|
+
if (!isGlobalSettingsView(payload)) return;
|
|
18781
|
+
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
18782
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
17833
18783
|
}).catch(() => {});
|
|
17834
18784
|
const projections = new ClaudeProjectionStore({ report: (kind, detail) => {
|
|
17835
18785
|
diagnostics.report(kind, detail);
|
|
@@ -17874,6 +18824,17 @@ window.__ModuleLoader__.load({
|
|
|
17874
18824
|
resolve: (binding) => ({ hooks: { claudeProjection: projections.source(binding.sessionId) } })
|
|
17875
18825
|
}), "dsh-claude: sidecar projection provider");
|
|
17876
18826
|
ctx.effect(() => () => projections.dispose(), "dsh-claude: sidecar projection lifecycle");
|
|
18827
|
+
if (sessions !== void 0) ctx.effect(() => startClaudeSessionAlerts({
|
|
18828
|
+
sessions: {
|
|
18829
|
+
subscribe: (listener) => sessions.list.subscribe(listener),
|
|
18830
|
+
getSnapshot: () => sessions.list.getSnapshot()
|
|
18831
|
+
},
|
|
18832
|
+
projectionFor: (id) => projections.source(id),
|
|
18833
|
+
open: (id) => {
|
|
18834
|
+
sessions.open(id);
|
|
18835
|
+
},
|
|
18836
|
+
t
|
|
18837
|
+
}), "dsh-claude: session alerts");
|
|
17877
18838
|
const uiConversation = ctx.get("uiConversation");
|
|
17878
18839
|
if (uiConversation !== void 0) {
|
|
17879
18840
|
ctx.effect(() => uiConversation.events.register(claudeTurnDefinition), "dsh-claude: Claude turn marker");
|
|
@@ -17888,9 +18849,11 @@ window.__ModuleLoader__.load({
|
|
|
17888
18849
|
const layout = ctx.get("layout");
|
|
17889
18850
|
let disposePluginDetails;
|
|
17890
18851
|
let disposeDiffOverlay;
|
|
18852
|
+
let disposePlanOverlay;
|
|
17891
18853
|
let disposeExpandedDetailsResize;
|
|
17892
18854
|
let detailsSessionId;
|
|
17893
|
-
const diffOpen = new
|
|
18855
|
+
const diffOpen = new PanelOpenStore();
|
|
18856
|
+
const planOpen = new PanelOpenStore();
|
|
17894
18857
|
const restoreDiff = () => {
|
|
17895
18858
|
if (disposeDiffOverlay === void 0) return;
|
|
17896
18859
|
disposeDiffOverlay();
|
|
@@ -17898,16 +18861,26 @@ window.__ModuleLoader__.load({
|
|
|
17898
18861
|
layout?.openDetails();
|
|
17899
18862
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17900
18863
|
};
|
|
18864
|
+
const restorePlan = () => {
|
|
18865
|
+
if (disposePlanOverlay === void 0) return;
|
|
18866
|
+
disposePlanOverlay();
|
|
18867
|
+
disposePlanOverlay = void 0;
|
|
18868
|
+
layout?.openDetails();
|
|
18869
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
18870
|
+
};
|
|
17901
18871
|
const closePluginDetails = () => {
|
|
17902
|
-
if (disposePluginDetails === void 0 && disposeDiffOverlay === void 0 && disposeExpandedDetailsResize === void 0 && detailsSessionId === void 0) return;
|
|
18872
|
+
if (disposePluginDetails === void 0 && disposeDiffOverlay === void 0 && disposePlanOverlay === void 0 && disposeExpandedDetailsResize === void 0 && detailsSessionId === void 0) return;
|
|
17903
18873
|
disposeDiffOverlay?.();
|
|
17904
18874
|
disposeDiffOverlay = void 0;
|
|
18875
|
+
disposePlanOverlay?.();
|
|
18876
|
+
disposePlanOverlay = void 0;
|
|
17905
18877
|
disposeExpandedDetailsResize?.();
|
|
17906
18878
|
disposeExpandedDetailsResize = void 0;
|
|
17907
18879
|
disposePluginDetails?.();
|
|
17908
18880
|
disposePluginDetails = void 0;
|
|
17909
18881
|
detailsSessionId = void 0;
|
|
17910
18882
|
diffOpen.close();
|
|
18883
|
+
planOpen.close();
|
|
17911
18884
|
layout?.closeDetails();
|
|
17912
18885
|
};
|
|
17913
18886
|
ctx.effect(() => ctx.slots.onEntryError((key, entry, error) => {
|
|
@@ -17916,6 +18889,7 @@ window.__ModuleLoader__.load({
|
|
|
17916
18889
|
${error.stack ?? ""}` : String(error);
|
|
17917
18890
|
diagnostics.report("slot-entry-crashed", `slot "${key}"${id}: ${message}`);
|
|
17918
18891
|
if (key === "shell.overlay" && entry.options.id === "claude-diff-overlay") restoreDiff();
|
|
18892
|
+
if (key === "shell.overlay" && entry.options.id === "claude-plan-overlay") restorePlan();
|
|
17919
18893
|
}), "dsh-claude: Slot entry failure reporting");
|
|
17920
18894
|
const openTasksPanel = (sessionId, turn) => {
|
|
17921
18895
|
closePluginDetails();
|
|
@@ -17937,6 +18911,55 @@ window.__ModuleLoader__.load({
|
|
|
17937
18911
|
layout?.openDetails();
|
|
17938
18912
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17939
18913
|
};
|
|
18914
|
+
const openPlanPanel = (sessionId) => {
|
|
18915
|
+
closePluginDetails();
|
|
18916
|
+
const maximizePlan = () => {
|
|
18917
|
+
if (disposePlanOverlay !== void 0) {
|
|
18918
|
+
restorePlan();
|
|
18919
|
+
return;
|
|
18920
|
+
}
|
|
18921
|
+
disposeExpandedDetailsResize?.();
|
|
18922
|
+
disposeExpandedDetailsResize = void 0;
|
|
18923
|
+
layout?.closeDetails();
|
|
18924
|
+
try {
|
|
18925
|
+
disposePlanOverlay = ctx.slots.register({
|
|
18926
|
+
name: "shell.overlay",
|
|
18927
|
+
id: "claude-plan-overlay",
|
|
18928
|
+
locale: namespace
|
|
18929
|
+
}, () => /* @__PURE__ */ jsx(MaximizedPlan, {
|
|
18930
|
+
source: projections.source(sessionId),
|
|
18931
|
+
t,
|
|
18932
|
+
sessionId,
|
|
18933
|
+
closeDetails: closePluginDetails,
|
|
18934
|
+
restore: restorePlan
|
|
18935
|
+
}));
|
|
18936
|
+
} catch {
|
|
18937
|
+
disposePlanOverlay = void 0;
|
|
18938
|
+
layout?.openDetails();
|
|
18939
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
18940
|
+
}
|
|
18941
|
+
};
|
|
18942
|
+
try {
|
|
18943
|
+
disposePluginDetails = ctx.slots.register({
|
|
18944
|
+
name: "details",
|
|
18945
|
+
priority: -10,
|
|
18946
|
+
locale: namespace,
|
|
18947
|
+
inject: () => ({
|
|
18948
|
+
t,
|
|
18949
|
+
sessionId,
|
|
18950
|
+
closeDetails: closePluginDetails,
|
|
18951
|
+
maximized: false,
|
|
18952
|
+
toggleMaximized: maximizePlan
|
|
18953
|
+
})
|
|
18954
|
+
}, ClaudePlanPanel);
|
|
18955
|
+
} catch {
|
|
18956
|
+
return;
|
|
18957
|
+
}
|
|
18958
|
+
detailsSessionId = sessionId;
|
|
18959
|
+
planOpen.open(sessionId);
|
|
18960
|
+
layout?.openDetails();
|
|
18961
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
18962
|
+
};
|
|
17940
18963
|
const openOverviewPanel = (sessionId) => {
|
|
17941
18964
|
if (sessions === void 0) return;
|
|
17942
18965
|
closePluginDetails();
|
|
@@ -18027,7 +19050,7 @@ window.__ModuleLoader__.load({
|
|
|
18027
19050
|
ctx.effect(() => {
|
|
18028
19051
|
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => closePluginDetails();
|
|
18029
19052
|
const observer = new MutationObserver(() => {
|
|
18030
|
-
if (detailsSessionId !== void 0 && disposeDiffOverlay === void 0 && document.querySelector("[data-details-collapsed]") !== null) closePluginDetails();
|
|
19053
|
+
if (detailsSessionId !== void 0 && disposeDiffOverlay === void 0 && disposePlanOverlay === void 0 && document.querySelector("[data-details-collapsed]") !== null) closePluginDetails();
|
|
18031
19054
|
});
|
|
18032
19055
|
observer.observe(document.body, {
|
|
18033
19056
|
attributes: true,
|
|
@@ -18073,6 +19096,20 @@ window.__ModuleLoader__.load({
|
|
|
18073
19096
|
})
|
|
18074
19097
|
}, ClaudeAgentPresetLabel));
|
|
18075
19098
|
}
|
|
19099
|
+
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
19100
|
+
name: "conversation.session.header.utilities",
|
|
19101
|
+
id: "claude-plan",
|
|
19102
|
+
order: 29,
|
|
19103
|
+
locale: namespace,
|
|
19104
|
+
inject: (sessionId) => ({
|
|
19105
|
+
t,
|
|
19106
|
+
togglePlan: () => {
|
|
19107
|
+
if (planOpen.isOpen(sessionId)) closePluginDetails();
|
|
19108
|
+
else openPlanPanel(sessionId);
|
|
19109
|
+
},
|
|
19110
|
+
planOpen: planOpen.sourceFor(sessionId)
|
|
19111
|
+
})
|
|
19112
|
+
}, ClaudePlanHeaderAction));
|
|
18076
19113
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
18077
19114
|
name: "conversation.session.header.utilities",
|
|
18078
19115
|
id: "claude-diff",
|