@norman-else/dsh-claude 0.1.40 → 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 +32 -0
- package/lib/client.js +1653 -634
- 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 +769 -165
- 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,
|
|
@@ -4387,9 +4552,8 @@ window.__ModuleLoader__.load({
|
|
|
4387
4552
|
feed: applyLine,
|
|
4388
4553
|
subscribe(listener) {
|
|
4389
4554
|
if (disposed) return () => {};
|
|
4390
|
-
const wasIdle = listeners.size === 0;
|
|
4391
4555
|
listeners.add(listener);
|
|
4392
|
-
|
|
4556
|
+
onDemand(true);
|
|
4393
4557
|
return () => {
|
|
4394
4558
|
listeners.delete(listener);
|
|
4395
4559
|
if (listeners.size !== 0) return;
|
|
@@ -4405,6 +4569,13 @@ window.__ModuleLoader__.load({
|
|
|
4405
4569
|
}
|
|
4406
4570
|
};
|
|
4407
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
|
+
}
|
|
4408
4579
|
/**
|
|
4409
4580
|
* Every session's projection over ONE connection.
|
|
4410
4581
|
*
|
|
@@ -4477,10 +4648,12 @@ window.__ModuleLoader__.load({
|
|
|
4477
4648
|
* a session list would otherwise reopen it once per row. */
|
|
4478
4649
|
#demand(sessionId, active) {
|
|
4479
4650
|
if (this.#disposed) return;
|
|
4651
|
+
const before = this.#lanes();
|
|
4480
4652
|
if (active) {
|
|
4481
4653
|
this.#wanted.delete(sessionId);
|
|
4482
4654
|
this.#wanted.add(sessionId);
|
|
4483
4655
|
} else if (!this.#wanted.delete(sessionId)) return;
|
|
4656
|
+
if (sameLanes(before, this.#lanes())) return;
|
|
4484
4657
|
if (this.#settle !== void 0) clearTimeout(this.#settle);
|
|
4485
4658
|
const timer = setTimeout(() => {
|
|
4486
4659
|
this.#settle = void 0;
|
|
@@ -4624,10 +4797,10 @@ window.__ModuleLoader__.load({
|
|
|
4624
4797
|
".dsh-claude-act-running{animation:dsh-claude-act-pulse 1.2s ease-in-out infinite}",
|
|
4625
4798
|
"@keyframes dsh-claude-act-pulse{0%,100%{opacity:1}50%{opacity:.3}}"
|
|
4626
4799
|
].join("");
|
|
4627
|
-
let cssInjected$
|
|
4628
|
-
function ensureCss$
|
|
4629
|
-
if (cssInjected$
|
|
4630
|
-
cssInjected$
|
|
4800
|
+
let cssInjected$4 = false;
|
|
4801
|
+
function ensureCss$4() {
|
|
4802
|
+
if (cssInjected$4 || typeof document === "undefined") return;
|
|
4803
|
+
cssInjected$4 = true;
|
|
4631
4804
|
const element = document.createElement("style");
|
|
4632
4805
|
element.dataset.dshClaudeActivity = "";
|
|
4633
4806
|
element.textContent = ACTIVITY_CSS;
|
|
@@ -5055,7 +5228,7 @@ window.__ModuleLoader__.load({
|
|
|
5055
5228
|
});
|
|
5056
5229
|
}
|
|
5057
5230
|
function ClaudeActivityNode({ node, useClaudeProjection, t }) {
|
|
5058
|
-
ensureCss$
|
|
5231
|
+
ensureCss$4();
|
|
5059
5232
|
const marker = node.data;
|
|
5060
5233
|
const activities = useClaudeProjection((value) => selectStepActivities(value, marker.turn, marker.step));
|
|
5061
5234
|
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS$1);
|
|
@@ -5521,91 +5694,592 @@ window.__ModuleLoader__.load({
|
|
|
5521
5694
|
});
|
|
5522
5695
|
}
|
|
5523
5696
|
//#endregion
|
|
5524
|
-
//#region src/
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
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;
|
|
5531
5708
|
}
|
|
5532
|
-
}
|
|
5709
|
+
}
|
|
5710
|
+
//#endregion
|
|
5711
|
+
//#region src/client/pr-feedback-api.ts
|
|
5533
5712
|
function record$5(value) {
|
|
5534
5713
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5535
5714
|
}
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
* The routes answer `{ error, message }`, so `message` is already the sentence
|
|
5543
|
-
* to show. A body carrying only a code — a 405, a bad JSON body — used to read
|
|
5544
|
-
* 'Jira is unavailable.' rather than leaking the code as prose, and it still
|
|
5545
|
-
* does. Transport failures (a starved pool, an elapsed budget, an older Host
|
|
5546
|
-
* without the route) carry their own wording and keep it.
|
|
5547
|
-
*/
|
|
5548
|
-
function jiraFailure(cause) {
|
|
5549
|
-
if (cause instanceof JiraClientError) return cause;
|
|
5550
|
-
if (!(cause instanceof PluginRequestError)) return new JiraClientError(cause instanceof Error ? cause.message : String(cause));
|
|
5551
|
-
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
|
+
};
|
|
5552
5721
|
}
|
|
5553
|
-
function
|
|
5722
|
+
function answer(value) {
|
|
5554
5723
|
const body = record$5(value);
|
|
5555
|
-
if (body === void 0) throw new
|
|
5556
|
-
return body;
|
|
5557
|
-
}
|
|
5558
|
-
function status(value) {
|
|
5559
|
-
const body = payload(value);
|
|
5560
|
-
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.");
|
|
5561
5725
|
return body;
|
|
5562
5726
|
}
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
}
|
|
5567
|
-
throw jiraFailure(cause);
|
|
5568
|
-
}
|
|
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) }));
|
|
5569
5731
|
}
|
|
5570
|
-
async function
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
}
|
|
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
|
+
}));
|
|
5576
5737
|
}
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
throw jiraFailure(cause);
|
|
5582
|
-
}
|
|
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;
|
|
5583
5742
|
}
|
|
5584
|
-
async function
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
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
|
+
});
|
|
5597
5761
|
}
|
|
5762
|
+
return threads;
|
|
5598
5763
|
}
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
}
|
|
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;
|
|
5605
5772
|
}
|
|
5606
|
-
/**
|
|
5607
|
-
function
|
|
5608
|
-
|
|
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
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
/** Draft seeded into the composer when a session starts from a ticket. */
|
|
6281
|
+
function ticketPrompt(ticket) {
|
|
6282
|
+
return `Work on Jira ticket ${ticket.key}: ${ticket.summary}\n${ticket.url}\n\nRead the ticket, implement what it asks for, and reference ${ticket.key} in the commit and pull request.`;
|
|
5609
6283
|
}
|
|
5610
6284
|
/** Appended to a user-written draft so the session still knows its ticket. */
|
|
5611
6285
|
function ticketContext(ticket) {
|
|
@@ -5835,6 +6509,12 @@ window.__ModuleLoader__.load({
|
|
|
5835
6509
|
const value = settings.find((setting) => setting.key === "prose")?.value;
|
|
5836
6510
|
return isClaudeProseMode(value) ? value : DEFAULT_CLAUDE_PROSE_MODE;
|
|
5837
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
|
+
}
|
|
5838
6518
|
/** Settings whose row only makes sense under a particular value of another.
|
|
5839
6519
|
* Filtering here rather than server-side keeps the descriptor list flat: the
|
|
5840
6520
|
* server has no view of what the Client can paint. Fails OPEN — a payload
|
|
@@ -5860,6 +6540,10 @@ window.__ModuleLoader__.load({
|
|
|
5860
6540
|
label: "prose",
|
|
5861
6541
|
hint: "proseEffect"
|
|
5862
6542
|
},
|
|
6543
|
+
alerts: {
|
|
6544
|
+
label: "alerts",
|
|
6545
|
+
hint: "alertsEffect"
|
|
6546
|
+
},
|
|
5863
6547
|
worktreeBranchPrefix: {
|
|
5864
6548
|
label: "worktreeBranchPrefix",
|
|
5865
6549
|
hint: "worktreeBranchPrefixEffect"
|
|
@@ -5880,7 +6564,9 @@ window.__ModuleLoader__.load({
|
|
|
5880
6564
|
"renderer:plugin": "rendererPlugin",
|
|
5881
6565
|
"renderer:native": "rendererNative",
|
|
5882
6566
|
"prose:plain": "prosePlain",
|
|
5883
|
-
"prose:enhanced": "proseEnhanced"
|
|
6567
|
+
"prose:enhanced": "proseEnhanced",
|
|
6568
|
+
"alerts:off": "alertsOff",
|
|
6569
|
+
"alerts:on": "alertsOn"
|
|
5884
6570
|
};
|
|
5885
6571
|
function settingOptionLabel(settingKey, option, t) {
|
|
5886
6572
|
const key = SETTING_OPTION_COPY[`${settingKey}:${option.value}`];
|
|
@@ -6120,6 +6806,7 @@ window.__ModuleLoader__.load({
|
|
|
6120
6806
|
if (!isGlobalSettingsView(payload)) throw new Error("Invalid global settings response");
|
|
6121
6807
|
setGlobalSettings(payload);
|
|
6122
6808
|
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
6809
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
6123
6810
|
} catch (cause) {
|
|
6124
6811
|
setGlobalSettingsError(cardFailure(cause));
|
|
6125
6812
|
} finally {
|
|
@@ -6467,6 +7154,531 @@ window.__ModuleLoader__.load({
|
|
|
6467
7154
|
});
|
|
6468
7155
|
}
|
|
6469
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
|
|
7592
|
+
},
|
|
7593
|
+
disabled: sending || notes.length === 0 && draft.trim().length === 0,
|
|
7594
|
+
onClick: send,
|
|
7595
|
+
children: t(sending ? "planSending" : "planSendForChanges")
|
|
7596
|
+
})]
|
|
7597
|
+
})
|
|
7598
|
+
]
|
|
7599
|
+
})
|
|
7600
|
+
]
|
|
7601
|
+
});
|
|
7602
|
+
}
|
|
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
|
|
6470
7682
|
//#region src/client/repository-action-api.ts
|
|
6471
7683
|
var RepositoryActionClientError = class extends Error {
|
|
6472
7684
|
code;
|
|
@@ -6478,7 +7690,7 @@ window.__ModuleLoader__.load({
|
|
|
6478
7690
|
if (commit !== void 0) this.commit = commit;
|
|
6479
7691
|
}
|
|
6480
7692
|
};
|
|
6481
|
-
function record$
|
|
7693
|
+
function record$3(value) {
|
|
6482
7694
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6483
7695
|
}
|
|
6484
7696
|
/** The dialog branches on `code`, so a route refusal keeps arriving as this
|
|
@@ -6491,20 +7703,20 @@ window.__ModuleLoader__.load({
|
|
|
6491
7703
|
return error instanceof PluginRequestError ? new RepositoryActionClientError(error.message, error.code) : error;
|
|
6492
7704
|
}
|
|
6493
7705
|
function preview(value) {
|
|
6494
|
-
const input = record$
|
|
7706
|
+
const input = record$3(value);
|
|
6495
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.");
|
|
6496
7708
|
for (const file of input.files) {
|
|
6497
|
-
const item = record$
|
|
7709
|
+
const item = record$3(file);
|
|
6498
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.");
|
|
6499
7711
|
}
|
|
6500
7712
|
for (const commit of input.unpushedCommits) {
|
|
6501
|
-
const item = record$
|
|
7713
|
+
const item = record$3(commit);
|
|
6502
7714
|
if (item === void 0 || typeof item.hash !== "string" || typeof item.subject !== "string") throw new Error("Invalid repository action commit.");
|
|
6503
7715
|
}
|
|
6504
7716
|
return input;
|
|
6505
7717
|
}
|
|
6506
7718
|
function result(value) {
|
|
6507
|
-
const input = record$
|
|
7719
|
+
const input = record$3(value);
|
|
6508
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.");
|
|
6509
7721
|
return input;
|
|
6510
7722
|
}
|
|
@@ -6518,7 +7730,7 @@ window.__ModuleLoader__.load({
|
|
|
6518
7730
|
}
|
|
6519
7731
|
async function generateCommitMessage(sessionId, fingerprint, signal) {
|
|
6520
7732
|
try {
|
|
6521
|
-
const value = record$
|
|
7733
|
+
const value = record$3(await pluginWrite(`${CLAUDE_REPOSITORY_ACTION_PATH}/message`, "remote", signal, {
|
|
6522
7734
|
query: { sessionId },
|
|
6523
7735
|
json: { fingerprint }
|
|
6524
7736
|
}));
|
|
@@ -6581,16 +7793,16 @@ window.__ModuleLoader__.load({
|
|
|
6581
7793
|
/** The draft only has to describe the work; the host truncates it again before
|
|
6582
7794
|
* summarizing, and the setup route caps the whole body at 16 KiB. */
|
|
6583
7795
|
const MAX_INTENT_CHARS = 2e3;
|
|
6584
|
-
function record$
|
|
7796
|
+
function record$2(value) {
|
|
6585
7797
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6586
7798
|
}
|
|
6587
7799
|
function setupResult(value) {
|
|
6588
|
-
const item = record$
|
|
7800
|
+
const item = record$2(value);
|
|
6589
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;
|
|
6590
7802
|
return item;
|
|
6591
7803
|
}
|
|
6592
7804
|
function parseRepositorySetupEvent(line, onProgress) {
|
|
6593
|
-
const event = record$
|
|
7805
|
+
const event = record$2(JSON.parse(line));
|
|
6594
7806
|
if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
|
|
6595
7807
|
onProgress(event.stage);
|
|
6596
7808
|
return;
|
|
@@ -6667,235 +7879,46 @@ window.__ModuleLoader__.load({
|
|
|
6667
7879
|
} });
|
|
6668
7880
|
if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
|
|
6669
7881
|
return body;
|
|
6670
|
-
}
|
|
6671
|
-
/** Ask the Host to reconcile worktrees against the workspace registry now.
|
|
6672
|
-
* Deleting a workspace only touches the registry, so this kick is what makes
|
|
6673
|
-
* the worktree and its sessions go without waiting out the sweep interval. */
|
|
6674
|
-
async function sweepWorktrees() {
|
|
6675
|
-
await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/sweep`, "fast");
|
|
6676
|
-
}
|
|
6677
|
-
/** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
|
|
6678
|
-
async function loadRepositoryFileLines(cwd, path, from, to, signal) {
|
|
6679
|
-
const body = await pluginRead(CLAUDE_REPOSITORY_FILE_PATH, "git", signal, { query: {
|
|
6680
|
-
cwd,
|
|
6681
|
-
path,
|
|
6682
|
-
from: String(from),
|
|
6683
|
-
to: String(to)
|
|
6684
|
-
} });
|
|
6685
|
-
if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
|
|
6686
|
-
return {
|
|
6687
|
-
lines: body.lines.map(String),
|
|
6688
|
-
total: body.total
|
|
6689
|
-
};
|
|
6690
|
-
}
|
|
6691
|
-
async function loadRepositoryStatusFor(cwd, signal) {
|
|
6692
|
-
const body = await pluginRead(CLAUDE_REPOSITORY_STATUS_PATH, "git", signal, { query: { cwd } });
|
|
6693
|
-
if (typeof body.status !== "string" || typeof body.cwd !== "string") throw new Error("Invalid repository status response.");
|
|
6694
|
-
return body;
|
|
6695
|
-
}
|
|
6696
|
-
//#endregion
|
|
6697
|
-
//#region src/client/relative-age.ts
|
|
6698
|
-
/** Compact age of an ISO timestamp, in the shape the panels already use
|
|
6699
|
-
* ("<1h", "4h", "3d", "2mo"). `now` is a parameter so callers that re-render
|
|
6700
|
-
* on a clock — and tests — stay deterministic. */
|
|
6701
|
-
function relativeAge(value, now = Date.now()) {
|
|
6702
|
-
if (value === void 0) return void 0;
|
|
6703
|
-
const elapsedHours = Math.max(0, Math.floor((now - Date.parse(value)) / 36e5));
|
|
6704
|
-
if (!Number.isFinite(elapsedHours)) return void 0;
|
|
6705
|
-
if (elapsedHours < 1) return "<1h";
|
|
6706
|
-
if (elapsedHours < 24) return `${elapsedHours}h`;
|
|
6707
|
-
const days = Math.floor(elapsedHours / 24);
|
|
6708
|
-
return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
|
|
6709
|
-
}
|
|
6710
|
-
//#endregion
|
|
6711
|
-
//#region src/github-url.ts
|
|
6712
|
-
/** Only GitHub's own image hosts; the browser loads these directly, so a URL
|
|
6713
|
-
* the API did not vouch for must never become an outbound request. */
|
|
6714
|
-
function githubAvatarUrl(value) {
|
|
6715
|
-
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return void 0;
|
|
6716
|
-
try {
|
|
6717
|
-
const url = new URL(value);
|
|
6718
|
-
const allowed = url.hostname === "github.com" || url.hostname === "githubusercontent.com" || url.hostname.endsWith(".githubusercontent.com");
|
|
6719
|
-
return url.protocol === "https:" && allowed ? url.href : void 0;
|
|
6720
|
-
} catch {
|
|
6721
|
-
return;
|
|
6722
|
-
}
|
|
6723
|
-
}
|
|
6724
|
-
//#endregion
|
|
6725
|
-
//#region src/client/pr-feedback-api.ts
|
|
6726
|
-
function record$2(value) {
|
|
6727
|
-
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6728
|
-
}
|
|
6729
|
-
function feedbackQuery(sessionId, pullNumber, extra) {
|
|
6730
|
-
return {
|
|
6731
|
-
sessionId,
|
|
6732
|
-
number: String(pullNumber),
|
|
6733
|
-
...extra
|
|
6734
|
-
};
|
|
6735
|
-
}
|
|
6736
|
-
function answer(value) {
|
|
6737
|
-
const body = record$2(value);
|
|
6738
|
-
if (body === void 0) throw new Error("Invalid pull request feedback response.");
|
|
6739
|
-
return body;
|
|
6740
|
-
}
|
|
6741
|
-
/** Every arm of this route shells out to `gh`, so reads and writes alike take
|
|
6742
|
-
* the remote budget. */
|
|
6743
|
-
async function loadJson(path, sessionId, pullNumber, signal, extra) {
|
|
6744
|
-
return answer(await pluginRead(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", signal, { query: feedbackQuery(sessionId, pullNumber, extra) }));
|
|
6745
|
-
}
|
|
6746
|
-
async function postJson(path, sessionId, pullNumber, input) {
|
|
6747
|
-
return answer(await pluginWrite(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", void 0, {
|
|
6748
|
-
query: feedbackQuery(sessionId, pullNumber),
|
|
6749
|
-
json: input
|
|
6750
|
-
}));
|
|
6751
|
-
}
|
|
6752
|
-
function reviewComment(value) {
|
|
6753
|
-
const input = record$2(value);
|
|
6754
|
-
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;
|
|
6755
|
-
return input;
|
|
6756
|
-
}
|
|
6757
|
-
async function loadPullRequestThreads(sessionId, pullNumber, signal) {
|
|
6758
|
-
const body = await loadJson("/comments", sessionId, pullNumber, signal);
|
|
6759
|
-
if (!Array.isArray(body.threads)) throw new Error("Invalid pull request comments response.");
|
|
6760
|
-
const threads = [];
|
|
6761
|
-
for (const item of body.threads) {
|
|
6762
|
-
const input = record$2(item);
|
|
6763
|
-
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;
|
|
6764
|
-
const comments = input.comments.map(reviewComment).filter((value) => value !== void 0);
|
|
6765
|
-
if (comments.length === 0) continue;
|
|
6766
|
-
threads.push({
|
|
6767
|
-
id: input.id,
|
|
6768
|
-
path: input.path,
|
|
6769
|
-
...typeof input.line === "number" ? { line: input.line } : {},
|
|
6770
|
-
side: input.side,
|
|
6771
|
-
resolved: input.resolved === true,
|
|
6772
|
-
outdated: input.outdated === true,
|
|
6773
|
-
comments
|
|
6774
|
-
});
|
|
6775
|
-
}
|
|
6776
|
-
return threads;
|
|
6777
|
-
}
|
|
6778
|
-
/** Post one reply into the thread that `commentId` belongs to. */
|
|
6779
|
-
async function replyToReviewThread(sessionId, pullNumber, commentId, body) {
|
|
6780
|
-
const comment = reviewComment((await postJson("/reply", sessionId, pullNumber, {
|
|
6781
|
-
commentId,
|
|
6782
|
-
body
|
|
6783
|
-
})).comment);
|
|
6784
|
-
if (comment === void 0) throw new Error("Invalid pull request reply response.");
|
|
6785
|
-
return comment;
|
|
6786
|
-
}
|
|
6787
|
-
/** Resolve or reopen a thread; returns the state GitHub reports afterwards. */
|
|
6788
|
-
async function setReviewThreadResolved(sessionId, pullNumber, threadId, resolved) {
|
|
6789
|
-
const answer = await postJson("/resolve", sessionId, pullNumber, {
|
|
6790
|
-
threadId,
|
|
6791
|
-
resolved
|
|
6792
|
-
});
|
|
6793
|
-
if (typeof answer.resolved !== "boolean") throw new Error("Invalid pull request resolve response.");
|
|
6794
|
-
return answer.resolved;
|
|
6795
|
-
}
|
|
6796
|
-
/** Logins GitHub would notify, for the reply composer's `@` completion. */
|
|
6797
|
-
async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
|
|
6798
|
-
const body = await loadJson("/mentionables", sessionId, pullNumber, signal, { q: query });
|
|
6799
|
-
if (!Array.isArray(body.users)) return [];
|
|
6800
|
-
const users = [];
|
|
6801
|
-
for (const item of body.users) {
|
|
6802
|
-
const input = record$2(item);
|
|
6803
|
-
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
6804
|
-
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
6805
|
-
users.push({
|
|
6806
|
-
login: input.login,
|
|
6807
|
-
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
6808
|
-
});
|
|
6809
|
-
}
|
|
6810
|
-
return users;
|
|
6811
|
-
}
|
|
6812
|
-
async function loadFailingChecks(sessionId, pullNumber, signal) {
|
|
6813
|
-
const body = await loadJson("/checks", sessionId, pullNumber, signal);
|
|
6814
|
-
if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
|
|
6815
|
-
const checks = [];
|
|
6816
|
-
for (const item of body.checks) {
|
|
6817
|
-
const input = record$2(item);
|
|
6818
|
-
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;
|
|
6819
|
-
checks.push(input);
|
|
6820
|
-
}
|
|
6821
|
-
return checks;
|
|
6822
|
-
}
|
|
6823
|
-
/** Draft handed to Claude when the user forwards GitHub review comments. A
|
|
6824
|
-
* resolved thread is a settled conversation: forwarding it would ask Claude to
|
|
6825
|
-
* redo work the reviewers already signed off. */
|
|
6826
|
-
function composeCommentsPrompt(threads) {
|
|
6827
|
-
const open = threads.filter((thread) => !thread.resolved);
|
|
6828
|
-
if (open.length === 0) return "";
|
|
6829
|
-
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) => {
|
|
6830
|
-
const [first, ...rest] = thread.comments;
|
|
6831
|
-
if (first === void 0) return "";
|
|
6832
|
-
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");
|
|
6833
|
-
}).filter((block) => block.length > 0).join("\n")}`;
|
|
6834
|
-
}
|
|
6835
|
-
/** Draft handed to Claude when the user forwards failing CI checks. */
|
|
6836
|
-
function composeChecksPrompt(checks) {
|
|
6837
|
-
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) => {
|
|
6838
|
-
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\`\`\``}`;
|
|
6839
|
-
}).join("\n\n")}`;
|
|
6840
|
-
}
|
|
6841
|
-
/** Draft handed to Claude after an update-branch merge left conflicts behind. */
|
|
6842
|
-
function composeConflictsPrompt(baseBranch, conflicts, method = "merge") {
|
|
6843
|
-
const list = conflicts.map((file) => `- ${file}`).join("\n");
|
|
6844
|
-
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}`;
|
|
6845
|
-
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}`;
|
|
6846
|
-
}
|
|
6847
|
-
//#endregion
|
|
6848
|
-
//#region src/client/auto-fix.ts
|
|
6849
|
-
const AUTO_FIX_INTERVAL_MS = 3e4;
|
|
6850
|
-
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.";
|
|
6851
|
-
const EMPTY_MEMORY = { handledCommentIds: /* @__PURE__ */ new Set() };
|
|
6852
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
6853
|
-
function session(sessionId) {
|
|
6854
|
-
let entry = sessions.get(sessionId);
|
|
6855
|
-
if (entry === void 0) {
|
|
6856
|
-
entry = {
|
|
6857
|
-
enabled: false,
|
|
6858
|
-
memory: EMPTY_MEMORY
|
|
6859
|
-
};
|
|
6860
|
-
sessions.set(sessionId, entry);
|
|
6861
|
-
}
|
|
6862
|
-
return entry;
|
|
6863
|
-
}
|
|
6864
|
-
function autoFixEnabled(sessionId) {
|
|
6865
|
-
return session(sessionId).enabled;
|
|
6866
|
-
}
|
|
6867
|
-
function setAutoFixEnabled(sessionId, enabled) {
|
|
6868
|
-
session(sessionId).enabled = enabled;
|
|
6869
|
-
}
|
|
6870
|
-
function autoFixMemory(sessionId) {
|
|
6871
|
-
return session(sessionId).memory;
|
|
6872
|
-
}
|
|
6873
|
-
function rememberAutoFix(sessionId, memory) {
|
|
6874
|
-
session(sessionId).memory = memory;
|
|
6875
|
-
}
|
|
6876
|
-
/** One failing CI run yields one fix attempt: run links change when CI re-runs. */
|
|
6877
|
-
function checksSignature(checks) {
|
|
6878
|
-
if (checks.length === 0) return void 0;
|
|
6879
|
-
return checks.map((check) => `${check.name}|${check.link ?? ""}`).sort().join("\n");
|
|
6880
|
-
}
|
|
6881
|
-
function planAutoFix(memory, threads, checks) {
|
|
6882
|
-
const unhandled = threads.filter((thread) => !thread.resolved).filter((thread) => thread.comments.some((comment) => !memory.handledCommentIds.has(comment.id)));
|
|
6883
|
-
const fresh = unhandled.flatMap((thread) => thread.comments);
|
|
6884
|
-
const signature = checksSignature(checks);
|
|
6885
|
-
const checksChanged = signature !== void 0 && signature !== memory.handledChecksSignature;
|
|
6886
|
-
const sections = [];
|
|
6887
|
-
if (unhandled.length > 0) sections.push(composeCommentsPrompt(unhandled));
|
|
6888
|
-
if (checksChanged) sections.push(composeChecksPrompt(checks));
|
|
6889
|
-
if (sections.length === 0) return { memory };
|
|
6890
|
-
const nextSignature = checksChanged ? signature : memory.handledChecksSignature;
|
|
7882
|
+
}
|
|
7883
|
+
/** Ask the Host to reconcile worktrees against the workspace registry now.
|
|
7884
|
+
* Deleting a workspace only touches the registry, so this kick is what makes
|
|
7885
|
+
* the worktree and its sessions go without waiting out the sweep interval. */
|
|
7886
|
+
async function sweepWorktrees() {
|
|
7887
|
+
await pluginWrite(`${CLAUDE_REPOSITORY_SETUP_PATH}/sweep`, "fast");
|
|
7888
|
+
}
|
|
7889
|
+
/** Lines [from, to] of a working-tree file plus its total line count, for expanding unmodified diff context. */
|
|
7890
|
+
async function loadRepositoryFileLines(cwd, path, from, to, signal) {
|
|
7891
|
+
const body = await pluginRead(CLAUDE_REPOSITORY_FILE_PATH, "git", signal, { query: {
|
|
7892
|
+
cwd,
|
|
7893
|
+
path,
|
|
7894
|
+
from: String(from),
|
|
7895
|
+
to: String(to)
|
|
7896
|
+
} });
|
|
7897
|
+
if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
|
|
6891
7898
|
return {
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
handledCommentIds: /* @__PURE__ */ new Set([...memory.handledCommentIds, ...fresh.map((comment) => comment.id)]),
|
|
6895
|
-
...nextSignature === void 0 ? {} : { handledChecksSignature: nextSignature }
|
|
6896
|
-
}
|
|
7899
|
+
lines: body.lines.map(String),
|
|
7900
|
+
total: body.total
|
|
6897
7901
|
};
|
|
6898
7902
|
}
|
|
7903
|
+
async function loadRepositoryStatusFor(cwd, signal) {
|
|
7904
|
+
const body = await pluginRead(CLAUDE_REPOSITORY_STATUS_PATH, "git", signal, { query: { cwd } });
|
|
7905
|
+
if (typeof body.status !== "string" || typeof body.cwd !== "string") throw new Error("Invalid repository status response.");
|
|
7906
|
+
return body;
|
|
7907
|
+
}
|
|
7908
|
+
//#endregion
|
|
7909
|
+
//#region src/client/relative-age.ts
|
|
7910
|
+
/** Compact age of an ISO timestamp, in the shape the panels already use
|
|
7911
|
+
* ("<1h", "4h", "3d", "2mo"). `now` is a parameter so callers that re-render
|
|
7912
|
+
* on a clock — and tests — stay deterministic. */
|
|
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`;
|
|
7921
|
+
}
|
|
6899
7922
|
//#endregion
|
|
6900
7923
|
//#region src/client/boot-check.ts
|
|
6901
7924
|
/** Boot-time assertions against the running Host.
|
|
@@ -7088,7 +8111,7 @@ window.__ModuleLoader__.load({
|
|
|
7088
8111
|
]
|
|
7089
8112
|
});
|
|
7090
8113
|
}
|
|
7091
|
-
function repositoryName
|
|
8114
|
+
function repositoryName(remote) {
|
|
7092
8115
|
return remote?.split("/").at(-1);
|
|
7093
8116
|
}
|
|
7094
8117
|
function PullRequestHoverCard({ repository, t }) {
|
|
@@ -7116,7 +8139,7 @@ window.__ModuleLoader__.load({
|
|
|
7116
8139
|
/* @__PURE__ */ jsxs("span", {
|
|
7117
8140
|
style: repositoryPrHoverRepo,
|
|
7118
8141
|
children: [
|
|
7119
|
-
repositoryName
|
|
8142
|
+
repositoryName(repository.remote),
|
|
7120
8143
|
" #",
|
|
7121
8144
|
pullRequest.number,
|
|
7122
8145
|
pullRequest.baseBranch === void 0 ? "" : ` → ${pullRequest.baseBranch}`
|
|
@@ -7894,7 +8917,7 @@ window.__ModuleLoader__.load({
|
|
|
7894
8917
|
}),
|
|
7895
8918
|
repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
7896
8919
|
style: repositoryRemote,
|
|
7897
|
-
children: repositoryName
|
|
8920
|
+
children: repositoryName(repository.remote)
|
|
7898
8921
|
}),
|
|
7899
8922
|
/* @__PURE__ */ jsx(Tooltip, {
|
|
7900
8923
|
label: branch,
|
|
@@ -13939,7 +14962,7 @@ window.__ModuleLoader__.load({
|
|
|
13939
14962
|
] });
|
|
13940
14963
|
}
|
|
13941
14964
|
//#endregion
|
|
13942
|
-
//#region src/client/
|
|
14965
|
+
//#region src/client/ClaudePanelOverlay.tsx
|
|
13943
14966
|
const useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
13944
14967
|
function shouldRestoreFromEscape(event, root = document) {
|
|
13945
14968
|
return event.key === "Escape" && root.querySelector("[role=\"dialog\"][aria-modal=\"true\"]") === null;
|
|
@@ -13987,7 +15010,7 @@ window.__ModuleLoader__.load({
|
|
|
13987
15010
|
window.removeEventListener("resize", update);
|
|
13988
15011
|
};
|
|
13989
15012
|
}
|
|
13990
|
-
function
|
|
15013
|
+
function ClaudePanelOverlay({ children, onRestore }) {
|
|
13991
15014
|
const ref = useRef(null);
|
|
13992
15015
|
const [bounds, setBounds] = useState();
|
|
13993
15016
|
useClientLayoutEffect(() => {
|
|
@@ -14006,7 +15029,7 @@ window.__ModuleLoader__.load({
|
|
|
14006
15029
|
}, [onRestore]);
|
|
14007
15030
|
return /* @__PURE__ */ jsx("div", {
|
|
14008
15031
|
ref,
|
|
14009
|
-
"data-dsh-claude-
|
|
15032
|
+
"data-dsh-claude-panel-overlay": true,
|
|
14010
15033
|
style: {
|
|
14011
15034
|
position: "absolute",
|
|
14012
15035
|
left: bounds?.left ?? 0,
|
|
@@ -14075,304 +15098,110 @@ window.__ModuleLoader__.load({
|
|
|
14075
15098
|
label: hint ?? label,
|
|
14076
15099
|
side: "bottom",
|
|
14077
15100
|
delayMs: 400,
|
|
14078
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
14079
|
-
type: "button",
|
|
14080
|
-
className: panelIconButtonClass,
|
|
14081
|
-
"aria-label": label,
|
|
14082
|
-
disabled,
|
|
14083
|
-
onClick,
|
|
14084
|
-
children: icon
|
|
14085
|
-
})
|
|
14086
|
-
});
|
|
14087
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
14088
|
-
style: repositoryBarFrame,
|
|
14089
|
-
"data-claude-queue-dock": "",
|
|
14090
|
-
children: [/* @__PURE__ */ jsx("style", {
|
|
14091
|
-
"data-dsh-claude-queue-styles": true,
|
|
14092
|
-
children: panelIconButtonCss
|
|
14093
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
14094
|
-
style: queueBar,
|
|
14095
|
-
children: [queue.length > 1 ? /* @__PURE__ */ jsxs("button", {
|
|
14096
|
-
type: "button",
|
|
14097
|
-
style: queueHeader,
|
|
14098
|
-
"aria-controls": listId,
|
|
14099
|
-
"aria-expanded": expanded,
|
|
14100
|
-
disabled: interacting,
|
|
14101
|
-
onClick: () => {
|
|
14102
|
-
setCollapsed((value) => !value);
|
|
14103
|
-
},
|
|
14104
|
-
children: [
|
|
14105
|
-
/* @__PURE__ */ jsx("span", {
|
|
14106
|
-
style: queueLead,
|
|
14107
|
-
"aria-hidden": "true",
|
|
14108
|
-
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
14109
|
-
}),
|
|
14110
|
-
/* @__PURE__ */ jsx("span", {
|
|
14111
|
-
style: queueCount,
|
|
14112
|
-
children: t("queueCount", { n: queue.length })
|
|
14113
|
-
}),
|
|
14114
|
-
/* @__PURE__ */ jsx("span", {
|
|
14115
|
-
style: queueLead,
|
|
14116
|
-
"aria-hidden": "true",
|
|
14117
|
-
children: expanded ? /* @__PURE__ */ jsx(IconChevronDownOutline14, {}) : /* @__PURE__ */ jsx(IconChevronUpOutline14, {})
|
|
14118
|
-
})
|
|
14119
|
-
]
|
|
14120
|
-
}) : null, /* @__PURE__ */ jsx("ul", {
|
|
14121
|
-
id: listId,
|
|
14122
|
-
style: queueList,
|
|
14123
|
-
hidden: !listVisible,
|
|
14124
|
-
children: listVisible ? queue.map((row, index) => /* @__PURE__ */ jsxs("li", {
|
|
14125
|
-
style: {
|
|
14126
|
-
...queueRow,
|
|
14127
|
-
...index > 0 ? queueRowDivider : {}
|
|
14128
|
-
},
|
|
14129
|
-
children: [
|
|
14130
|
-
queue.length === 1 ? /* @__PURE__ */ jsx("span", {
|
|
14131
|
-
style: queueLead,
|
|
14132
|
-
"aria-hidden": "true",
|
|
14133
|
-
children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
|
|
14134
|
-
}) : null,
|
|
14135
|
-
editing?.id === row.id ? /* @__PURE__ */ jsx("input", {
|
|
14136
|
-
autoFocus: true,
|
|
14137
|
-
style: queueEditor,
|
|
14138
|
-
"aria-label": t("queueEdit"),
|
|
14139
|
-
value: editing.text,
|
|
14140
|
-
onChange: (event) => {
|
|
14141
|
-
setEditing({
|
|
14142
|
-
id: row.id,
|
|
14143
|
-
text: event.currentTarget.value
|
|
14144
|
-
});
|
|
14145
|
-
},
|
|
14146
|
-
onKeyDown: (event) => {
|
|
14147
|
-
if (event.key === "Escape") setEditing(void 0);
|
|
14148
|
-
else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
14149
|
-
event.preventDefault();
|
|
14150
|
-
saveEdit();
|
|
14151
|
-
}
|
|
14152
|
-
}
|
|
14153
|
-
}) : /* @__PURE__ */ jsx("span", {
|
|
14154
|
-
style: queuePreview,
|
|
14155
|
-
children: row.preview
|
|
14156
|
-
}),
|
|
14157
|
-
mutable ? /* @__PURE__ */ jsx("span", {
|
|
14158
|
-
style: queueActions,
|
|
14159
|
-
children: editing?.id === row.id ? /* @__PURE__ */ jsxs(Fragment$1, { children: [action(t("queueSave"), /* @__PURE__ */ jsx(IconCheckOutline16, { size: 14 }), () => {
|
|
14160
|
-
saveEdit();
|
|
14161
|
-
}, busy !== void 0 || editing.text.trim() === ""), action(t("queueCancelEdit"), /* @__PURE__ */ jsx(IconCloseOutline16, { size: 14 }), () => {
|
|
14162
|
-
setEditing(void 0);
|
|
14163
|
-
}, busy !== void 0)] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
14164
|
-
action(t("queueEdit"), /* @__PURE__ */ jsx(IconEditOutline16, { size: 14 }), () => {
|
|
14165
|
-
if (row.text !== null) setEditing({
|
|
14166
|
-
id: row.id,
|
|
14167
|
-
text: row.text
|
|
14168
|
-
});
|
|
14169
|
-
}, busy !== void 0 || row.text === null, row.text === null ? t("queueEditUnsupported") : void 0),
|
|
14170
|
-
action(t("queueRemove"), /* @__PURE__ */ jsx(IconTrashOutline16, { size: 14 }), () => {
|
|
14171
|
-
apply(row.id, { kind: "remove" }, t("queueRemoveFailed"));
|
|
14172
|
-
}, busy !== void 0),
|
|
14173
|
-
action(t("queueSteer"), /* @__PURE__ */ jsx(IconSendOutline14, {}), () => {
|
|
14174
|
-
apply(row.id, { kind: "steer" }, t("queueSteerFailed"));
|
|
14175
|
-
}, busy !== void 0 || !running, running ? void 0 : t("queueSteerUnavailable"))
|
|
14176
|
-
] })
|
|
14177
|
-
}) : null
|
|
14178
|
-
]
|
|
14179
|
-
}, row.id)) : null
|
|
14180
|
-
})]
|
|
14181
|
-
})]
|
|
14182
|
-
});
|
|
14183
|
-
}
|
|
14184
|
-
//#endregion
|
|
14185
|
-
//#region src/client/session-preset.ts
|
|
14186
|
-
/**
|
|
14187
|
-
* Resolve one row's preset id, newest seat first.
|
|
14188
|
-
* @param row - a session-list row, or undefined when the id is not listed.
|
|
14189
|
-
* @returns the preset id, or undefined when neither source carries one.
|
|
14190
|
-
*/
|
|
14191
|
-
function sessionRowPreset(row) {
|
|
14192
|
-
return row?.agentPreset ?? row?.projectionValues?.agentPreset ?? void 0;
|
|
14193
|
-
}
|
|
14194
|
-
//#endregion
|
|
14195
|
-
//#region src/client/ClaudePullRequestsPanel.tsx
|
|
14196
|
-
const NO_WORKSPACE_STATE = {};
|
|
14197
|
-
const NO_WORKSPACES = {
|
|
14198
|
-
subscribe: () => () => {},
|
|
14199
|
-
getSnapshot: () => NO_WORKSPACE_STATE
|
|
14200
|
-
};
|
|
14201
|
-
const OVERVIEW_REFRESH_MS = 3e4;
|
|
14202
|
-
/** What a running session is blocked on: the latest permission or question
|
|
14203
|
-
* activity that is still in its started phase. */
|
|
14204
|
-
function overviewAttention(activities) {
|
|
14205
|
-
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
14206
|
-
const activity = activities[index];
|
|
14207
|
-
if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
|
|
14208
|
-
return activity.phase === "started" ? activity.kind : void 0;
|
|
14209
|
-
}
|
|
14210
|
-
}
|
|
14211
|
-
/** Claude sessions worth listing: rows still in the host list (byId keeps
|
|
14212
|
-
* deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
|
|
14213
|
-
* running first. */
|
|
14214
|
-
function claudeSessionRows(state, archivedSessionIds = []) {
|
|
14215
|
-
const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
|
|
14216
|
-
const archived = new Set(archivedSessionIds);
|
|
14217
|
-
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));
|
|
14218
|
-
}
|
|
14219
|
-
function Badge({ label, tone = "neutral" }) {
|
|
14220
|
-
const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
|
|
14221
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
14222
|
-
style: {
|
|
14223
|
-
...repositoryItem,
|
|
14224
|
-
...toneStyle
|
|
14225
|
-
},
|
|
14226
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
14227
|
-
style: repositoryItemDot,
|
|
14228
|
-
"aria-hidden": "true"
|
|
14229
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
14230
|
-
style: repositoryItemLabel,
|
|
14231
|
-
children: label
|
|
14232
|
-
})]
|
|
14233
|
-
});
|
|
14234
|
-
}
|
|
14235
|
-
function repositoryName(remote) {
|
|
14236
|
-
return remote?.split("/").at(-1);
|
|
14237
|
-
}
|
|
14238
|
-
function OverviewAttention({ source, running, t }) {
|
|
14239
|
-
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
14240
|
-
const attention = running ? overviewAttention(snapshot.activities) : void 0;
|
|
14241
|
-
const usage = snapshot.contextUsage;
|
|
14242
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
14243
|
-
attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
|
|
14244
|
-
label: t("overviewNeedsPermission"),
|
|
14245
|
-
tone: "warning"
|
|
14246
|
-
}) : null,
|
|
14247
|
-
attention === "question" ? /* @__PURE__ */ jsx(Badge, {
|
|
14248
|
-
label: t("overviewNeedsAnswer"),
|
|
14249
|
-
tone: "warning"
|
|
14250
|
-
}) : null,
|
|
14251
|
-
usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
|
|
14252
|
-
] });
|
|
14253
|
-
}
|
|
14254
|
-
function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
|
|
14255
|
-
const sessionStore = useMemo(() => ({
|
|
14256
|
-
subscribe: (listener) => sessions.subscribe(listener),
|
|
14257
|
-
getSnapshot: () => sessions.getSnapshot()
|
|
14258
|
-
}), [sessions]);
|
|
14259
|
-
const snapshot = useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
|
|
14260
|
-
const workspaceStore = useMemo(() => {
|
|
14261
|
-
const source = workspaces ?? NO_WORKSPACES;
|
|
14262
|
-
return {
|
|
14263
|
-
subscribe: (listener) => source.subscribe(listener),
|
|
14264
|
-
getSnapshot: () => source.getSnapshot()
|
|
14265
|
-
};
|
|
14266
|
-
}, [workspaces]);
|
|
14267
|
-
const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
|
|
14268
|
-
const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
|
|
14269
|
-
const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
|
|
14270
|
-
const [statuses, setStatuses] = useState({});
|
|
14271
|
-
useEffect(() => {
|
|
14272
|
-
const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
|
|
14273
|
-
if (cwds.length === 0) return;
|
|
14274
|
-
const controller = new AbortController();
|
|
14275
|
-
const refresh = () => {
|
|
14276
|
-
for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
|
|
14277
|
-
if (!controller.signal.aborted) setStatuses((previous) => ({
|
|
14278
|
-
...previous,
|
|
14279
|
-
[cwd]: status
|
|
14280
|
-
}));
|
|
14281
|
-
}, () => void 0);
|
|
14282
|
-
};
|
|
14283
|
-
refresh();
|
|
14284
|
-
const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
|
|
14285
|
-
return () => {
|
|
14286
|
-
controller.abort();
|
|
14287
|
-
clearInterval(timer);
|
|
14288
|
-
};
|
|
14289
|
-
}, [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
|
+
});
|
|
14290
15110
|
return /* @__PURE__ */ jsxs("div", {
|
|
14291
|
-
|
|
14292
|
-
|
|
14293
|
-
children: [
|
|
14294
|
-
|
|
14295
|
-
|
|
14296
|
-
|
|
14297
|
-
|
|
14298
|
-
/* @__PURE__ */ jsxs("
|
|
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
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
|
|
14374
|
-
|
|
14375
|
-
|
|
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
|
+
})]
|
|
14376
15205
|
});
|
|
14377
15206
|
}
|
|
14378
15207
|
//#endregion
|
|
@@ -15112,13 +15941,19 @@ window.__ModuleLoader__.load({
|
|
|
15112
15941
|
//#endregion
|
|
15113
15942
|
//#region src/client/rewind-api.ts
|
|
15114
15943
|
/** Drop one user message and everything after it: the rows are hidden from
|
|
15115
|
-
* this session's transcript and Claude resumes before that turn.
|
|
15116
|
-
|
|
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) {
|
|
15117
15951
|
try {
|
|
15118
|
-
await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
15952
|
+
return { filesRestored: (await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
15119
15953
|
sessionId,
|
|
15120
|
-
seq
|
|
15121
|
-
|
|
15954
|
+
seq,
|
|
15955
|
+
restoreFiles
|
|
15956
|
+
} }))?.filesRestored === true };
|
|
15122
15957
|
} catch (error) {
|
|
15123
15958
|
if (!(error instanceof PluginRequestError)) throw error;
|
|
15124
15959
|
throw new Error(error.code ?? error.reason);
|
|
@@ -15187,6 +16022,8 @@ window.__ModuleLoader__.load({
|
|
|
15187
16022
|
const [target, setTarget] = useState();
|
|
15188
16023
|
const [submitting, setSubmitting] = useState(false);
|
|
15189
16024
|
const [error, setError] = useState();
|
|
16025
|
+
const [restoreFiles, setRestoreFiles] = useState(true);
|
|
16026
|
+
const { toast, report } = useActionToast();
|
|
15190
16027
|
const ranges = projection.rewind?.ranges ?? EMPTY_RANGES;
|
|
15191
16028
|
const owned = projection.owned;
|
|
15192
16029
|
const unavailable = snapshot.running;
|
|
@@ -15254,6 +16091,7 @@ window.__ModuleLoader__.load({
|
|
|
15254
16091
|
setTarget(void 0);
|
|
15255
16092
|
setSubmitting(false);
|
|
15256
16093
|
setError(void 0);
|
|
16094
|
+
setRestoreFiles(true);
|
|
15257
16095
|
}, [sessionId]);
|
|
15258
16096
|
if (sessionId === void 0 || !owned) return /* @__PURE__ */ jsx("span", {
|
|
15259
16097
|
"data-dsh-claude-rewind-armed": "armed",
|
|
@@ -15268,10 +16106,11 @@ window.__ModuleLoader__.load({
|
|
|
15268
16106
|
if (target === void 0 || submitting) return;
|
|
15269
16107
|
setSubmitting(true);
|
|
15270
16108
|
setError(void 0);
|
|
15271
|
-
rewindSession(sessionId, target.seq).then(() => {
|
|
16109
|
+
rewindSession(sessionId, target.seq, restoreFiles).then(({ filesRestored }) => {
|
|
15272
16110
|
setSubmitting(false);
|
|
15273
16111
|
setTarget(void 0);
|
|
15274
16112
|
if (target.text !== "") setDraft?.(sessionId, target.text);
|
|
16113
|
+
if (restoreFiles && !filesRestored) report(t("rewindFilesUnavailable"));
|
|
15275
16114
|
}, (reason) => {
|
|
15276
16115
|
setSubmitting(false);
|
|
15277
16116
|
const code = reason instanceof Error && reason.message !== "" ? reason.message : "unknown";
|
|
@@ -15279,6 +16118,7 @@ window.__ModuleLoader__.load({
|
|
|
15279
16118
|
});
|
|
15280
16119
|
};
|
|
15281
16120
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
16121
|
+
toast,
|
|
15282
16122
|
/* @__PURE__ */ jsx("style", {
|
|
15283
16123
|
"data-dsh-claude-rewind-styles": true,
|
|
15284
16124
|
children: `${rewindActionCss}${rewindHiddenCss(hiddenKeys)}`
|
|
@@ -15343,9 +16183,20 @@ window.__ModuleLoader__.load({
|
|
|
15343
16183
|
style: rewindModalMessage,
|
|
15344
16184
|
children: target.text.slice(0, 2e3)
|
|
15345
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
|
+
}),
|
|
15346
16197
|
/* @__PURE__ */ jsx("p", {
|
|
15347
16198
|
style: diffModalStatus,
|
|
15348
|
-
children: t("rewindHint")
|
|
16199
|
+
children: restoreFiles ? t("rewindRestoreFilesHint") : t("rewindHint")
|
|
15349
16200
|
}),
|
|
15350
16201
|
error === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
15351
16202
|
style: diffModalError,
|
|
@@ -16674,19 +17525,19 @@ window.__ModuleLoader__.load({
|
|
|
16674
17525
|
});
|
|
16675
17526
|
}
|
|
16676
17527
|
//#endregion
|
|
16677
|
-
//#region src/client/
|
|
16678
|
-
var
|
|
17528
|
+
//#region src/client/panel-open-store.ts
|
|
17529
|
+
var PanelOpenStore = class {
|
|
16679
17530
|
#sessionId;
|
|
16680
17531
|
#listeners = /* @__PURE__ */ new Set();
|
|
16681
|
-
/** Mark
|
|
17532
|
+
/** Mark this panel open for one session, replacing any previous holder. */
|
|
16682
17533
|
open(sessionId) {
|
|
16683
17534
|
this.#set(sessionId);
|
|
16684
17535
|
}
|
|
16685
|
-
/** Mark
|
|
17536
|
+
/** Mark this panel closed. */
|
|
16686
17537
|
close() {
|
|
16687
17538
|
this.#set(void 0);
|
|
16688
17539
|
}
|
|
16689
|
-
/** Whether
|
|
17540
|
+
/** Whether this panel is currently open for this session. */
|
|
16690
17541
|
isOpen(sessionId) {
|
|
16691
17542
|
return this.#sessionId === sessionId;
|
|
16692
17543
|
}
|
|
@@ -17222,6 +18073,27 @@ window.__ModuleLoader__.load({
|
|
|
17222
18073
|
yes: "是",
|
|
17223
18074
|
no: "否",
|
|
17224
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 的审批弹框里批准或拒绝这个方案。",
|
|
17225
18097
|
diffClose: "关闭 Diff 面板",
|
|
17226
18098
|
sessionMenu: "会话菜单",
|
|
17227
18099
|
sessionMenuOpenIn: "打开方式",
|
|
@@ -17403,6 +18275,17 @@ window.__ModuleLoader__.load({
|
|
|
17403
18275
|
rewindFailed: "回退失败({code})。",
|
|
17404
18276
|
rewindBusy: "这个会话正在运行,请等本轮结束后再回退。",
|
|
17405
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: "对话已回退,但文件没有还原:这一轮没有留下工作区快照。",
|
|
17406
18289
|
turnUsage: "本回合用量",
|
|
17407
18290
|
turnUsageTokens: "{count} tok",
|
|
17408
18291
|
turnUsageCache: "缓存命中 {percent}%",
|
|
@@ -17600,6 +18483,27 @@ window.__ModuleLoader__.load({
|
|
|
17600
18483
|
yes: "Yes",
|
|
17601
18484
|
no: "No",
|
|
17602
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.",
|
|
17603
18507
|
diffClose: "Close diff panel",
|
|
17604
18508
|
sessionMenu: "Session menu",
|
|
17605
18509
|
sessionMenuOpenIn: "Open in",
|
|
@@ -17781,6 +18685,17 @@ window.__ModuleLoader__.load({
|
|
|
17781
18685
|
rewindFailed: "The rewind failed ({code}).",
|
|
17782
18686
|
rewindBusy: "This session is running; wait for the turn to finish before rewinding.",
|
|
17783
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.",
|
|
17784
18699
|
turnUsage: "Turn usage",
|
|
17785
18700
|
turnUsageTokens: "{count} tok",
|
|
17786
18701
|
turnUsageCache: "Cache hit {percent}%",
|
|
@@ -17792,7 +18707,7 @@ window.__ModuleLoader__.load({
|
|
|
17792
18707
|
function MaximizedDiff({ source, t, sessionId, closeDetails, restore, submitPrompt }) {
|
|
17793
18708
|
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
17794
18709
|
const useClaudeProjection = (selector) => selector(snapshot);
|
|
17795
|
-
return /* @__PURE__ */ jsx(
|
|
18710
|
+
return /* @__PURE__ */ jsx(ClaudePanelOverlay, {
|
|
17796
18711
|
onRestore: restore,
|
|
17797
18712
|
children: /* @__PURE__ */ jsx(ClaudeDiffPanel, {
|
|
17798
18713
|
useClaudeProjection,
|
|
@@ -17806,6 +18721,21 @@ window.__ModuleLoader__.load({
|
|
|
17806
18721
|
});
|
|
17807
18722
|
}
|
|
17808
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
|
+
}
|
|
17809
18739
|
const inject = [
|
|
17810
18740
|
"slots",
|
|
17811
18741
|
"locale",
|
|
@@ -17847,7 +18777,9 @@ window.__ModuleLoader__.load({
|
|
|
17847
18777
|
const t = ctx.locale.bind(namespace);
|
|
17848
18778
|
ctx.effect(() => restyleHostChrome(), "dsh-claude: Host chrome restyling");
|
|
17849
18779
|
pluginRead(CLAUDE_GLOBAL_SETTINGS_PATH, "fast").then((payload) => {
|
|
17850
|
-
if (isGlobalSettingsView(payload))
|
|
18780
|
+
if (!isGlobalSettingsView(payload)) return;
|
|
18781
|
+
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
18782
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
17851
18783
|
}).catch(() => {});
|
|
17852
18784
|
const projections = new ClaudeProjectionStore({ report: (kind, detail) => {
|
|
17853
18785
|
diagnostics.report(kind, detail);
|
|
@@ -17892,6 +18824,17 @@ window.__ModuleLoader__.load({
|
|
|
17892
18824
|
resolve: (binding) => ({ hooks: { claudeProjection: projections.source(binding.sessionId) } })
|
|
17893
18825
|
}), "dsh-claude: sidecar projection provider");
|
|
17894
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");
|
|
17895
18838
|
const uiConversation = ctx.get("uiConversation");
|
|
17896
18839
|
if (uiConversation !== void 0) {
|
|
17897
18840
|
ctx.effect(() => uiConversation.events.register(claudeTurnDefinition), "dsh-claude: Claude turn marker");
|
|
@@ -17906,9 +18849,11 @@ window.__ModuleLoader__.load({
|
|
|
17906
18849
|
const layout = ctx.get("layout");
|
|
17907
18850
|
let disposePluginDetails;
|
|
17908
18851
|
let disposeDiffOverlay;
|
|
18852
|
+
let disposePlanOverlay;
|
|
17909
18853
|
let disposeExpandedDetailsResize;
|
|
17910
18854
|
let detailsSessionId;
|
|
17911
|
-
const diffOpen = new
|
|
18855
|
+
const diffOpen = new PanelOpenStore();
|
|
18856
|
+
const planOpen = new PanelOpenStore();
|
|
17912
18857
|
const restoreDiff = () => {
|
|
17913
18858
|
if (disposeDiffOverlay === void 0) return;
|
|
17914
18859
|
disposeDiffOverlay();
|
|
@@ -17916,16 +18861,26 @@ window.__ModuleLoader__.load({
|
|
|
17916
18861
|
layout?.openDetails();
|
|
17917
18862
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17918
18863
|
};
|
|
18864
|
+
const restorePlan = () => {
|
|
18865
|
+
if (disposePlanOverlay === void 0) return;
|
|
18866
|
+
disposePlanOverlay();
|
|
18867
|
+
disposePlanOverlay = void 0;
|
|
18868
|
+
layout?.openDetails();
|
|
18869
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
18870
|
+
};
|
|
17919
18871
|
const closePluginDetails = () => {
|
|
17920
|
-
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;
|
|
17921
18873
|
disposeDiffOverlay?.();
|
|
17922
18874
|
disposeDiffOverlay = void 0;
|
|
18875
|
+
disposePlanOverlay?.();
|
|
18876
|
+
disposePlanOverlay = void 0;
|
|
17923
18877
|
disposeExpandedDetailsResize?.();
|
|
17924
18878
|
disposeExpandedDetailsResize = void 0;
|
|
17925
18879
|
disposePluginDetails?.();
|
|
17926
18880
|
disposePluginDetails = void 0;
|
|
17927
18881
|
detailsSessionId = void 0;
|
|
17928
18882
|
diffOpen.close();
|
|
18883
|
+
planOpen.close();
|
|
17929
18884
|
layout?.closeDetails();
|
|
17930
18885
|
};
|
|
17931
18886
|
ctx.effect(() => ctx.slots.onEntryError((key, entry, error) => {
|
|
@@ -17934,6 +18889,7 @@ window.__ModuleLoader__.load({
|
|
|
17934
18889
|
${error.stack ?? ""}` : String(error);
|
|
17935
18890
|
diagnostics.report("slot-entry-crashed", `slot "${key}"${id}: ${message}`);
|
|
17936
18891
|
if (key === "shell.overlay" && entry.options.id === "claude-diff-overlay") restoreDiff();
|
|
18892
|
+
if (key === "shell.overlay" && entry.options.id === "claude-plan-overlay") restorePlan();
|
|
17937
18893
|
}), "dsh-claude: Slot entry failure reporting");
|
|
17938
18894
|
const openTasksPanel = (sessionId, turn) => {
|
|
17939
18895
|
closePluginDetails();
|
|
@@ -17955,6 +18911,55 @@ window.__ModuleLoader__.load({
|
|
|
17955
18911
|
layout?.openDetails();
|
|
17956
18912
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17957
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
|
+
};
|
|
17958
18963
|
const openOverviewPanel = (sessionId) => {
|
|
17959
18964
|
if (sessions === void 0) return;
|
|
17960
18965
|
closePluginDetails();
|
|
@@ -18045,7 +19050,7 @@ window.__ModuleLoader__.load({
|
|
|
18045
19050
|
ctx.effect(() => {
|
|
18046
19051
|
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => closePluginDetails();
|
|
18047
19052
|
const observer = new MutationObserver(() => {
|
|
18048
|
-
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();
|
|
18049
19054
|
});
|
|
18050
19055
|
observer.observe(document.body, {
|
|
18051
19056
|
attributes: true,
|
|
@@ -18091,6 +19096,20 @@ window.__ModuleLoader__.load({
|
|
|
18091
19096
|
})
|
|
18092
19097
|
}, ClaudeAgentPresetLabel));
|
|
18093
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));
|
|
18094
19113
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
18095
19114
|
name: "conversation.session.header.utilities",
|
|
18096
19115
|
id: "claude-diff",
|