@norman-else/dsh-claude 0.1.40 → 0.1.42
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 +7 -3
- package/lib/bin.mjs +1 -1
- package/lib/client.d.ts +47 -1
- package/lib/client.js +2192 -523
- package/lib/client.js.map +1 -1
- package/lib/{events-OhBoFNKO.mjs → events-oovRTmX7.mjs} +10 -2
- package/lib/events-oovRTmX7.mjs.map +1 -0
- package/lib/index.d.mts +56 -3
- package/lib/index.mjs +1424 -202
- package/lib/index.mjs.map +1 -1
- package/lib/{presenters-BBoM1Ju1.mjs → presenters-BVWj7u0a.mjs} +21 -2
- package/lib/{presenters-BBoM1Ju1.mjs.map → presenters-BVWj7u0a.mjs.map} +1 -1
- package/lib/{preset-installer-loenwnLS.mjs → preset-installer-JUktnfwS.mjs} +2 -2
- package/lib/{preset-installer-loenwnLS.mjs.map → preset-installer-JUktnfwS.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
|
@@ -4,7 +4,7 @@ window.__ModuleLoader__.load({
|
|
|
4
4
|
var module = { exports: {} };
|
|
5
5
|
module.exports;
|
|
6
6
|
var { Fragment, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } = require("react");
|
|
7
|
-
var { DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFullscreenOutline16, IconQueueOutline14, IconRefreshOutline14, IconRightUpOutline14, IconSearchOutline16, IconSendOutline14, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Toast, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
7
|
+
var { Button, DiffBlock, DisclosureRow, HoverCard, IconAgentPresetOutline16, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronRightOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconEllipsisOutline16, IconFullscreenOutline16, IconListPenOutline16, IconLoadingOutline16, IconQueueOutline14, IconRefreshOutline14, IconRefreshOutline16, IconRightUpOutline14, IconSearchOutline16, IconSendOutline14, IconSparkle16, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Toast, Tooltip, useDismissOnOutsidePointer } = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
8
8
|
var { Fragment: Fragment$1, jsx, jsxs } = require("react/jsx-runtime");
|
|
9
9
|
var { createPortal } = require("react-dom");
|
|
10
10
|
/** Claude's subagent dispatch tools; rendered as plugin-owned group cards
|
|
@@ -27,6 +27,10 @@ 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";
|
|
31
|
+
const CLAUDE_PROMPTS_PATH = "/plugins/dsh-claude/prompts";
|
|
32
|
+
const CLAUDE_PROMPT_NAME_PATH = "/plugins/dsh-claude/prompts/name";
|
|
33
|
+
const CLAUDE_PROMPT_REFINE_PATH = "/plugins/dsh-claude/prompts/refine";
|
|
30
34
|
function isClaudeRenderMode(value) {
|
|
31
35
|
return value === "plugin" || value === "native";
|
|
32
36
|
}
|
|
@@ -34,6 +38,9 @@ window.__ModuleLoader__.load({
|
|
|
34
38
|
function isClaudeProseMode(value) {
|
|
35
39
|
return value === "plain" || value === "enhanced";
|
|
36
40
|
}
|
|
41
|
+
function isClaudeAlertMode(value) {
|
|
42
|
+
return value === "off" || value === "on";
|
|
43
|
+
}
|
|
37
44
|
//#endregion
|
|
38
45
|
//#region src/client/task-projection.ts
|
|
39
46
|
/** Tasks UI is reserved for detached work and genuine Claude subagents. */
|
|
@@ -268,6 +275,10 @@ window.__ModuleLoader__.load({
|
|
|
268
275
|
failedAction = description === void 0 ? `run ${command}` : description;
|
|
269
276
|
break;
|
|
270
277
|
}
|
|
278
|
+
case "ExitPlanMode":
|
|
279
|
+
completed = "Proposed a plan";
|
|
280
|
+
failedAction = "propose a plan";
|
|
281
|
+
break;
|
|
271
282
|
default:
|
|
272
283
|
completed = description ?? `${toolName}${target === void 0 ? "" : ` ${target}`}`;
|
|
273
284
|
failedAction = completed.charAt(0).toLowerCase() + completed.slice(1);
|
|
@@ -1096,6 +1107,163 @@ window.__ModuleLoader__.load({
|
|
|
1096
1107
|
fontSize: 12,
|
|
1097
1108
|
lineHeight: "18px"
|
|
1098
1109
|
};
|
|
1110
|
+
/** Title and state chip: one phrase at the header's left end. */
|
|
1111
|
+
const planHeaderStart = {
|
|
1112
|
+
display: "flex",
|
|
1113
|
+
alignItems: "center",
|
|
1114
|
+
gap: 8,
|
|
1115
|
+
minWidth: 0
|
|
1116
|
+
};
|
|
1117
|
+
/** "2 / 5" next to the title when the session has proposed more than one. */
|
|
1118
|
+
const planCount = {
|
|
1119
|
+
flex: "none",
|
|
1120
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1121
|
+
fontSize: 12,
|
|
1122
|
+
lineHeight: "17px",
|
|
1123
|
+
fontVariantNumeric: "tabular-nums"
|
|
1124
|
+
};
|
|
1125
|
+
/** The review composer: notes filed so far, the pending quote, and the box.
|
|
1126
|
+
* Docked below the plan body so the plan itself keeps the scrolling room. */
|
|
1127
|
+
const planComposer = {
|
|
1128
|
+
flex: "none",
|
|
1129
|
+
display: "flex",
|
|
1130
|
+
flexDirection: "column",
|
|
1131
|
+
gap: 6,
|
|
1132
|
+
maxHeight: "45%",
|
|
1133
|
+
overflowY: "auto",
|
|
1134
|
+
padding: "10px 14px 12px",
|
|
1135
|
+
borderTop: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
|
|
1136
|
+
background: "var(--dsw-alias-bg-layer-1)"
|
|
1137
|
+
};
|
|
1138
|
+
const planNoteList = {
|
|
1139
|
+
display: "flex",
|
|
1140
|
+
flexDirection: "column",
|
|
1141
|
+
gap: 6,
|
|
1142
|
+
margin: 0,
|
|
1143
|
+
padding: 0,
|
|
1144
|
+
listStyle: "none"
|
|
1145
|
+
};
|
|
1146
|
+
const planNote = {
|
|
1147
|
+
position: "relative",
|
|
1148
|
+
padding: "6px 26px 6px 8px",
|
|
1149
|
+
borderRadius: 7,
|
|
1150
|
+
background: "var(--dsw-alias-bg-base)"
|
|
1151
|
+
};
|
|
1152
|
+
/** The quoted passage, marked as someone else's words by the rule down its
|
|
1153
|
+
* left edge rather than by quotation marks it may already contain. */
|
|
1154
|
+
const planNoteQuote = {
|
|
1155
|
+
display: "-webkit-box",
|
|
1156
|
+
WebkitLineClamp: 3,
|
|
1157
|
+
WebkitBoxOrient: "vertical",
|
|
1158
|
+
overflow: "hidden",
|
|
1159
|
+
margin: 0,
|
|
1160
|
+
paddingLeft: 8,
|
|
1161
|
+
borderLeft: "2px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 24%, transparent))",
|
|
1162
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1163
|
+
fontSize: 11,
|
|
1164
|
+
lineHeight: "16px",
|
|
1165
|
+
whiteSpace: "pre-wrap"
|
|
1166
|
+
};
|
|
1167
|
+
const planNoteText = {
|
|
1168
|
+
margin: "4px 0 0",
|
|
1169
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1170
|
+
fontSize: 12,
|
|
1171
|
+
lineHeight: "17px",
|
|
1172
|
+
whiteSpace: "pre-wrap"
|
|
1173
|
+
};
|
|
1174
|
+
const planNoteRemove = {
|
|
1175
|
+
position: "absolute",
|
|
1176
|
+
top: 4,
|
|
1177
|
+
right: 4,
|
|
1178
|
+
width: 18,
|
|
1179
|
+
height: 18,
|
|
1180
|
+
display: "grid",
|
|
1181
|
+
placeItems: "center",
|
|
1182
|
+
padding: 0,
|
|
1183
|
+
border: 0,
|
|
1184
|
+
borderRadius: 5,
|
|
1185
|
+
background: "transparent",
|
|
1186
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1187
|
+
fontSize: 14,
|
|
1188
|
+
lineHeight: 1,
|
|
1189
|
+
cursor: "pointer"
|
|
1190
|
+
};
|
|
1191
|
+
/** The live selection, shown attached to the box it will be filed with. */
|
|
1192
|
+
const planQuoteChip = {
|
|
1193
|
+
position: "relative",
|
|
1194
|
+
padding: "6px 26px 6px 8px",
|
|
1195
|
+
borderRadius: 7,
|
|
1196
|
+
background: "var(--dsw-alias-interactive-bg-hover)"
|
|
1197
|
+
};
|
|
1198
|
+
const planComposerInput = {
|
|
1199
|
+
boxSizing: "border-box",
|
|
1200
|
+
width: "100%",
|
|
1201
|
+
minHeight: 56,
|
|
1202
|
+
padding: "7px 9px",
|
|
1203
|
+
border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
|
|
1204
|
+
borderRadius: 8,
|
|
1205
|
+
background: "var(--dsw-alias-bg-base)",
|
|
1206
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1207
|
+
fontFamily: "inherit",
|
|
1208
|
+
fontSize: 12,
|
|
1209
|
+
lineHeight: "18px",
|
|
1210
|
+
resize: "vertical"
|
|
1211
|
+
};
|
|
1212
|
+
const planComposerActions = {
|
|
1213
|
+
display: "flex",
|
|
1214
|
+
alignItems: "center",
|
|
1215
|
+
justifyContent: "space-between",
|
|
1216
|
+
gap: 8
|
|
1217
|
+
};
|
|
1218
|
+
const planComposerHint = {
|
|
1219
|
+
minWidth: 0,
|
|
1220
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
1221
|
+
fontSize: 11,
|
|
1222
|
+
lineHeight: "16px"
|
|
1223
|
+
};
|
|
1224
|
+
const planComposerError = {
|
|
1225
|
+
margin: 0,
|
|
1226
|
+
color: "var(--dsw-alias-state-error-primary)",
|
|
1227
|
+
fontSize: 11,
|
|
1228
|
+
lineHeight: "16px"
|
|
1229
|
+
};
|
|
1230
|
+
/** Maximize and close: the control group at the header's right end. */
|
|
1231
|
+
const planHeaderEnd = {
|
|
1232
|
+
flex: "none",
|
|
1233
|
+
display: "flex",
|
|
1234
|
+
alignItems: "center",
|
|
1235
|
+
gap: 2
|
|
1236
|
+
};
|
|
1237
|
+
const planBadge = {
|
|
1238
|
+
flex: "none",
|
|
1239
|
+
padding: "2px 8px",
|
|
1240
|
+
borderRadius: 999,
|
|
1241
|
+
background: "color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)",
|
|
1242
|
+
color: "var(--dsw-alias-state-success-primary)",
|
|
1243
|
+
fontSize: 11,
|
|
1244
|
+
lineHeight: "16px",
|
|
1245
|
+
fontWeight: 600,
|
|
1246
|
+
whiteSpace: "nowrap"
|
|
1247
|
+
};
|
|
1248
|
+
const planBadgePending = {
|
|
1249
|
+
background: "var(--dsw-alias-interactive-bg-hover)",
|
|
1250
|
+
color: "var(--dsw-alias-label-secondary)"
|
|
1251
|
+
};
|
|
1252
|
+
const planBadgeRejected = {
|
|
1253
|
+
background: "color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent)",
|
|
1254
|
+
color: "var(--dsw-alias-state-error-primary)"
|
|
1255
|
+
};
|
|
1256
|
+
/** Says where the decision is made, since this panel deliberately does not
|
|
1257
|
+
* make it: the Host's approval dialog owns the buttons. */
|
|
1258
|
+
const planHint = {
|
|
1259
|
+
margin: "0 0 12px",
|
|
1260
|
+
padding: "8px 10px",
|
|
1261
|
+
borderRadius: 8,
|
|
1262
|
+
background: "var(--dsw-alias-interactive-bg-hover)",
|
|
1263
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
1264
|
+
fontSize: 12,
|
|
1265
|
+
lineHeight: "18px"
|
|
1266
|
+
};
|
|
1099
1267
|
const tasksFinishedSection = {
|
|
1100
1268
|
marginTop: 12,
|
|
1101
1269
|
paddingTop: 8,
|
|
@@ -1669,7 +1837,7 @@ window.__ModuleLoader__.load({
|
|
|
1669
1837
|
whiteSpace: "nowrap"
|
|
1670
1838
|
};
|
|
1671
1839
|
const repositoryBarFrame = {
|
|
1672
|
-
width: "calc(100% -
|
|
1840
|
+
width: "calc(100% - 2 * var(--dsh-composer-side-clearance, 16px))",
|
|
1673
1841
|
maxWidth: "var(--dsh-composer-card-max-width, 782px)",
|
|
1674
1842
|
minWidth: 0,
|
|
1675
1843
|
margin: "0 auto",
|
|
@@ -2829,7 +2997,7 @@ window.__ModuleLoader__.load({
|
|
|
2829
2997
|
fontSize: 11
|
|
2830
2998
|
};
|
|
2831
2999
|
const reviewCommentBarFrame = {
|
|
2832
|
-
width: "calc(100% -
|
|
3000
|
+
width: "calc(100% - 2 * var(--dsh-composer-side-clearance, 16px))",
|
|
2833
3001
|
maxWidth: "var(--dsh-composer-card-max-width, 782px)",
|
|
2834
3002
|
margin: "0 auto",
|
|
2835
3003
|
boxSizing: "border-box"
|
|
@@ -2920,6 +3088,113 @@ window.__ModuleLoader__.load({
|
|
|
2920
3088
|
whiteSpace: "pre-wrap",
|
|
2921
3089
|
overflowWrap: "anywhere"
|
|
2922
3090
|
};
|
|
3091
|
+
const promptSaveTriggerClass = "dshClaudePromptSaveTrigger";
|
|
3092
|
+
const promptSpinClass = "dshClaudePromptSpin";
|
|
3093
|
+
const promptUndoClass = "dshClaudePromptUndo";
|
|
3094
|
+
const promptSaveFieldClass = "dshClaudePromptSaveField";
|
|
3095
|
+
/** The composer's own attach button geometry (InputBar.module.css `.add`:
|
|
3096
|
+
* 28x28 circle on --dsw-specific-selector, 14px glyph). The primitives'
|
|
3097
|
+
* Button has no icon-only form — its `sm` is a 36px-wide capsule around a
|
|
3098
|
+
* 16px slot, which is why this reads oversized beside its neighbours. */
|
|
3099
|
+
const promptSaveTriggerCss = `
|
|
3100
|
+
.${promptSaveTriggerClass} {
|
|
3101
|
+
width: 28px;
|
|
3102
|
+
height: 28px;
|
|
3103
|
+
flex: none;
|
|
3104
|
+
display: grid;
|
|
3105
|
+
place-items: center;
|
|
3106
|
+
padding: 0;
|
|
3107
|
+
border: none;
|
|
3108
|
+
border-radius: 999px;
|
|
3109
|
+
background: var(--dsw-specific-selector);
|
|
3110
|
+
color: var(--dsw-alias-label-primary);
|
|
3111
|
+
cursor: pointer;
|
|
3112
|
+
transition: background-color 120ms ease, color 120ms ease;
|
|
3113
|
+
}
|
|
3114
|
+
/* With no draft there is nothing to keep, so the control drops its seat
|
|
3115
|
+
* entirely rather than sitting there as a filled-but-dead circle. */
|
|
3116
|
+
.${promptSaveTriggerClass}:disabled {
|
|
3117
|
+
cursor: default;
|
|
3118
|
+
background: transparent;
|
|
3119
|
+
color: var(--dsw-alias-label-tertiary);
|
|
3120
|
+
}
|
|
3121
|
+
.${promptSaveTriggerClass}:focus-visible { outline: none; }
|
|
3122
|
+
.${promptSaveFieldClass} {
|
|
3123
|
+
box-sizing: border-box;
|
|
3124
|
+
width: 100%;
|
|
3125
|
+
height: 30px;
|
|
3126
|
+
padding: 0 9px;
|
|
3127
|
+
border: 1px solid var(--dsw-alias-border-inverted);
|
|
3128
|
+
border-radius: 7px;
|
|
3129
|
+
background: var(--dsw-alias-interactive-bg-hover);
|
|
3130
|
+
color: var(--dsw-alias-label-primary);
|
|
3131
|
+
font-family: var(--dsw-font-family);
|
|
3132
|
+
font-size: 13px;
|
|
3133
|
+
line-height: 20px;
|
|
3134
|
+
}
|
|
3135
|
+
/* Not --dsw-alias-brand-primary: in the dark theme that alias resolves to
|
|
3136
|
+
* neutral-bluish-50, so a focused field draws a near-white slab around
|
|
3137
|
+
* itself. The focus ring is a step up in the same greys the card is made of. */
|
|
3138
|
+
.${promptSaveFieldClass}:focus-visible {
|
|
3139
|
+
outline: none;
|
|
3140
|
+
border-color: var(--dsw-alias-label-tertiary);
|
|
3141
|
+
background: var(--dsw-alias-interactive-bg-active);
|
|
3142
|
+
}
|
|
3143
|
+
.${promptSaveFieldClass}:disabled { opacity: 0.6; }
|
|
3144
|
+
/* ic_ds_loading_outline_16 is a bare open ring and nothing in the primitives
|
|
3145
|
+
* turns it, so a four-second rewrite sat behind a frozen glyph that reads as
|
|
3146
|
+
* the letter C. The spin is what makes it a spinner. */
|
|
3147
|
+
.${promptSpinClass} { animation: dshClaudePromptSpin 900ms linear infinite; }
|
|
3148
|
+
@keyframes dshClaudePromptSpin { to { transform: rotate(360deg); } }
|
|
3149
|
+
/* The icon set has no undo arrow, and ic_ds_refresh_outline_16 turns
|
|
3150
|
+
* clockwise — the redo direction. Mirrored, it is the ordinary undo glyph. */
|
|
3151
|
+
.${promptUndoClass} { transform: scaleX(-1); }
|
|
3152
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3153
|
+
.${promptSpinClass} { animation: none; }
|
|
3154
|
+
}
|
|
3155
|
+
`;
|
|
3156
|
+
/** The naming card hangs off the composer tool row, portaled so the composer
|
|
3157
|
+
* card cannot clip it and positioned by useAnchoredPosition. */
|
|
3158
|
+
const promptSaveCard = {
|
|
3159
|
+
position: "fixed",
|
|
3160
|
+
zIndex: 120,
|
|
3161
|
+
boxSizing: "border-box",
|
|
3162
|
+
display: "flex",
|
|
3163
|
+
flexDirection: "column",
|
|
3164
|
+
gap: 8,
|
|
3165
|
+
width: 340,
|
|
3166
|
+
maxWidth: "calc(100vw - 32px)",
|
|
3167
|
+
padding: "10px 12px",
|
|
3168
|
+
border: "1px solid var(--dsw-alias-border-inverted)",
|
|
3169
|
+
borderRadius: 11,
|
|
3170
|
+
background: "var(--dsw-specific-menu)",
|
|
3171
|
+
boxShadow: "var(--dsw-shadow-lv3)",
|
|
3172
|
+
color: "var(--dsw-alias-label-primary)",
|
|
3173
|
+
fontSize: 12,
|
|
3174
|
+
lineHeight: "20px"
|
|
3175
|
+
};
|
|
3176
|
+
const promptSaveHeading = {
|
|
3177
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
3178
|
+
fontSize: 11,
|
|
3179
|
+
lineHeight: "16px"
|
|
3180
|
+
};
|
|
3181
|
+
const promptSaveActions = {
|
|
3182
|
+
display: "flex",
|
|
3183
|
+
justifyContent: "flex-end",
|
|
3184
|
+
gap: 6
|
|
3185
|
+
};
|
|
3186
|
+
/** The saved file, wrapped rather than truncated: a path the user cannot read
|
|
3187
|
+
* in full does not tell them where to go and edit it. */
|
|
3188
|
+
const promptSaveLocation = {
|
|
3189
|
+
color: "var(--dsw-alias-label-tertiary)",
|
|
3190
|
+
fontSize: 11,
|
|
3191
|
+
lineHeight: "16px",
|
|
3192
|
+
overflowWrap: "anywhere"
|
|
3193
|
+
};
|
|
3194
|
+
const promptSaveError = {
|
|
3195
|
+
color: "var(--dsw-static-red-450, #d64545)",
|
|
3196
|
+
overflowWrap: "anywhere"
|
|
3197
|
+
};
|
|
2923
3198
|
const diffAhead = { color: "var(--dsw-static-blue-450)" };
|
|
2924
3199
|
const diffAheadMuted = { color: "color-mix(in srgb, var(--dsw-static-blue-450) 62%, var(--dsw-alias-label-tertiary))" };
|
|
2925
3200
|
const diffAddMuted = { color: "color-mix(in srgb, var(--dsw-alias-state-success-primary) 62%, var(--dsw-alias-label-tertiary))" };
|
|
@@ -3843,6 +4118,16 @@ window.__ModuleLoader__.load({
|
|
|
3843
4118
|
laneHeld[lane] -= 1;
|
|
3844
4119
|
pump();
|
|
3845
4120
|
}
|
|
4121
|
+
/** Runs at most once, so a release can be wired to every ending of a stream
|
|
4122
|
+
* without any of them having to know about the others. */
|
|
4123
|
+
function once(action) {
|
|
4124
|
+
let done = false;
|
|
4125
|
+
return () => {
|
|
4126
|
+
if (done) return;
|
|
4127
|
+
done = true;
|
|
4128
|
+
action();
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
3846
4131
|
function acquire(lane) {
|
|
3847
4132
|
let released = false;
|
|
3848
4133
|
const releaseOnce = () => {
|
|
@@ -3946,11 +4231,53 @@ window.__ModuleLoader__.load({
|
|
|
3946
4231
|
writeId += 1;
|
|
3947
4232
|
return writeId;
|
|
3948
4233
|
}
|
|
4234
|
+
/**
|
|
4235
|
+
* A reader that gives its permit back however the stream ends.
|
|
4236
|
+
*
|
|
4237
|
+
* The permit used to be released by one event only -- the caller's abort --
|
|
4238
|
+
* and a stream has three endings, two of which the caller does not cause: the
|
|
4239
|
+
* server closing the response, and the body failing mid-read. Either one left
|
|
4240
|
+
* the permit held. For the reserved carrier that permit is a single boolean,
|
|
4241
|
+
* so one such ending closed the transcript stream for the life of the page and
|
|
4242
|
+
* every reopen answered `starved`, silently, forever.
|
|
4243
|
+
*
|
|
4244
|
+
* Binding the release to the reader rather than to the signal is what makes
|
|
4245
|
+
* that unrepresentable: a caller cannot hold the permit past the stream it is
|
|
4246
|
+
* reading, because every way of leaving the read passes through here. `free`
|
|
4247
|
+
* is idempotent, so the abort listener below stays as the belt to this brace.
|
|
4248
|
+
*/
|
|
4249
|
+
function releasingReader(reader, free) {
|
|
4250
|
+
return {
|
|
4251
|
+
get closed() {
|
|
4252
|
+
return reader.closed;
|
|
4253
|
+
},
|
|
4254
|
+
read: async () => {
|
|
4255
|
+
try {
|
|
4256
|
+
const chunk = await reader.read();
|
|
4257
|
+
if (chunk.done) free();
|
|
4258
|
+
return chunk;
|
|
4259
|
+
} catch (error) {
|
|
4260
|
+
free();
|
|
4261
|
+
throw error;
|
|
4262
|
+
}
|
|
4263
|
+
},
|
|
4264
|
+
cancel: async (reason) => {
|
|
4265
|
+
try {
|
|
4266
|
+
await reader.cancel(reason);
|
|
4267
|
+
} finally {
|
|
4268
|
+
free();
|
|
4269
|
+
}
|
|
4270
|
+
},
|
|
4271
|
+
releaseLock: () => {
|
|
4272
|
+
reader.releaseLock();
|
|
4273
|
+
}
|
|
4274
|
+
};
|
|
4275
|
+
}
|
|
3949
4276
|
async function openStream(lane, path, cancel, options, reserved) {
|
|
3950
4277
|
const url = withQuery(path, options?.query);
|
|
3951
|
-
const free = reserved ? () => {
|
|
4278
|
+
const free = reserved ? once(() => {
|
|
3952
4279
|
projectionHeld = false;
|
|
3953
|
-
} : await acquire(lane);
|
|
4280
|
+
}) : await acquire(lane);
|
|
3954
4281
|
try {
|
|
3955
4282
|
const response = await send(url, {
|
|
3956
4283
|
method: options?.method ?? "GET",
|
|
@@ -3968,7 +4295,7 @@ window.__ModuleLoader__.load({
|
|
|
3968
4295
|
throw new PluginRequestError("http", `HTTP ${response.status}`, response.status);
|
|
3969
4296
|
}
|
|
3970
4297
|
cancel.addEventListener("abort", free, { once: true });
|
|
3971
|
-
return response.body.getReader();
|
|
4298
|
+
return releasingReader(response.body.getReader(), free);
|
|
3972
4299
|
} catch (error) {
|
|
3973
4300
|
free();
|
|
3974
4301
|
throw failureOf(error, cancel);
|
|
@@ -4387,9 +4714,8 @@ window.__ModuleLoader__.load({
|
|
|
4387
4714
|
feed: applyLine,
|
|
4388
4715
|
subscribe(listener) {
|
|
4389
4716
|
if (disposed) return () => {};
|
|
4390
|
-
const wasIdle = listeners.size === 0;
|
|
4391
4717
|
listeners.add(listener);
|
|
4392
|
-
|
|
4718
|
+
onDemand(true);
|
|
4393
4719
|
return () => {
|
|
4394
4720
|
listeners.delete(listener);
|
|
4395
4721
|
if (listeners.size !== 0) return;
|
|
@@ -4405,6 +4731,13 @@ window.__ModuleLoader__.load({
|
|
|
4405
4731
|
}
|
|
4406
4732
|
};
|
|
4407
4733
|
}
|
|
4734
|
+
/** Whether two lane sets carry the same sessions. Order is the LRU's business,
|
|
4735
|
+
* not the carrier's: the server reads `sessions=` as a set. */
|
|
4736
|
+
function sameLanes(before, after) {
|
|
4737
|
+
if (before.length !== after.length) return false;
|
|
4738
|
+
const held = new Set(before);
|
|
4739
|
+
return after.every((sessionId) => held.has(sessionId));
|
|
4740
|
+
}
|
|
4408
4741
|
/**
|
|
4409
4742
|
* Every session's projection over ONE connection.
|
|
4410
4743
|
*
|
|
@@ -4427,6 +4760,10 @@ window.__ModuleLoader__.load({
|
|
|
4427
4760
|
#report;
|
|
4428
4761
|
#resyncCooldownMs;
|
|
4429
4762
|
#resyncedAt = 0;
|
|
4763
|
+
/** Whether the carrier is in a run of failures. One report per outage: the
|
|
4764
|
+
* retry loop runs every couple of seconds and a beacon per attempt would
|
|
4765
|
+
* be the plugin reporting its own noise. */
|
|
4766
|
+
#carrierFailing = false;
|
|
4430
4767
|
#controller;
|
|
4431
4768
|
#settle;
|
|
4432
4769
|
#running = false;
|
|
@@ -4477,10 +4814,12 @@ window.__ModuleLoader__.load({
|
|
|
4477
4814
|
* a session list would otherwise reopen it once per row. */
|
|
4478
4815
|
#demand(sessionId, active) {
|
|
4479
4816
|
if (this.#disposed) return;
|
|
4817
|
+
const before = this.#lanes();
|
|
4480
4818
|
if (active) {
|
|
4481
4819
|
this.#wanted.delete(sessionId);
|
|
4482
4820
|
this.#wanted.add(sessionId);
|
|
4483
4821
|
} else if (!this.#wanted.delete(sessionId)) return;
|
|
4822
|
+
if (sameLanes(before, this.#lanes())) return;
|
|
4484
4823
|
if (this.#settle !== void 0) clearTimeout(this.#settle);
|
|
4485
4824
|
const timer = setTimeout(() => {
|
|
4486
4825
|
this.#settle = void 0;
|
|
@@ -4510,6 +4849,7 @@ window.__ModuleLoader__.load({
|
|
|
4510
4849
|
let superseded = false;
|
|
4511
4850
|
try {
|
|
4512
4851
|
const reader = await this.#open(`${CLAUDE_PROJECTION_PATH}/multi?sessions=${lanes.map(encodeURIComponent).join(",")}`, controller.signal);
|
|
4852
|
+
this.#carrierFailing = false;
|
|
4513
4853
|
const stop = () => {
|
|
4514
4854
|
superseded = true;
|
|
4515
4855
|
reader.cancel().catch(() => void 0);
|
|
@@ -4532,6 +4872,10 @@ window.__ModuleLoader__.load({
|
|
|
4532
4872
|
if (this.#controller !== controller) continue;
|
|
4533
4873
|
return;
|
|
4534
4874
|
}
|
|
4875
|
+
if (!this.#carrierFailing) {
|
|
4876
|
+
this.#carrierFailing = true;
|
|
4877
|
+
this.#report("projection-carrier-unavailable", error instanceof Error ? error.message : String(error));
|
|
4878
|
+
}
|
|
4535
4879
|
} finally {
|
|
4536
4880
|
if (this.#controller === controller) this.#controller = void 0;
|
|
4537
4881
|
}
|
|
@@ -4624,10 +4968,10 @@ window.__ModuleLoader__.load({
|
|
|
4624
4968
|
".dsh-claude-act-running{animation:dsh-claude-act-pulse 1.2s ease-in-out infinite}",
|
|
4625
4969
|
"@keyframes dsh-claude-act-pulse{0%,100%{opacity:1}50%{opacity:.3}}"
|
|
4626
4970
|
].join("");
|
|
4627
|
-
let cssInjected$
|
|
4628
|
-
function ensureCss$
|
|
4629
|
-
if (cssInjected$
|
|
4630
|
-
cssInjected$
|
|
4971
|
+
let cssInjected$4 = false;
|
|
4972
|
+
function ensureCss$4() {
|
|
4973
|
+
if (cssInjected$4 || typeof document === "undefined") return;
|
|
4974
|
+
cssInjected$4 = true;
|
|
4631
4975
|
const element = document.createElement("style");
|
|
4632
4976
|
element.dataset.dshClaudeActivity = "";
|
|
4633
4977
|
element.textContent = ACTIVITY_CSS;
|
|
@@ -5055,7 +5399,7 @@ window.__ModuleLoader__.load({
|
|
|
5055
5399
|
});
|
|
5056
5400
|
}
|
|
5057
5401
|
function ClaudeActivityNode({ node, useClaudeProjection, t }) {
|
|
5058
|
-
ensureCss$
|
|
5402
|
+
ensureCss$4();
|
|
5059
5403
|
const marker = node.data;
|
|
5060
5404
|
const activities = useClaudeProjection((value) => selectStepActivities(value, marker.turn, marker.step));
|
|
5061
5405
|
const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS$1);
|
|
@@ -5521,50 +5865,551 @@ window.__ModuleLoader__.load({
|
|
|
5521
5865
|
});
|
|
5522
5866
|
}
|
|
5523
5867
|
//#endregion
|
|
5524
|
-
//#region src/
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5868
|
+
//#region src/github-url.ts
|
|
5869
|
+
/** Only GitHub's own image hosts; the browser loads these directly, so a URL
|
|
5870
|
+
* the API did not vouch for must never become an outbound request. */
|
|
5871
|
+
function githubAvatarUrl(value) {
|
|
5872
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 1024) return void 0;
|
|
5873
|
+
try {
|
|
5874
|
+
const url = new URL(value);
|
|
5875
|
+
const allowed = url.hostname === "github.com" || url.hostname === "githubusercontent.com" || url.hostname.endsWith(".githubusercontent.com");
|
|
5876
|
+
return url.protocol === "https:" && allowed ? url.href : void 0;
|
|
5877
|
+
} catch {
|
|
5878
|
+
return;
|
|
5531
5879
|
}
|
|
5532
|
-
}
|
|
5880
|
+
}
|
|
5881
|
+
//#endregion
|
|
5882
|
+
//#region src/client/pr-feedback-api.ts
|
|
5533
5883
|
function record$5(value) {
|
|
5534
5884
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
5535
5885
|
}
|
|
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);
|
|
5886
|
+
function feedbackQuery(sessionId, pullNumber, extra) {
|
|
5887
|
+
return {
|
|
5888
|
+
sessionId,
|
|
5889
|
+
number: String(pullNumber),
|
|
5890
|
+
...extra
|
|
5891
|
+
};
|
|
5552
5892
|
}
|
|
5553
|
-
function
|
|
5893
|
+
function answer(value) {
|
|
5554
5894
|
const body = record$5(value);
|
|
5555
|
-
if (body === void 0) throw new
|
|
5895
|
+
if (body === void 0) throw new Error("Invalid pull request feedback response.");
|
|
5556
5896
|
return body;
|
|
5557
5897
|
}
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
return
|
|
5898
|
+
/** Every arm of this route shells out to `gh`, so reads and writes alike take
|
|
5899
|
+
* the remote budget. */
|
|
5900
|
+
async function loadJson(path, sessionId, pullNumber, signal, extra) {
|
|
5901
|
+
return answer(await pluginRead(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", signal, { query: feedbackQuery(sessionId, pullNumber, extra) }));
|
|
5562
5902
|
}
|
|
5563
|
-
async function
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5903
|
+
async function postJson(path, sessionId, pullNumber, input) {
|
|
5904
|
+
return answer(await pluginWrite(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}`, "remote", void 0, {
|
|
5905
|
+
query: feedbackQuery(sessionId, pullNumber),
|
|
5906
|
+
json: input
|
|
5907
|
+
}));
|
|
5908
|
+
}
|
|
5909
|
+
function reviewComment(value) {
|
|
5910
|
+
const input = record$5(value);
|
|
5911
|
+
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;
|
|
5912
|
+
return input;
|
|
5913
|
+
}
|
|
5914
|
+
async function loadPullRequestThreads(sessionId, pullNumber, signal) {
|
|
5915
|
+
const body = await loadJson("/comments", sessionId, pullNumber, signal);
|
|
5916
|
+
if (!Array.isArray(body.threads)) throw new Error("Invalid pull request comments response.");
|
|
5917
|
+
const threads = [];
|
|
5918
|
+
for (const item of body.threads) {
|
|
5919
|
+
const input = record$5(item);
|
|
5920
|
+
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;
|
|
5921
|
+
const comments = input.comments.map(reviewComment).filter((value) => value !== void 0);
|
|
5922
|
+
if (comments.length === 0) continue;
|
|
5923
|
+
threads.push({
|
|
5924
|
+
id: input.id,
|
|
5925
|
+
path: input.path,
|
|
5926
|
+
...typeof input.line === "number" ? { line: input.line } : {},
|
|
5927
|
+
side: input.side,
|
|
5928
|
+
resolved: input.resolved === true,
|
|
5929
|
+
outdated: input.outdated === true,
|
|
5930
|
+
comments
|
|
5931
|
+
});
|
|
5932
|
+
}
|
|
5933
|
+
return threads;
|
|
5934
|
+
}
|
|
5935
|
+
/** Post one reply into the thread that `commentId` belongs to. */
|
|
5936
|
+
async function replyToReviewThread(sessionId, pullNumber, commentId, body) {
|
|
5937
|
+
const comment = reviewComment((await postJson("/reply", sessionId, pullNumber, {
|
|
5938
|
+
commentId,
|
|
5939
|
+
body
|
|
5940
|
+
})).comment);
|
|
5941
|
+
if (comment === void 0) throw new Error("Invalid pull request reply response.");
|
|
5942
|
+
return comment;
|
|
5943
|
+
}
|
|
5944
|
+
/** Resolve or reopen a thread; returns the state GitHub reports afterwards. */
|
|
5945
|
+
async function setReviewThreadResolved(sessionId, pullNumber, threadId, resolved) {
|
|
5946
|
+
const answer = await postJson("/resolve", sessionId, pullNumber, {
|
|
5947
|
+
threadId,
|
|
5948
|
+
resolved
|
|
5949
|
+
});
|
|
5950
|
+
if (typeof answer.resolved !== "boolean") throw new Error("Invalid pull request resolve response.");
|
|
5951
|
+
return answer.resolved;
|
|
5952
|
+
}
|
|
5953
|
+
/** Logins GitHub would notify, for the reply composer's `@` completion. */
|
|
5954
|
+
async function loadMentionableUsers(sessionId, pullNumber, query, signal) {
|
|
5955
|
+
const body = await loadJson("/mentionables", sessionId, pullNumber, signal, { q: query });
|
|
5956
|
+
if (!Array.isArray(body.users)) return [];
|
|
5957
|
+
const users = [];
|
|
5958
|
+
for (const item of body.users) {
|
|
5959
|
+
const input = record$5(item);
|
|
5960
|
+
if (input === void 0 || typeof input.login !== "string" || input.login.length === 0) continue;
|
|
5961
|
+
const avatarUrl = githubAvatarUrl(input.avatarUrl);
|
|
5962
|
+
users.push({
|
|
5963
|
+
login: input.login,
|
|
5964
|
+
...avatarUrl === void 0 ? {} : { avatarUrl }
|
|
5965
|
+
});
|
|
5966
|
+
}
|
|
5967
|
+
return users;
|
|
5968
|
+
}
|
|
5969
|
+
async function loadFailingChecks(sessionId, pullNumber, signal) {
|
|
5970
|
+
const body = await loadJson("/checks", sessionId, pullNumber, signal);
|
|
5971
|
+
if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
|
|
5972
|
+
const checks = [];
|
|
5973
|
+
for (const item of body.checks) {
|
|
5974
|
+
const input = record$5(item);
|
|
5975
|
+
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;
|
|
5976
|
+
checks.push(input);
|
|
5977
|
+
}
|
|
5978
|
+
return checks;
|
|
5979
|
+
}
|
|
5980
|
+
/** Draft handed to Claude when the user forwards GitHub review comments. A
|
|
5981
|
+
* resolved thread is a settled conversation: forwarding it would ask Claude to
|
|
5982
|
+
* redo work the reviewers already signed off. */
|
|
5983
|
+
function composeCommentsPrompt(threads) {
|
|
5984
|
+
const open = threads.filter((thread) => !thread.resolved);
|
|
5985
|
+
if (open.length === 0) return "";
|
|
5986
|
+
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) => {
|
|
5987
|
+
const [first, ...rest] = thread.comments;
|
|
5988
|
+
if (first === void 0) return "";
|
|
5989
|
+
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");
|
|
5990
|
+
}).filter((block) => block.length > 0).join("\n")}`;
|
|
5991
|
+
}
|
|
5992
|
+
/** Draft handed to Claude when the user forwards failing CI checks. */
|
|
5993
|
+
function composeChecksPrompt(checks) {
|
|
5994
|
+
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) => {
|
|
5995
|
+
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\`\`\``}`;
|
|
5996
|
+
}).join("\n\n")}`;
|
|
5997
|
+
}
|
|
5998
|
+
/** Draft handed to Claude after an update-branch merge left conflicts behind. */
|
|
5999
|
+
function composeConflictsPrompt(baseBranch, conflicts, method = "merge") {
|
|
6000
|
+
const list = conflicts.map((file) => `- ${file}`).join("\n");
|
|
6001
|
+
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}`;
|
|
6002
|
+
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}`;
|
|
6003
|
+
}
|
|
6004
|
+
//#endregion
|
|
6005
|
+
//#region src/client/auto-fix.ts
|
|
6006
|
+
const AUTO_FIX_INTERVAL_MS = 3e4;
|
|
6007
|
+
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.";
|
|
6008
|
+
const EMPTY_MEMORY = { handledCommentIds: /* @__PURE__ */ new Set() };
|
|
6009
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
6010
|
+
function session(sessionId) {
|
|
6011
|
+
let entry = sessions.get(sessionId);
|
|
6012
|
+
if (entry === void 0) {
|
|
6013
|
+
entry = {
|
|
6014
|
+
enabled: false,
|
|
6015
|
+
memory: EMPTY_MEMORY
|
|
6016
|
+
};
|
|
6017
|
+
sessions.set(sessionId, entry);
|
|
6018
|
+
}
|
|
6019
|
+
return entry;
|
|
6020
|
+
}
|
|
6021
|
+
function autoFixEnabled(sessionId) {
|
|
6022
|
+
return session(sessionId).enabled;
|
|
6023
|
+
}
|
|
6024
|
+
function setAutoFixEnabled(sessionId, enabled) {
|
|
6025
|
+
session(sessionId).enabled = enabled;
|
|
6026
|
+
}
|
|
6027
|
+
function autoFixMemory(sessionId) {
|
|
6028
|
+
return session(sessionId).memory;
|
|
6029
|
+
}
|
|
6030
|
+
function rememberAutoFix(sessionId, memory) {
|
|
6031
|
+
session(sessionId).memory = memory;
|
|
6032
|
+
}
|
|
6033
|
+
/** One failing CI run yields one fix attempt: run links change when CI re-runs. */
|
|
6034
|
+
function checksSignature(checks) {
|
|
6035
|
+
if (checks.length === 0) return void 0;
|
|
6036
|
+
return checks.map((check) => `${check.name}|${check.link ?? ""}`).sort().join("\n");
|
|
6037
|
+
}
|
|
6038
|
+
function planAutoFix(memory, threads, checks) {
|
|
6039
|
+
const unhandled = threads.filter((thread) => !thread.resolved).filter((thread) => thread.comments.some((comment) => !memory.handledCommentIds.has(comment.id)));
|
|
6040
|
+
const fresh = unhandled.flatMap((thread) => thread.comments);
|
|
6041
|
+
const signature = checksSignature(checks);
|
|
6042
|
+
const checksChanged = signature !== void 0 && signature !== memory.handledChecksSignature;
|
|
6043
|
+
const sections = [];
|
|
6044
|
+
if (unhandled.length > 0) sections.push(composeCommentsPrompt(unhandled));
|
|
6045
|
+
if (checksChanged) sections.push(composeChecksPrompt(checks));
|
|
6046
|
+
if (sections.length === 0) return { memory };
|
|
6047
|
+
const nextSignature = checksChanged ? signature : memory.handledChecksSignature;
|
|
6048
|
+
return {
|
|
6049
|
+
prompt: `${sections.join("\n\n")}\n\n${AUTO_FIX_FOOTER}`,
|
|
6050
|
+
memory: {
|
|
6051
|
+
handledCommentIds: /* @__PURE__ */ new Set([...memory.handledCommentIds, ...fresh.map((comment) => comment.id)]),
|
|
6052
|
+
...nextSignature === void 0 ? {} : { handledChecksSignature: nextSignature }
|
|
6053
|
+
}
|
|
6054
|
+
};
|
|
6055
|
+
}
|
|
6056
|
+
//#endregion
|
|
6057
|
+
//#region src/client/session-preset.ts
|
|
6058
|
+
/**
|
|
6059
|
+
* Resolve one row's preset id, newest seat first.
|
|
6060
|
+
* @param row - a session-list row, or undefined when the id is not listed.
|
|
6061
|
+
* @returns the preset id, or undefined when neither source carries one.
|
|
6062
|
+
*/
|
|
6063
|
+
function sessionRowPreset(row) {
|
|
6064
|
+
return row?.agentPreset ?? row?.projectionValues?.agentPreset ?? void 0;
|
|
6065
|
+
}
|
|
6066
|
+
//#endregion
|
|
6067
|
+
//#region src/client/ClaudePullRequestsPanel.tsx
|
|
6068
|
+
const NO_WORKSPACE_STATE = {};
|
|
6069
|
+
const NO_WORKSPACES = {
|
|
6070
|
+
subscribe: () => () => {},
|
|
6071
|
+
getSnapshot: () => NO_WORKSPACE_STATE
|
|
6072
|
+
};
|
|
6073
|
+
const OVERVIEW_REFRESH_MS = 3e4;
|
|
6074
|
+
/** What a running session is blocked on: the latest permission or question
|
|
6075
|
+
* activity that is still in its started phase. */
|
|
6076
|
+
function overviewAttention(activities) {
|
|
6077
|
+
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
6078
|
+
const activity = activities[index];
|
|
6079
|
+
if (activity === void 0 || activity.kind !== "permission" && activity.kind !== "question") continue;
|
|
6080
|
+
return activity.phase === "started" ? activity.kind : void 0;
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
/** Claude sessions worth listing: rows still in the host list (byId keeps
|
|
6084
|
+
* deleted and breadcrumb rows), non-blank, non-subagent, with a checkout;
|
|
6085
|
+
* running first. */
|
|
6086
|
+
function claudeSessionRows(state, archivedSessionIds = []) {
|
|
6087
|
+
const rows = state.ids === void 0 ? Object.values(state.byId) : state.ids.map((id) => state.byId[id]);
|
|
6088
|
+
const archived = new Set(archivedSessionIds);
|
|
6089
|
+
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));
|
|
6090
|
+
}
|
|
6091
|
+
function Badge({ label, tone = "neutral" }) {
|
|
6092
|
+
const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
|
|
6093
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
6094
|
+
style: {
|
|
6095
|
+
...repositoryItem,
|
|
6096
|
+
...toneStyle
|
|
6097
|
+
},
|
|
6098
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
6099
|
+
style: repositoryItemDot,
|
|
6100
|
+
"aria-hidden": "true"
|
|
6101
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
6102
|
+
style: repositoryItemLabel,
|
|
6103
|
+
children: label
|
|
6104
|
+
})]
|
|
6105
|
+
});
|
|
6106
|
+
}
|
|
6107
|
+
function repositoryName$1(remote) {
|
|
6108
|
+
return remote?.split("/").at(-1);
|
|
6109
|
+
}
|
|
6110
|
+
function OverviewAttention({ source, running, t }) {
|
|
6111
|
+
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
6112
|
+
const attention = running ? overviewAttention(snapshot.activities) : void 0;
|
|
6113
|
+
const usage = snapshot.contextUsage;
|
|
6114
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
6115
|
+
attention === "permission" ? /* @__PURE__ */ jsx(Badge, {
|
|
6116
|
+
label: t("overviewNeedsPermission"),
|
|
6117
|
+
tone: "warning"
|
|
6118
|
+
}) : null,
|
|
6119
|
+
attention === "question" ? /* @__PURE__ */ jsx(Badge, {
|
|
6120
|
+
label: t("overviewNeedsAnswer"),
|
|
6121
|
+
tone: "warning"
|
|
6122
|
+
}) : null,
|
|
6123
|
+
usage === void 0 ? null : /* @__PURE__ */ jsx("span", { children: t("overviewContextUsage", { percentage: usage.percentage }) })
|
|
6124
|
+
] });
|
|
6125
|
+
}
|
|
6126
|
+
function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions, workspaces, projectionFor }) {
|
|
6127
|
+
const sessionStore = useMemo(() => ({
|
|
6128
|
+
subscribe: (listener) => sessions.subscribe(listener),
|
|
6129
|
+
getSnapshot: () => sessions.getSnapshot()
|
|
6130
|
+
}), [sessions]);
|
|
6131
|
+
const snapshot = useSyncExternalStore(sessionStore.subscribe, sessionStore.getSnapshot, sessionStore.getSnapshot);
|
|
6132
|
+
const workspaceStore = useMemo(() => {
|
|
6133
|
+
const source = workspaces ?? NO_WORKSPACES;
|
|
6134
|
+
return {
|
|
6135
|
+
subscribe: (listener) => source.subscribe(listener),
|
|
6136
|
+
getSnapshot: () => source.getSnapshot()
|
|
6137
|
+
};
|
|
6138
|
+
}, [workspaces]);
|
|
6139
|
+
const workspaceState = useSyncExternalStore(workspaceStore.subscribe, workspaceStore.getSnapshot, workspaceStore.getSnapshot);
|
|
6140
|
+
const rows = useMemo(() => claudeSessionRows(snapshot, workspaceState.archivedSessionIds ?? []), [snapshot, workspaceState]);
|
|
6141
|
+
const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
|
|
6142
|
+
const [statuses, setStatuses] = useState({});
|
|
6143
|
+
useEffect(() => {
|
|
6144
|
+
const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
|
|
6145
|
+
if (cwds.length === 0) return;
|
|
6146
|
+
const controller = new AbortController();
|
|
6147
|
+
const refresh = () => {
|
|
6148
|
+
for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
|
|
6149
|
+
if (!controller.signal.aborted) setStatuses((previous) => ({
|
|
6150
|
+
...previous,
|
|
6151
|
+
[cwd]: status
|
|
6152
|
+
}));
|
|
6153
|
+
}, () => void 0);
|
|
6154
|
+
};
|
|
6155
|
+
refresh();
|
|
6156
|
+
const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
|
|
6157
|
+
return () => {
|
|
6158
|
+
controller.abort();
|
|
6159
|
+
clearInterval(timer);
|
|
6160
|
+
};
|
|
6161
|
+
}, [cwdKey, loadStatus]);
|
|
6162
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
6163
|
+
className: detailsCardClass,
|
|
6164
|
+
style: tasksPanel,
|
|
6165
|
+
children: [
|
|
6166
|
+
/* @__PURE__ */ jsxs("style", {
|
|
6167
|
+
"data-dsh-claude-overview-styles": true,
|
|
6168
|
+
children: [detailsCardCss, panelIconButtonCss]
|
|
6169
|
+
}),
|
|
6170
|
+
/* @__PURE__ */ jsxs("header", {
|
|
6171
|
+
style: tasksHeader,
|
|
6172
|
+
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
6173
|
+
style: tasksHeading,
|
|
6174
|
+
children: t("overviewTitle")
|
|
6175
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
6176
|
+
style: tasksTurnMeta,
|
|
6177
|
+
children: t("overviewBody")
|
|
6178
|
+
})] }), /* @__PURE__ */ jsx("button", {
|
|
6179
|
+
type: "button",
|
|
6180
|
+
className: panelIconButtonClass,
|
|
6181
|
+
"aria-label": t("diffClose"),
|
|
6182
|
+
onClick: closeDetails,
|
|
6183
|
+
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
6184
|
+
})]
|
|
6185
|
+
}),
|
|
6186
|
+
/* @__PURE__ */ jsx("div", {
|
|
6187
|
+
style: overviewBody,
|
|
6188
|
+
children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
6189
|
+
style: overviewEmpty,
|
|
6190
|
+
children: t("overviewEmpty")
|
|
6191
|
+
}) : rows.map((row) => {
|
|
6192
|
+
const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
|
|
6193
|
+
const pullRequest = repository?.pullRequest;
|
|
6194
|
+
const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
|
|
6195
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
6196
|
+
type: "button",
|
|
6197
|
+
style: overviewRow,
|
|
6198
|
+
onClick: () => {
|
|
6199
|
+
openSession(row.id);
|
|
6200
|
+
},
|
|
6201
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
6202
|
+
style: overviewRowTop,
|
|
6203
|
+
children: [
|
|
6204
|
+
row.running === true ? /* @__PURE__ */ jsx("span", {
|
|
6205
|
+
style: overviewRunningDot,
|
|
6206
|
+
"aria-label": t("overviewRunning")
|
|
6207
|
+
}) : null,
|
|
6208
|
+
/* @__PURE__ */ jsx("span", {
|
|
6209
|
+
style: overviewTitle,
|
|
6210
|
+
children: row.displayTitle ?? row.id
|
|
6211
|
+
}),
|
|
6212
|
+
pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
|
|
6213
|
+
label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
|
|
6214
|
+
tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
|
|
6215
|
+
})
|
|
6216
|
+
]
|
|
6217
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
6218
|
+
style: overviewMeta,
|
|
6219
|
+
children: [
|
|
6220
|
+
repositoryName$1(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName$1(repository?.remote) }),
|
|
6221
|
+
/* @__PURE__ */ jsx("span", {
|
|
6222
|
+
style: overviewBranch,
|
|
6223
|
+
children: branch
|
|
6224
|
+
}),
|
|
6225
|
+
pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
6226
|
+
label: t(`repositoryChecks_${pullRequest.checks}`),
|
|
6227
|
+
tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
|
|
6228
|
+
}) : null,
|
|
6229
|
+
pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
6230
|
+
label: t(`repositoryReview_${pullRequest.review}`),
|
|
6231
|
+
tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
|
|
6232
|
+
}) : null,
|
|
6233
|
+
autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
|
|
6234
|
+
label: t("overviewAutoFix"),
|
|
6235
|
+
tone: "success"
|
|
6236
|
+
}) : null,
|
|
6237
|
+
projectionFor === void 0 ? null : /* @__PURE__ */ jsx(OverviewAttention, {
|
|
6238
|
+
source: projectionFor(row.id),
|
|
6239
|
+
running: row.running === true,
|
|
6240
|
+
t
|
|
6241
|
+
})
|
|
6242
|
+
]
|
|
6243
|
+
})]
|
|
6244
|
+
}, row.id);
|
|
6245
|
+
})
|
|
6246
|
+
})
|
|
6247
|
+
]
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
//#endregion
|
|
6251
|
+
//#region src/client/session-alerts.ts
|
|
6252
|
+
/** Desktop notifications for the sessions the user is not looking at.
|
|
6253
|
+
*
|
|
6254
|
+
* The session board already knows which session is blocked on an approval or
|
|
6255
|
+
* a question and which has gone quiet — but only while it is open, which
|
|
6256
|
+
* makes the user the poller. Running several worktree sessions at once is the
|
|
6257
|
+
* workflow this plugin is built for, and it is the one thing that gets worse
|
|
6258
|
+
* the more of them there are.
|
|
6259
|
+
*
|
|
6260
|
+
* Everything here is derived from the two feeds the board already reads, so a
|
|
6261
|
+
* standing watcher costs one more subscriber on the shared projection
|
|
6262
|
+
* carrier rather than a stream per session.
|
|
6263
|
+
*/
|
|
6264
|
+
/** ponytail: module state, like the auto-fix toggle next door. The Settings
|
|
6265
|
+
* panel and the boot read both write it, and the watcher reads it per alert,
|
|
6266
|
+
* so switching alerts off lands without a reload. */
|
|
6267
|
+
let alertsEnabled = true;
|
|
6268
|
+
function claudeAlertsEnabled() {
|
|
6269
|
+
return alertsEnabled;
|
|
6270
|
+
}
|
|
6271
|
+
function setClaudeAlertsEnabled(enabled) {
|
|
6272
|
+
alertsEnabled = enabled;
|
|
6273
|
+
}
|
|
6274
|
+
/** The alert one observation earns, or undefined when nothing happened that is
|
|
6275
|
+
* worth interrupting the user for.
|
|
6276
|
+
*
|
|
6277
|
+
* A session observed for the first time earns nothing: the watcher starts
|
|
6278
|
+
* with every session unknown, and announcing the state each one merely
|
|
6279
|
+
* happens to be in would greet a restart with a burst. */
|
|
6280
|
+
function sessionAlert(previous, next) {
|
|
6281
|
+
if (previous === void 0) return void 0;
|
|
6282
|
+
if (next.attention !== void 0 && next.attention !== previous.attention) return next.attention;
|
|
6283
|
+
if (previous.running && !next.running) return "idle";
|
|
6284
|
+
}
|
|
6285
|
+
/** Deliver one alert as a desktop notification.
|
|
6286
|
+
*
|
|
6287
|
+
* Best effort throughout: a Host without the Notification API, or a user who
|
|
6288
|
+
* has refused permission, simply gets no alerts. The tag collapses repeat
|
|
6289
|
+
* alerts for one session into a single banner rather than a stack. */
|
|
6290
|
+
function postSessionAlert(alert) {
|
|
6291
|
+
if (typeof Notification === "undefined" || Notification.permission === "denied") return;
|
|
6292
|
+
const show = () => {
|
|
6293
|
+
try {
|
|
6294
|
+
const notification = new Notification(alert.title, {
|
|
6295
|
+
body: alert.body,
|
|
6296
|
+
tag: `dsh-claude:${alert.sessionId}`
|
|
6297
|
+
});
|
|
6298
|
+
notification.onclick = () => {
|
|
6299
|
+
globalThis.focus?.();
|
|
6300
|
+
alert.open();
|
|
6301
|
+
};
|
|
6302
|
+
} catch {}
|
|
6303
|
+
};
|
|
6304
|
+
if (Notification.permission === "granted") show();
|
|
6305
|
+
else Notification.requestPermission().then((result) => {
|
|
6306
|
+
if (result === "granted") show();
|
|
6307
|
+
}, () => void 0);
|
|
6308
|
+
}
|
|
6309
|
+
const BODY_KEY = {
|
|
6310
|
+
permission: "alertNeedsPermission",
|
|
6311
|
+
question: "alertNeedsAnswer",
|
|
6312
|
+
idle: "alertTurnFinished"
|
|
6313
|
+
};
|
|
6314
|
+
/** Watch every Claude session and announce the ones that need the user.
|
|
6315
|
+
* Returns the unsubscriber. */
|
|
6316
|
+
function startClaudeSessionAlerts(deps) {
|
|
6317
|
+
const post = deps.post ?? postSessionAlert;
|
|
6318
|
+
const known = /* @__PURE__ */ new Map();
|
|
6319
|
+
const projections = /* @__PURE__ */ new Map();
|
|
6320
|
+
let disposed = false;
|
|
6321
|
+
const announce = (row, kind) => {
|
|
6322
|
+
if (!(deps.enabled ?? claudeAlertsEnabled)()) return;
|
|
6323
|
+
post({
|
|
6324
|
+
sessionId: row.id,
|
|
6325
|
+
title: row.displayTitle ?? deps.t("alertFallbackTitle"),
|
|
6326
|
+
body: deps.t(BODY_KEY[kind]),
|
|
6327
|
+
open: () => {
|
|
6328
|
+
deps.open(row.id);
|
|
6329
|
+
}
|
|
6330
|
+
});
|
|
6331
|
+
};
|
|
6332
|
+
const evaluate = () => {
|
|
6333
|
+
if (disposed) return;
|
|
6334
|
+
const snapshot = deps.sessions.getSnapshot();
|
|
6335
|
+
const rows = claudeSessionRows(snapshot);
|
|
6336
|
+
const live = new Set(rows.map((row) => row.id));
|
|
6337
|
+
for (const [sessionId, unsubscribe] of [...projections]) {
|
|
6338
|
+
if (live.has(sessionId)) continue;
|
|
6339
|
+
unsubscribe();
|
|
6340
|
+
projections.delete(sessionId);
|
|
6341
|
+
known.delete(sessionId);
|
|
6342
|
+
}
|
|
6343
|
+
for (const row of rows) {
|
|
6344
|
+
const source = deps.projectionFor(row.id);
|
|
6345
|
+
if (!projections.has(row.id)) projections.set(row.id, source.subscribe(evaluate));
|
|
6346
|
+
const running = row.running === true;
|
|
6347
|
+
const next = {
|
|
6348
|
+
running,
|
|
6349
|
+
attention: running ? overviewAttention(source.getSnapshot().activities) : void 0
|
|
6350
|
+
};
|
|
6351
|
+
const previous = known.get(row.id);
|
|
6352
|
+
known.set(row.id, next);
|
|
6353
|
+
if (row.id === snapshot.current) continue;
|
|
6354
|
+
const kind = sessionAlert(previous, next);
|
|
6355
|
+
if (kind !== void 0) announce(row, kind);
|
|
6356
|
+
}
|
|
6357
|
+
};
|
|
6358
|
+
const unsubscribe = deps.sessions.subscribe(evaluate);
|
|
6359
|
+
evaluate();
|
|
6360
|
+
return () => {
|
|
6361
|
+
disposed = true;
|
|
6362
|
+
unsubscribe();
|
|
6363
|
+
for (const dispose of projections.values()) dispose();
|
|
6364
|
+
projections.clear();
|
|
6365
|
+
known.clear();
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
//#endregion
|
|
6369
|
+
//#region src/client/jira-api.ts
|
|
6370
|
+
var JiraClientError = class extends Error {
|
|
6371
|
+
code;
|
|
6372
|
+
constructor(message, code) {
|
|
6373
|
+
super(message);
|
|
6374
|
+
this.name = "JiraClientError";
|
|
6375
|
+
if (code !== void 0) this.code = code;
|
|
6376
|
+
}
|
|
6377
|
+
};
|
|
6378
|
+
function record$4(value) {
|
|
6379
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6380
|
+
}
|
|
6381
|
+
/**
|
|
6382
|
+
* Every Jira failure the panels catch is a `JiraClientError`, whatever the
|
|
6383
|
+
* transport threw: `ClaudeHeroRepositoryControls` branches on `code` to tell
|
|
6384
|
+
* 'not-connected' apart from a real outage, and the settings card renders the
|
|
6385
|
+
* message verbatim.
|
|
6386
|
+
*
|
|
6387
|
+
* The routes answer `{ error, message }`, so `message` is already the sentence
|
|
6388
|
+
* to show. A body carrying only a code — a 405, a bad JSON body — used to read
|
|
6389
|
+
* 'Jira is unavailable.' rather than leaking the code as prose, and it still
|
|
6390
|
+
* does. Transport failures (a starved pool, an elapsed budget, an older Host
|
|
6391
|
+
* without the route) carry their own wording and keep it.
|
|
6392
|
+
*/
|
|
6393
|
+
function jiraFailure(cause) {
|
|
6394
|
+
if (cause instanceof JiraClientError) return cause;
|
|
6395
|
+
if (!(cause instanceof PluginRequestError)) return new JiraClientError(cause instanceof Error ? cause.message : String(cause));
|
|
6396
|
+
return new JiraClientError(cause.message === cause.code ? "Jira is unavailable." : cause.message, cause.code);
|
|
6397
|
+
}
|
|
6398
|
+
function payload(value) {
|
|
6399
|
+
const body = record$4(value);
|
|
6400
|
+
if (body === void 0) throw new JiraClientError("Invalid Jira response.");
|
|
6401
|
+
return body;
|
|
6402
|
+
}
|
|
6403
|
+
function status(value) {
|
|
6404
|
+
const body = payload(value);
|
|
6405
|
+
if (typeof body.connected !== "boolean") throw new JiraClientError("Invalid Jira status response.");
|
|
6406
|
+
return body;
|
|
6407
|
+
}
|
|
6408
|
+
async function loadJiraStatus(signal) {
|
|
6409
|
+
try {
|
|
6410
|
+
return status(await pluginRead(`${CLAUDE_JIRA_PATH}/status`, "remote", signal));
|
|
6411
|
+
} catch (cause) {
|
|
6412
|
+
throw jiraFailure(cause);
|
|
5568
6413
|
}
|
|
5569
6414
|
}
|
|
5570
6415
|
async function connectJira(input) {
|
|
@@ -5587,7 +6432,7 @@ window.__ModuleLoader__.load({
|
|
|
5587
6432
|
if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
|
|
5588
6433
|
const tickets = [];
|
|
5589
6434
|
for (const item of body.tickets) {
|
|
5590
|
-
const ticket = record$
|
|
6435
|
+
const ticket = record$4(item);
|
|
5591
6436
|
if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
|
|
5592
6437
|
tickets.push(ticket);
|
|
5593
6438
|
}
|
|
@@ -5835,6 +6680,12 @@ window.__ModuleLoader__.load({
|
|
|
5835
6680
|
const value = settings.find((setting) => setting.key === "prose")?.value;
|
|
5836
6681
|
return isClaudeProseMode(value) ? value : DEFAULT_CLAUDE_PROSE_MODE;
|
|
5837
6682
|
}
|
|
6683
|
+
/** The alert mode a settings payload carries, read the same way and for the
|
|
6684
|
+
* same reason as {@link proseModeOf}. */
|
|
6685
|
+
function alertModeOf(settings) {
|
|
6686
|
+
const value = settings.find((setting) => setting.key === "alerts")?.value;
|
|
6687
|
+
return isClaudeAlertMode(value) ? value : "on";
|
|
6688
|
+
}
|
|
5838
6689
|
/** Settings whose row only makes sense under a particular value of another.
|
|
5839
6690
|
* Filtering here rather than server-side keeps the descriptor list flat: the
|
|
5840
6691
|
* server has no view of what the Client can paint. Fails OPEN — a payload
|
|
@@ -5860,6 +6711,10 @@ window.__ModuleLoader__.load({
|
|
|
5860
6711
|
label: "prose",
|
|
5861
6712
|
hint: "proseEffect"
|
|
5862
6713
|
},
|
|
6714
|
+
alerts: {
|
|
6715
|
+
label: "alerts",
|
|
6716
|
+
hint: "alertsEffect"
|
|
6717
|
+
},
|
|
5863
6718
|
worktreeBranchPrefix: {
|
|
5864
6719
|
label: "worktreeBranchPrefix",
|
|
5865
6720
|
hint: "worktreeBranchPrefixEffect"
|
|
@@ -5880,7 +6735,9 @@ window.__ModuleLoader__.load({
|
|
|
5880
6735
|
"renderer:plugin": "rendererPlugin",
|
|
5881
6736
|
"renderer:native": "rendererNative",
|
|
5882
6737
|
"prose:plain": "prosePlain",
|
|
5883
|
-
"prose:enhanced": "proseEnhanced"
|
|
6738
|
+
"prose:enhanced": "proseEnhanced",
|
|
6739
|
+
"alerts:off": "alertsOff",
|
|
6740
|
+
"alerts:on": "alertsOn"
|
|
5884
6741
|
};
|
|
5885
6742
|
function settingOptionLabel(settingKey, option, t) {
|
|
5886
6743
|
const key = SETTING_OPTION_COPY[`${settingKey}:${option.value}`];
|
|
@@ -6120,6 +6977,7 @@ window.__ModuleLoader__.load({
|
|
|
6120
6977
|
if (!isGlobalSettingsView(payload)) throw new Error("Invalid global settings response");
|
|
6121
6978
|
setGlobalSettings(payload);
|
|
6122
6979
|
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
6980
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
6123
6981
|
} catch (cause) {
|
|
6124
6982
|
setGlobalSettingsError(cardFailure(cause));
|
|
6125
6983
|
} finally {
|
|
@@ -6467,6 +7325,531 @@ window.__ModuleLoader__.load({
|
|
|
6467
7325
|
});
|
|
6468
7326
|
}
|
|
6469
7327
|
//#endregion
|
|
7328
|
+
//#region src/client/plan-feedback-api.ts
|
|
7329
|
+
/** Send one plan back for changes. */
|
|
7330
|
+
async function sendPlanForChanges(sessionId, toolUseId, notes) {
|
|
7331
|
+
try {
|
|
7332
|
+
await pluginWrite(CLAUDE_PLAN_FEEDBACK_PATH, "fast", void 0, {
|
|
7333
|
+
query: { sessionId },
|
|
7334
|
+
json: {
|
|
7335
|
+
toolUseId,
|
|
7336
|
+
notes
|
|
7337
|
+
}
|
|
7338
|
+
});
|
|
7339
|
+
} catch (error) {
|
|
7340
|
+
const settled = error instanceof PluginRequestError && error.reason === "http" && error.status === 409;
|
|
7341
|
+
throw new Error(settled ? "planSettled" : "planFeedbackFailed");
|
|
7342
|
+
}
|
|
7343
|
+
}
|
|
7344
|
+
//#endregion
|
|
7345
|
+
//#region src/client/ClaudePlanPanel.tsx
|
|
7346
|
+
const PLAN_TOOL = "ExitPlanMode";
|
|
7347
|
+
/** Every plan this session handed over, oldest first, each with where its
|
|
7348
|
+
* approval stands.
|
|
7349
|
+
*
|
|
7350
|
+
* The permission bridge writes one `started` record carrying the plan and,
|
|
7351
|
+
* once the user decides, a second record under the same `toolUseId` whose
|
|
7352
|
+
* phase says which way it went. Both arrive on the ordinary activity stream,
|
|
7353
|
+
* so the panel needs no channel of its own — it reads the transcript the
|
|
7354
|
+
* session already has. */
|
|
7355
|
+
function planReviews(activities) {
|
|
7356
|
+
const plans = /* @__PURE__ */ new Map();
|
|
7357
|
+
const states = /* @__PURE__ */ new Map();
|
|
7358
|
+
for (const activity of activities) {
|
|
7359
|
+
const { toolUseId } = activity;
|
|
7360
|
+
if (activity.kind !== "permission" || toolUseId === void 0) continue;
|
|
7361
|
+
if (activity.toolName === PLAN_TOOL && activity.phase === "started" && activity.text !== void 0 && activity.text.length > 0) {
|
|
7362
|
+
plans.set(toolUseId, activity.text);
|
|
7363
|
+
if (!states.has(toolUseId)) states.set(toolUseId, "pending");
|
|
7364
|
+
}
|
|
7365
|
+
if (activity.phase === "completed") states.set(toolUseId, "approved");
|
|
7366
|
+
else if (activity.phase === "denied" || activity.phase === "failed") states.set(toolUseId, "rejected");
|
|
7367
|
+
}
|
|
7368
|
+
return [...plans].map(([toolUseId, plan]) => ({
|
|
7369
|
+
toolUseId,
|
|
7370
|
+
plan,
|
|
7371
|
+
state: states.get(toolUseId) ?? "pending"
|
|
7372
|
+
}));
|
|
7373
|
+
}
|
|
7374
|
+
/** The newest plan, for readers that only care what is on the table now. */
|
|
7375
|
+
function latestPlanReview(activities) {
|
|
7376
|
+
return planReviews(activities).at(-1);
|
|
7377
|
+
}
|
|
7378
|
+
/** A plan's own first heading, so a list of several can name them.
|
|
7379
|
+
*
|
|
7380
|
+
* Falls back to the opening line: a plan without a heading is unusual but
|
|
7381
|
+
* still has to be pickable. Fenced blocks are not scanned — a `#` comment in
|
|
7382
|
+
* the first code block of a heading-less plan is a worse label than the first
|
|
7383
|
+
* line, but not a wrong one, and the cost of getting it exactly right is a
|
|
7384
|
+
* fence-state machine for a fallback. */
|
|
7385
|
+
function planTitle(plan) {
|
|
7386
|
+
for (const raw of plan.split("\n")) {
|
|
7387
|
+
const line = raw.trim();
|
|
7388
|
+
if (line.length === 0) continue;
|
|
7389
|
+
const heading = /^#{1,6}\s+(.+?)\s*#*$/u.exec(line);
|
|
7390
|
+
if (heading?.[1] !== void 0) return heading[1].slice(0, MAX_TITLE_CHARS);
|
|
7391
|
+
return line.slice(0, MAX_TITLE_CHARS);
|
|
7392
|
+
}
|
|
7393
|
+
return "";
|
|
7394
|
+
}
|
|
7395
|
+
const MAX_TITLE_CHARS = 80;
|
|
7396
|
+
const MAX_QUOTE_CHARS = 1e3;
|
|
7397
|
+
/** The passage under the current selection, when it lies inside the plan body.
|
|
7398
|
+
*
|
|
7399
|
+
* Reads the live selection rather than mirroring the DOM: the body is the
|
|
7400
|
+
* Host's Markdown output, which this package renders but does not own, and
|
|
7401
|
+
* the only stable thing about it is that it is inside this element. */
|
|
7402
|
+
function quotedSelection(selection, body) {
|
|
7403
|
+
if (selection === null || body === null || selection.isCollapsed || selection.rangeCount === 0) return void 0;
|
|
7404
|
+
const range = selection.getRangeAt(0);
|
|
7405
|
+
if (!body.contains(range.startContainer) || !body.contains(range.endContainer)) return void 0;
|
|
7406
|
+
const text = selection.toString().trim();
|
|
7407
|
+
return text.length === 0 ? void 0 : text.slice(0, MAX_QUOTE_CHARS);
|
|
7408
|
+
}
|
|
7409
|
+
/** The newest review as one primitive, for readers that only need to know
|
|
7410
|
+
* whether it changed. A snapshot hook keeps its value only while the
|
|
7411
|
+
* selection compares equal, and a fresh object per snapshot would defeat
|
|
7412
|
+
* that. Empty string when the session has proposed nothing. */
|
|
7413
|
+
function planReviewKey(activities) {
|
|
7414
|
+
const review = latestPlanReview(activities);
|
|
7415
|
+
return review === void 0 ? "" : `${review.state}:${review.toolUseId}`;
|
|
7416
|
+
}
|
|
7417
|
+
/** Split what {@link planReviewKey} joined. */
|
|
7418
|
+
function parsePlanReviewKey(key) {
|
|
7419
|
+
const cut = key.indexOf(":");
|
|
7420
|
+
if (cut < 0) return void 0;
|
|
7421
|
+
const state = key.slice(0, cut);
|
|
7422
|
+
if (state !== "pending" && state !== "approved" && state !== "rejected") return void 0;
|
|
7423
|
+
return {
|
|
7424
|
+
state,
|
|
7425
|
+
toolUseId: key.slice(cut + 1)
|
|
7426
|
+
};
|
|
7427
|
+
}
|
|
7428
|
+
/** Restore-from-maximized: four corners pulling inward. Mirrors the diff
|
|
7429
|
+
* panel's own, which the primitives set has no counterpart for. */
|
|
7430
|
+
function RestorePanelIcon$1() {
|
|
7431
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
7432
|
+
width: "16",
|
|
7433
|
+
height: "16",
|
|
7434
|
+
viewBox: "0 0 16 16",
|
|
7435
|
+
fill: "currentColor",
|
|
7436
|
+
"aria-hidden": "true",
|
|
7437
|
+
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" })
|
|
7438
|
+
});
|
|
7439
|
+
}
|
|
7440
|
+
/** Card and rows reproduce the primitives' menu surface, like the session
|
|
7441
|
+
* menu next door: r7, inverted hairline, shadow-lv3, 2px inset. */
|
|
7442
|
+
const PICKER_CSS = [
|
|
7443
|
+
".dsh-claude-plan-picker-root{position:relative;display:inline-flex;min-width:0}",
|
|
7444
|
+
".dsh-claude-plan-picker{display:inline-flex;align-items:center;gap:4px;min-width:0;padding:2px 6px;",
|
|
7445
|
+
"margin:-2px -6px;border:0;border-radius:7px;background:transparent;color:inherit;font:inherit;cursor:pointer;",
|
|
7446
|
+
"transition:background .12s ease}",
|
|
7447
|
+
".dsh-claude-plan-picker:hover,.dsh-claude-plan-picker[aria-expanded=\"true\"]{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7448
|
+
".dsh-claude-plan-picker:focus-visible{outline:none;background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7449
|
+
".dsh-claude-plan-picker>svg{flex:none;transition:transform .12s ease}",
|
|
7450
|
+
".dsh-claude-plan-picker[aria-expanded=\"true\"]>svg{transform:rotate(180deg)}",
|
|
7451
|
+
".dsh-claude-plan-picker-card{box-sizing:border-box;position:absolute;top:calc(100% + 6px);left:-6px;z-index:100;",
|
|
7452
|
+
"display:flex;flex-direction:column;gap:1px;padding:2px;min-width:240px;max-width:min(420px,70vw);",
|
|
7453
|
+
"max-height:min(320px,50vh);overflow-y:auto;border:1px solid var(--dsw-alias-border-inverted);",
|
|
7454
|
+
"border-radius:7px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3)}",
|
|
7455
|
+
".dsh-claude-plan-picker-item{display:flex;align-items:center;gap:8px;width:100%;min-height:26px;",
|
|
7456
|
+
"padding:5px 8px;border:0;border-radius:5px;background:transparent;color:var(--dsw-alias-label-primary);",
|
|
7457
|
+
"font:inherit;font-size:12px;line-height:17px;text-align:left;cursor:pointer}",
|
|
7458
|
+
".dsh-claude-plan-picker-item:hover,.dsh-claude-plan-picker-item:focus-visible{outline:none;",
|
|
7459
|
+
"background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7460
|
+
".dsh-claude-plan-picker-item[aria-current=\"true\"]{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
7461
|
+
".dsh-claude-plan-picker-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
7462
|
+
".dsh-claude-plan-picker-ordinal{flex:none;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}"
|
|
7463
|
+
].join("");
|
|
7464
|
+
function ChevronDownIcon() {
|
|
7465
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
7466
|
+
width: "12",
|
|
7467
|
+
height: "12",
|
|
7468
|
+
viewBox: "0 0 12 12",
|
|
7469
|
+
fill: "none",
|
|
7470
|
+
"aria-hidden": "true",
|
|
7471
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
7472
|
+
d: "m3 4.75 3 3 3-3",
|
|
7473
|
+
stroke: "currentColor",
|
|
7474
|
+
strokeWidth: "1.5",
|
|
7475
|
+
strokeLinecap: "round",
|
|
7476
|
+
strokeLinejoin: "round"
|
|
7477
|
+
})
|
|
7478
|
+
});
|
|
7479
|
+
}
|
|
7480
|
+
const STATE_LABEL = {
|
|
7481
|
+
pending: "planPending",
|
|
7482
|
+
approved: "planApproved",
|
|
7483
|
+
rejected: "planRejected"
|
|
7484
|
+
};
|
|
7485
|
+
/** The plan behind an `ExitPlanMode` approval, as the document it was written
|
|
7486
|
+
* as. The decision itself stays with the Host's approval dialog; this panel
|
|
7487
|
+
* is where the plan is actually read, under the reader's own prose palette. */
|
|
7488
|
+
function ClaudePlanPanel({ useClaudeProjection, t, sessionId, closeDetails, maximized, toggleMaximized, submitChanges = sendPlanForChanges }) {
|
|
7489
|
+
const markdownLabels = useClaudeMarkdownLabels(t);
|
|
7490
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
7491
|
+
const reviews = planReviews(useClaudeProjection((projection) => projection.activities));
|
|
7492
|
+
const [chosen, setChosen] = useState();
|
|
7493
|
+
const [open, setOpen] = useState(false);
|
|
7494
|
+
const picker = useRef(null);
|
|
7495
|
+
useDismissOnOutsidePointer(picker, open, (next) => {
|
|
7496
|
+
if (!next) setOpen(false);
|
|
7497
|
+
});
|
|
7498
|
+
const pendingId = reviews.find((item) => item.state === "pending")?.toolUseId;
|
|
7499
|
+
useEffect(() => {
|
|
7500
|
+
if (pendingId !== void 0) setChosen(pendingId);
|
|
7501
|
+
}, [pendingId]);
|
|
7502
|
+
const review = reviews.find((item) => item.toolUseId === chosen) ?? reviews.at(-1);
|
|
7503
|
+
const [notes, setNotes] = useState([]);
|
|
7504
|
+
const [draft, setDraft] = useState("");
|
|
7505
|
+
const [quote, setQuote] = useState();
|
|
7506
|
+
const [sending, setSending] = useState(false);
|
|
7507
|
+
const [failure, setFailure] = useState();
|
|
7508
|
+
const body = useRef(null);
|
|
7509
|
+
const composer = useRef(null);
|
|
7510
|
+
useEffect(() => {
|
|
7511
|
+
if (typeof document === "undefined") return;
|
|
7512
|
+
const read = () => {
|
|
7513
|
+
const selected = quotedSelection(document.getSelection(), body.current);
|
|
7514
|
+
if (selected !== void 0) setQuote(selected);
|
|
7515
|
+
};
|
|
7516
|
+
document.addEventListener("selectionchange", read);
|
|
7517
|
+
return () => {
|
|
7518
|
+
document.removeEventListener("selectionchange", read);
|
|
7519
|
+
};
|
|
7520
|
+
}, []);
|
|
7521
|
+
useEffect(() => {
|
|
7522
|
+
setNotes([]);
|
|
7523
|
+
setDraft("");
|
|
7524
|
+
setQuote(void 0);
|
|
7525
|
+
setFailure(void 0);
|
|
7526
|
+
}, [review?.toolUseId]);
|
|
7527
|
+
const addNote = useCallback(() => {
|
|
7528
|
+
const text = draft.trim();
|
|
7529
|
+
if (text.length === 0) return;
|
|
7530
|
+
setNotes((current) => [...current, quote === void 0 ? { text } : {
|
|
7531
|
+
quote,
|
|
7532
|
+
text
|
|
7533
|
+
}]);
|
|
7534
|
+
setDraft("");
|
|
7535
|
+
setQuote(void 0);
|
|
7536
|
+
}, [draft, quote]);
|
|
7537
|
+
const send = useCallback(() => {
|
|
7538
|
+
if (review === void 0 || sending) return;
|
|
7539
|
+
const text = draft.trim();
|
|
7540
|
+
const pending = text.length === 0 ? notes : [...notes, quote === void 0 ? { text } : {
|
|
7541
|
+
quote,
|
|
7542
|
+
text
|
|
7543
|
+
}];
|
|
7544
|
+
if (pending.length === 0) return;
|
|
7545
|
+
setSending(true);
|
|
7546
|
+
setFailure(void 0);
|
|
7547
|
+
submitChanges(sessionId, review.toolUseId, pending).then(() => {
|
|
7548
|
+
setNotes([]);
|
|
7549
|
+
setDraft("");
|
|
7550
|
+
setQuote(void 0);
|
|
7551
|
+
setSending(false);
|
|
7552
|
+
}, (error) => {
|
|
7553
|
+
setFailure(error instanceof Error && error.message === "planSettled" ? "planSettled" : "planFeedbackFailed");
|
|
7554
|
+
setSending(false);
|
|
7555
|
+
});
|
|
7556
|
+
}, [
|
|
7557
|
+
draft,
|
|
7558
|
+
notes,
|
|
7559
|
+
quote,
|
|
7560
|
+
review,
|
|
7561
|
+
sending,
|
|
7562
|
+
sessionId,
|
|
7563
|
+
submitChanges
|
|
7564
|
+
]);
|
|
7565
|
+
useEffect(() => {
|
|
7566
|
+
if (!owned || review === void 0) closeDetails();
|
|
7567
|
+
}, [
|
|
7568
|
+
closeDetails,
|
|
7569
|
+
owned,
|
|
7570
|
+
review === void 0
|
|
7571
|
+
]);
|
|
7572
|
+
if (!owned) return null;
|
|
7573
|
+
const index = review === void 0 ? -1 : reviews.findIndex((item) => item.toolUseId === review.toolUseId);
|
|
7574
|
+
const badge = (state) => ({
|
|
7575
|
+
...planBadge,
|
|
7576
|
+
...state === "pending" ? planBadgePending : state === "rejected" ? planBadgeRejected : {}
|
|
7577
|
+
});
|
|
7578
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
7579
|
+
className: detailsCardClass,
|
|
7580
|
+
style: {
|
|
7581
|
+
...tasksPanel,
|
|
7582
|
+
...maximized ? diffPanelMaximized : {}
|
|
7583
|
+
},
|
|
7584
|
+
children: [
|
|
7585
|
+
/* @__PURE__ */ jsxs("style", {
|
|
7586
|
+
"data-dsh-claude-panel-icon-styles": true,
|
|
7587
|
+
children: [
|
|
7588
|
+
detailsCardCss,
|
|
7589
|
+
panelIconButtonCss,
|
|
7590
|
+
PICKER_CSS
|
|
7591
|
+
]
|
|
7592
|
+
}),
|
|
7593
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7594
|
+
style: tasksHeader,
|
|
7595
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
7596
|
+
style: planHeaderStart,
|
|
7597
|
+
children: [reviews.length < 2 ? /* @__PURE__ */ jsx("span", {
|
|
7598
|
+
style: tasksHeading,
|
|
7599
|
+
children: t("planPanelTitle")
|
|
7600
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
7601
|
+
className: "dsh-claude-plan-picker-root",
|
|
7602
|
+
ref: picker,
|
|
7603
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
7604
|
+
type: "button",
|
|
7605
|
+
className: "dsh-claude-plan-picker",
|
|
7606
|
+
"aria-expanded": open,
|
|
7607
|
+
"aria-haspopup": "listbox",
|
|
7608
|
+
onClick: () => {
|
|
7609
|
+
setOpen((value) => !value);
|
|
7610
|
+
},
|
|
7611
|
+
children: [
|
|
7612
|
+
/* @__PURE__ */ jsx("span", {
|
|
7613
|
+
style: tasksHeading,
|
|
7614
|
+
children: t("planPanelTitle")
|
|
7615
|
+
}),
|
|
7616
|
+
/* @__PURE__ */ jsx("span", {
|
|
7617
|
+
style: planCount,
|
|
7618
|
+
children: t("planNth", {
|
|
7619
|
+
index: index + 1,
|
|
7620
|
+
total: reviews.length
|
|
7621
|
+
})
|
|
7622
|
+
}),
|
|
7623
|
+
/* @__PURE__ */ jsx(ChevronDownIcon, {})
|
|
7624
|
+
]
|
|
7625
|
+
}), !open ? null : /* @__PURE__ */ jsx("div", {
|
|
7626
|
+
className: "dsh-claude-plan-picker-card",
|
|
7627
|
+
role: "listbox",
|
|
7628
|
+
"aria-label": t("planHistory"),
|
|
7629
|
+
children: [...reviews].reverse().map((item, offset) => /* @__PURE__ */ jsxs("button", {
|
|
7630
|
+
type: "button",
|
|
7631
|
+
role: "option",
|
|
7632
|
+
className: "dsh-claude-plan-picker-item",
|
|
7633
|
+
"aria-current": item.toolUseId === review?.toolUseId,
|
|
7634
|
+
"aria-selected": item.toolUseId === review?.toolUseId,
|
|
7635
|
+
onClick: () => {
|
|
7636
|
+
setChosen(item.toolUseId);
|
|
7637
|
+
setOpen(false);
|
|
7638
|
+
},
|
|
7639
|
+
children: [
|
|
7640
|
+
/* @__PURE__ */ jsx("span", {
|
|
7641
|
+
className: "dsh-claude-plan-picker-ordinal",
|
|
7642
|
+
children: reviews.length - offset
|
|
7643
|
+
}),
|
|
7644
|
+
/* @__PURE__ */ jsx("span", {
|
|
7645
|
+
className: "dsh-claude-plan-picker-title",
|
|
7646
|
+
children: planTitle(item.plan)
|
|
7647
|
+
}),
|
|
7648
|
+
/* @__PURE__ */ jsx("span", {
|
|
7649
|
+
style: badge(item.state),
|
|
7650
|
+
children: t(STATE_LABEL[item.state])
|
|
7651
|
+
})
|
|
7652
|
+
]
|
|
7653
|
+
}, item.toolUseId))
|
|
7654
|
+
})]
|
|
7655
|
+
}), review === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
7656
|
+
style: badge(review.state),
|
|
7657
|
+
children: t(STATE_LABEL[review.state])
|
|
7658
|
+
})]
|
|
7659
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
7660
|
+
style: planHeaderEnd,
|
|
7661
|
+
children: [/* @__PURE__ */ jsx("button", {
|
|
7662
|
+
type: "button",
|
|
7663
|
+
className: panelIconButtonClass,
|
|
7664
|
+
"aria-label": maximized ? t("planRestore") : t("planMaximize"),
|
|
7665
|
+
onClick: toggleMaximized,
|
|
7666
|
+
children: maximized ? /* @__PURE__ */ jsx(RestorePanelIcon$1, {}) : /* @__PURE__ */ jsx(IconFullscreenOutline16, {})
|
|
7667
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7668
|
+
type: "button",
|
|
7669
|
+
className: panelIconButtonClass,
|
|
7670
|
+
"aria-label": t("planClose"),
|
|
7671
|
+
onClick: closeDetails,
|
|
7672
|
+
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
7673
|
+
})]
|
|
7674
|
+
})]
|
|
7675
|
+
}),
|
|
7676
|
+
/* @__PURE__ */ jsx("div", {
|
|
7677
|
+
style: tasksBody,
|
|
7678
|
+
ref: body,
|
|
7679
|
+
children: review === void 0 ? /* @__PURE__ */ jsx("p", {
|
|
7680
|
+
style: tasksGroupEmpty,
|
|
7681
|
+
children: t("planEmpty")
|
|
7682
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [review.state === "pending" ? /* @__PURE__ */ jsx("p", {
|
|
7683
|
+
style: planHint,
|
|
7684
|
+
children: t("planPendingHint")
|
|
7685
|
+
}) : null, /* @__PURE__ */ jsx(ClaudeMarkdown, {
|
|
7686
|
+
text: review.plan,
|
|
7687
|
+
labels: markdownLabels
|
|
7688
|
+
}, review.toolUseId)] })
|
|
7689
|
+
}),
|
|
7690
|
+
review?.state !== "pending" ? null : /* @__PURE__ */ jsxs("div", {
|
|
7691
|
+
style: planComposer,
|
|
7692
|
+
children: [
|
|
7693
|
+
notes.length === 0 ? null : /* @__PURE__ */ jsx("ul", {
|
|
7694
|
+
style: planNoteList,
|
|
7695
|
+
children: notes.map((note, index) => /* @__PURE__ */ jsxs("li", {
|
|
7696
|
+
style: planNote,
|
|
7697
|
+
children: [
|
|
7698
|
+
note.quote === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
7699
|
+
style: planNoteQuote,
|
|
7700
|
+
children: note.quote
|
|
7701
|
+
}),
|
|
7702
|
+
/* @__PURE__ */ jsx("p", {
|
|
7703
|
+
style: planNoteText,
|
|
7704
|
+
children: note.text
|
|
7705
|
+
}),
|
|
7706
|
+
/* @__PURE__ */ jsx("button", {
|
|
7707
|
+
type: "button",
|
|
7708
|
+
style: planNoteRemove,
|
|
7709
|
+
"aria-label": t("planNoteRemove"),
|
|
7710
|
+
onClick: () => {
|
|
7711
|
+
setNotes((current) => current.filter((_, at) => at !== index));
|
|
7712
|
+
},
|
|
7713
|
+
children: "×"
|
|
7714
|
+
})
|
|
7715
|
+
]
|
|
7716
|
+
}, index))
|
|
7717
|
+
}),
|
|
7718
|
+
quote === void 0 ? null : /* @__PURE__ */ jsxs("div", {
|
|
7719
|
+
style: planQuoteChip,
|
|
7720
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7721
|
+
style: planNoteQuote,
|
|
7722
|
+
children: quote
|
|
7723
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7724
|
+
type: "button",
|
|
7725
|
+
style: planNoteRemove,
|
|
7726
|
+
"aria-label": t("planQuoteClear"),
|
|
7727
|
+
onClick: () => {
|
|
7728
|
+
setQuote(void 0);
|
|
7729
|
+
},
|
|
7730
|
+
children: "×"
|
|
7731
|
+
})]
|
|
7732
|
+
}),
|
|
7733
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
7734
|
+
ref: composer,
|
|
7735
|
+
value: draft,
|
|
7736
|
+
placeholder: t(quote === void 0 ? "planNotePlaceholder" : "planNoteQuotedPlaceholder"),
|
|
7737
|
+
style: planComposerInput,
|
|
7738
|
+
onChange: (event) => {
|
|
7739
|
+
setDraft(event.currentTarget.value);
|
|
7740
|
+
},
|
|
7741
|
+
onKeyDown: (event) => {
|
|
7742
|
+
if (event.key !== "Enter" || event.nativeEvent.isComposing) return;
|
|
7743
|
+
event.preventDefault();
|
|
7744
|
+
if (event.metaKey || event.ctrlKey) send();
|
|
7745
|
+
else addNote();
|
|
7746
|
+
}
|
|
7747
|
+
}),
|
|
7748
|
+
failure === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
7749
|
+
role: "alert",
|
|
7750
|
+
style: planComposerError,
|
|
7751
|
+
children: t(failure)
|
|
7752
|
+
}),
|
|
7753
|
+
/* @__PURE__ */ jsxs("div", {
|
|
7754
|
+
style: planComposerActions,
|
|
7755
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
7756
|
+
style: planComposerHint,
|
|
7757
|
+
children: t("planNoteHint")
|
|
7758
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
7759
|
+
type: "button",
|
|
7760
|
+
style: {
|
|
7761
|
+
...askButton,
|
|
7762
|
+
...askPrimaryButton
|
|
7763
|
+
},
|
|
7764
|
+
disabled: sending || notes.length === 0 && draft.trim().length === 0,
|
|
7765
|
+
onClick: send,
|
|
7766
|
+
children: t(sending ? "planSending" : "planSendForChanges")
|
|
7767
|
+
})]
|
|
7768
|
+
})
|
|
7769
|
+
]
|
|
7770
|
+
})
|
|
7771
|
+
]
|
|
7772
|
+
});
|
|
7773
|
+
}
|
|
7774
|
+
//#endregion
|
|
7775
|
+
//#region src/client/ClaudePlanHeaderAction.tsx
|
|
7776
|
+
/** Same resting-quiet treatment as the diff action next to it, plus a dot for
|
|
7777
|
+
* a plan still waiting on its approval dialog. */
|
|
7778
|
+
const ACTION_CSS$1 = [
|
|
7779
|
+
".dsh-claude-header-plan{position:relative;flex:none;display:inline-flex;align-items:center;justify-content:center;",
|
|
7780
|
+
"width:32px;height:32px;padding:0;border:0;border-radius:9px;background:transparent;",
|
|
7781
|
+
"color:var(--dsw-alias-label-secondary);cursor:pointer;",
|
|
7782
|
+
"transition:background .12s ease,color .12s ease}",
|
|
7783
|
+
".dsh-claude-header-plan:hover,.dsh-claude-header-plan:focus-visible,.dsh-claude-header-plan[aria-pressed=\"true\"]{",
|
|
7784
|
+
"background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}",
|
|
7785
|
+
".dsh-claude-header-plan:active{background:var(--dsw-alias-interactive-bg-active)}",
|
|
7786
|
+
".dsh-claude-header-plan:focus-visible{outline:none}",
|
|
7787
|
+
".dsh-claude-header-plan>*{flex:none}",
|
|
7788
|
+
".dsh-claude-header-plan>svg.dsh-claude-header-plan-glyph{",
|
|
7789
|
+
"width:18px;height:18px;display:block;overflow:visible;opacity:1;",
|
|
7790
|
+
"fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}",
|
|
7791
|
+
".dsh-claude-header-plan[data-pending]::after{content:\"\";position:absolute;top:6px;right:6px;",
|
|
7792
|
+
"width:6px;height:6px;border-radius:999px;background:var(--dsw-alias-state-warning-primary,#e2a03f);",
|
|
7793
|
+
"box-shadow:0 0 0 2px var(--dsw-alias-bg-base)}"
|
|
7794
|
+
].join("");
|
|
7795
|
+
let cssInjected$3 = false;
|
|
7796
|
+
function ensureCss$3() {
|
|
7797
|
+
if (cssInjected$3 || typeof document === "undefined") return;
|
|
7798
|
+
cssInjected$3 = true;
|
|
7799
|
+
const element = document.createElement("style");
|
|
7800
|
+
element.dataset.dshClaudeHeaderPlan = "";
|
|
7801
|
+
element.textContent = ACTION_CSS$1;
|
|
7802
|
+
document.head.appendChild(element);
|
|
7803
|
+
}
|
|
7804
|
+
/** A written page, not a checklist: a plan is a document to read, and ticks
|
|
7805
|
+
* next to lines are what the task board means. Drawn inline for the same
|
|
7806
|
+
* reason the diff glyph is — the primitives set ships neither. */
|
|
7807
|
+
function PlanGlyph() {
|
|
7808
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
7809
|
+
className: "dsh-claude-header-plan-glyph",
|
|
7810
|
+
width: "18",
|
|
7811
|
+
height: "18",
|
|
7812
|
+
viewBox: "0 0 18 18",
|
|
7813
|
+
fill: "none",
|
|
7814
|
+
"aria-hidden": "true",
|
|
7815
|
+
focusable: "false",
|
|
7816
|
+
children: [
|
|
7817
|
+
/* @__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" }),
|
|
7818
|
+
/* @__PURE__ */ jsx("path", { d: "M10.5 2.25V6.5h4.25" }),
|
|
7819
|
+
/* @__PURE__ */ jsx("path", { d: "M6.25 9h5.5M6.25 12h3.5" })
|
|
7820
|
+
]
|
|
7821
|
+
});
|
|
7822
|
+
}
|
|
7823
|
+
function ClaudePlanHeaderAction({ t, sessionId, togglePlan, planOpen, useClaudeProjection }) {
|
|
7824
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
7825
|
+
const review = parsePlanReviewKey(useClaudeProjection((projection) => planReviewKey(projection.activities)));
|
|
7826
|
+
const open = useSyncExternalStore(planOpen.subscribe, planOpen.getSnapshot, planOpen.getSnapshot);
|
|
7827
|
+
const opened = useRef();
|
|
7828
|
+
useEffect(() => {
|
|
7829
|
+
if (review?.state !== "pending" || opened.current === review.toolUseId) return;
|
|
7830
|
+
opened.current = review.toolUseId;
|
|
7831
|
+
if (!open) togglePlan();
|
|
7832
|
+
});
|
|
7833
|
+
if (!owned || review === void 0) return null;
|
|
7834
|
+
ensureCss$3();
|
|
7835
|
+
const label = t(open ? "planClose" : "planOpen");
|
|
7836
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
7837
|
+
label,
|
|
7838
|
+
side: "bottom",
|
|
7839
|
+
delayMs: 250,
|
|
7840
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
7841
|
+
type: "button",
|
|
7842
|
+
className: "dsh-claude-header-plan",
|
|
7843
|
+
"aria-label": label,
|
|
7844
|
+
"aria-pressed": open,
|
|
7845
|
+
"data-pending": review.state === "pending" || void 0,
|
|
7846
|
+
"data-session": sessionId,
|
|
7847
|
+
onClick: togglePlan,
|
|
7848
|
+
children: /* @__PURE__ */ jsx(PlanGlyph, {})
|
|
7849
|
+
})
|
|
7850
|
+
});
|
|
7851
|
+
}
|
|
7852
|
+
//#endregion
|
|
6470
7853
|
//#region src/client/repository-action-api.ts
|
|
6471
7854
|
var RepositoryActionClientError = class extends Error {
|
|
6472
7855
|
code;
|
|
@@ -6478,7 +7861,7 @@ window.__ModuleLoader__.load({
|
|
|
6478
7861
|
if (commit !== void 0) this.commit = commit;
|
|
6479
7862
|
}
|
|
6480
7863
|
};
|
|
6481
|
-
function record$
|
|
7864
|
+
function record$3(value) {
|
|
6482
7865
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
6483
7866
|
}
|
|
6484
7867
|
/** The dialog branches on `code`, so a route refusal keeps arriving as this
|
|
@@ -6491,20 +7874,20 @@ window.__ModuleLoader__.load({
|
|
|
6491
7874
|
return error instanceof PluginRequestError ? new RepositoryActionClientError(error.message, error.code) : error;
|
|
6492
7875
|
}
|
|
6493
7876
|
function preview(value) {
|
|
6494
|
-
const input = record$
|
|
7877
|
+
const input = record$3(value);
|
|
6495
7878
|
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
7879
|
for (const file of input.files) {
|
|
6497
|
-
const item = record$
|
|
7880
|
+
const item = record$3(file);
|
|
6498
7881
|
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
7882
|
}
|
|
6500
7883
|
for (const commit of input.unpushedCommits) {
|
|
6501
|
-
const item = record$
|
|
7884
|
+
const item = record$3(commit);
|
|
6502
7885
|
if (item === void 0 || typeof item.hash !== "string" || typeof item.subject !== "string") throw new Error("Invalid repository action commit.");
|
|
6503
7886
|
}
|
|
6504
7887
|
return input;
|
|
6505
7888
|
}
|
|
6506
7889
|
function result(value) {
|
|
6507
|
-
const input = record$
|
|
7890
|
+
const input = record$3(value);
|
|
6508
7891
|
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
7892
|
return input;
|
|
6510
7893
|
}
|
|
@@ -6518,7 +7901,7 @@ window.__ModuleLoader__.load({
|
|
|
6518
7901
|
}
|
|
6519
7902
|
async function generateCommitMessage(sessionId, fingerprint, signal) {
|
|
6520
7903
|
try {
|
|
6521
|
-
const value = record$
|
|
7904
|
+
const value = record$3(await pluginWrite(`${CLAUDE_REPOSITORY_ACTION_PATH}/message`, "remote", signal, {
|
|
6522
7905
|
query: { sessionId },
|
|
6523
7906
|
json: { fingerprint }
|
|
6524
7907
|
}));
|
|
@@ -6581,16 +7964,16 @@ window.__ModuleLoader__.load({
|
|
|
6581
7964
|
/** The draft only has to describe the work; the host truncates it again before
|
|
6582
7965
|
* summarizing, and the setup route caps the whole body at 16 KiB. */
|
|
6583
7966
|
const MAX_INTENT_CHARS = 2e3;
|
|
6584
|
-
function record$
|
|
7967
|
+
function record$2(value) {
|
|
6585
7968
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
6586
7969
|
}
|
|
6587
7970
|
function setupResult(value) {
|
|
6588
|
-
const item = record$
|
|
7971
|
+
const item = record$2(value);
|
|
6589
7972
|
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
7973
|
return item;
|
|
6591
7974
|
}
|
|
6592
7975
|
function parseRepositorySetupEvent(line, onProgress) {
|
|
6593
|
-
const event = record$
|
|
7976
|
+
const event = record$2(JSON.parse(line));
|
|
6594
7977
|
if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
|
|
6595
7978
|
onProgress(event.stage);
|
|
6596
7979
|
return;
|
|
@@ -6680,222 +8063,33 @@ window.__ModuleLoader__.load({
|
|
|
6680
8063
|
cwd,
|
|
6681
8064
|
path,
|
|
6682
8065
|
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;
|
|
8066
|
+
to: String(to)
|
|
8067
|
+
} });
|
|
8068
|
+
if (!Array.isArray(body.lines) || typeof body.total !== "number") throw new Error("Invalid repository file response.");
|
|
6891
8069
|
return {
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
handledCommentIds: /* @__PURE__ */ new Set([...memory.handledCommentIds, ...fresh.map((comment) => comment.id)]),
|
|
6895
|
-
...nextSignature === void 0 ? {} : { handledChecksSignature: nextSignature }
|
|
6896
|
-
}
|
|
8070
|
+
lines: body.lines.map(String),
|
|
8071
|
+
total: body.total
|
|
6897
8072
|
};
|
|
6898
8073
|
}
|
|
8074
|
+
async function loadRepositoryStatusFor(cwd, signal) {
|
|
8075
|
+
const body = await pluginRead(CLAUDE_REPOSITORY_STATUS_PATH, "git", signal, { query: { cwd } });
|
|
8076
|
+
if (typeof body.status !== "string" || typeof body.cwd !== "string") throw new Error("Invalid repository status response.");
|
|
8077
|
+
return body;
|
|
8078
|
+
}
|
|
8079
|
+
//#endregion
|
|
8080
|
+
//#region src/client/relative-age.ts
|
|
8081
|
+
/** Compact age of an ISO timestamp, in the shape the panels already use
|
|
8082
|
+
* ("<1h", "4h", "3d", "2mo"). `now` is a parameter so callers that re-render
|
|
8083
|
+
* on a clock — and tests — stay deterministic. */
|
|
8084
|
+
function relativeAge(value, now = Date.now()) {
|
|
8085
|
+
if (value === void 0) return void 0;
|
|
8086
|
+
const elapsedHours = Math.max(0, Math.floor((now - Date.parse(value)) / 36e5));
|
|
8087
|
+
if (!Number.isFinite(elapsedHours)) return void 0;
|
|
8088
|
+
if (elapsedHours < 1) return "<1h";
|
|
8089
|
+
if (elapsedHours < 24) return `${elapsedHours}h`;
|
|
8090
|
+
const days = Math.floor(elapsedHours / 24);
|
|
8091
|
+
return days < 30 ? `${days}d` : `${Math.floor(days / 30)}mo`;
|
|
8092
|
+
}
|
|
6899
8093
|
//#endregion
|
|
6900
8094
|
//#region src/client/boot-check.ts
|
|
6901
8095
|
/** Boot-time assertions against the running Host.
|
|
@@ -7088,7 +8282,7 @@ window.__ModuleLoader__.load({
|
|
|
7088
8282
|
]
|
|
7089
8283
|
});
|
|
7090
8284
|
}
|
|
7091
|
-
function repositoryName
|
|
8285
|
+
function repositoryName(remote) {
|
|
7092
8286
|
return remote?.split("/").at(-1);
|
|
7093
8287
|
}
|
|
7094
8288
|
function PullRequestHoverCard({ repository, t }) {
|
|
@@ -7116,7 +8310,7 @@ window.__ModuleLoader__.load({
|
|
|
7116
8310
|
/* @__PURE__ */ jsxs("span", {
|
|
7117
8311
|
style: repositoryPrHoverRepo,
|
|
7118
8312
|
children: [
|
|
7119
|
-
repositoryName
|
|
8313
|
+
repositoryName(repository.remote),
|
|
7120
8314
|
" #",
|
|
7121
8315
|
pullRequest.number,
|
|
7122
8316
|
pullRequest.baseBranch === void 0 ? "" : ` → ${pullRequest.baseBranch}`
|
|
@@ -7894,7 +9088,7 @@ window.__ModuleLoader__.load({
|
|
|
7894
9088
|
}),
|
|
7895
9089
|
repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
7896
9090
|
style: repositoryRemote,
|
|
7897
|
-
children: repositoryName
|
|
9091
|
+
children: repositoryName(repository.remote)
|
|
7898
9092
|
}),
|
|
7899
9093
|
/* @__PURE__ */ jsx(Tooltip, {
|
|
7900
9094
|
label: branch,
|
|
@@ -13939,7 +15133,7 @@ window.__ModuleLoader__.load({
|
|
|
13939
15133
|
] });
|
|
13940
15134
|
}
|
|
13941
15135
|
//#endregion
|
|
13942
|
-
//#region src/client/
|
|
15136
|
+
//#region src/client/ClaudePanelOverlay.tsx
|
|
13943
15137
|
const useClientLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
13944
15138
|
function shouldRestoreFromEscape(event, root = document) {
|
|
13945
15139
|
return event.key === "Escape" && root.querySelector("[role=\"dialog\"][aria-modal=\"true\"]") === null;
|
|
@@ -13987,7 +15181,7 @@ window.__ModuleLoader__.load({
|
|
|
13987
15181
|
window.removeEventListener("resize", update);
|
|
13988
15182
|
};
|
|
13989
15183
|
}
|
|
13990
|
-
function
|
|
15184
|
+
function ClaudePanelOverlay({ children, onRestore }) {
|
|
13991
15185
|
const ref = useRef(null);
|
|
13992
15186
|
const [bounds, setBounds] = useState();
|
|
13993
15187
|
useClientLayoutEffect(() => {
|
|
@@ -14006,7 +15200,7 @@ window.__ModuleLoader__.load({
|
|
|
14006
15200
|
}, [onRestore]);
|
|
14007
15201
|
return /* @__PURE__ */ jsx("div", {
|
|
14008
15202
|
ref,
|
|
14009
|
-
"data-dsh-claude-
|
|
15203
|
+
"data-dsh-claude-panel-overlay": true,
|
|
14010
15204
|
style: {
|
|
14011
15205
|
position: "absolute",
|
|
14012
15206
|
left: bounds?.left ?? 0,
|
|
@@ -14147,232 +15341,38 @@ window.__ModuleLoader__.load({
|
|
|
14147
15341
|
if (event.key === "Escape") setEditing(void 0);
|
|
14148
15342
|
else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
|
|
14149
15343
|
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]);
|
|
14290
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
14291
|
-
className: detailsCardClass,
|
|
14292
|
-
style: tasksPanel,
|
|
14293
|
-
children: [
|
|
14294
|
-
/* @__PURE__ */ jsxs("style", {
|
|
14295
|
-
"data-dsh-claude-overview-styles": true,
|
|
14296
|
-
children: [detailsCardCss, panelIconButtonCss]
|
|
14297
|
-
}),
|
|
14298
|
-
/* @__PURE__ */ jsxs("header", {
|
|
14299
|
-
style: tasksHeader,
|
|
14300
|
-
children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
|
|
14301
|
-
style: tasksHeading,
|
|
14302
|
-
children: t("overviewTitle")
|
|
14303
|
-
}), /* @__PURE__ */ jsx("span", {
|
|
14304
|
-
style: tasksTurnMeta,
|
|
14305
|
-
children: t("overviewBody")
|
|
14306
|
-
})] }), /* @__PURE__ */ jsx("button", {
|
|
14307
|
-
type: "button",
|
|
14308
|
-
className: panelIconButtonClass,
|
|
14309
|
-
"aria-label": t("diffClose"),
|
|
14310
|
-
onClick: closeDetails,
|
|
14311
|
-
children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
|
|
14312
|
-
})]
|
|
14313
|
-
}),
|
|
14314
|
-
/* @__PURE__ */ jsx("div", {
|
|
14315
|
-
style: overviewBody,
|
|
14316
|
-
children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
14317
|
-
style: overviewEmpty,
|
|
14318
|
-
children: t("overviewEmpty")
|
|
14319
|
-
}) : rows.map((row) => {
|
|
14320
|
-
const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
|
|
14321
|
-
const pullRequest = repository?.pullRequest;
|
|
14322
|
-
const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
|
|
14323
|
-
return /* @__PURE__ */ jsxs("button", {
|
|
14324
|
-
type: "button",
|
|
14325
|
-
style: overviewRow,
|
|
14326
|
-
onClick: () => {
|
|
14327
|
-
openSession(row.id);
|
|
14328
|
-
},
|
|
14329
|
-
children: [/* @__PURE__ */ jsxs("span", {
|
|
14330
|
-
style: overviewRowTop,
|
|
14331
|
-
children: [
|
|
14332
|
-
row.running === true ? /* @__PURE__ */ jsx("span", {
|
|
14333
|
-
style: overviewRunningDot,
|
|
14334
|
-
"aria-label": t("overviewRunning")
|
|
14335
|
-
}) : null,
|
|
14336
|
-
/* @__PURE__ */ jsx("span", {
|
|
14337
|
-
style: overviewTitle,
|
|
14338
|
-
children: row.displayTitle ?? row.id
|
|
14339
|
-
}),
|
|
14340
|
-
pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
|
|
14341
|
-
label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
|
|
14342
|
-
tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
|
|
14343
|
-
})
|
|
14344
|
-
]
|
|
14345
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
14346
|
-
style: overviewMeta,
|
|
14347
|
-
children: [
|
|
14348
|
-
repositoryName(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName(repository?.remote) }),
|
|
14349
|
-
/* @__PURE__ */ jsx("span", {
|
|
14350
|
-
style: overviewBranch,
|
|
14351
|
-
children: branch
|
|
14352
|
-
}),
|
|
14353
|
-
pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
14354
|
-
label: t(`repositoryChecks_${pullRequest.checks}`),
|
|
14355
|
-
tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
|
|
14356
|
-
}) : null,
|
|
14357
|
-
pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
|
|
14358
|
-
label: t(`repositoryReview_${pullRequest.review}`),
|
|
14359
|
-
tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
|
|
14360
|
-
}) : null,
|
|
14361
|
-
autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
|
|
14362
|
-
label: t("overviewAutoFix"),
|
|
14363
|
-
tone: "success"
|
|
14364
|
-
}) : null,
|
|
14365
|
-
projectionFor === void 0 ? null : /* @__PURE__ */ jsx(OverviewAttention, {
|
|
14366
|
-
source: projectionFor(row.id),
|
|
14367
|
-
running: row.running === true,
|
|
14368
|
-
t
|
|
14369
|
-
})
|
|
14370
|
-
]
|
|
14371
|
-
})]
|
|
14372
|
-
}, row.id);
|
|
14373
|
-
})
|
|
14374
|
-
})
|
|
14375
|
-
]
|
|
15344
|
+
saveEdit();
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15347
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
15348
|
+
style: queuePreview,
|
|
15349
|
+
children: row.preview
|
|
15350
|
+
}),
|
|
15351
|
+
mutable ? /* @__PURE__ */ jsx("span", {
|
|
15352
|
+
style: queueActions,
|
|
15353
|
+
children: editing?.id === row.id ? /* @__PURE__ */ jsxs(Fragment$1, { children: [action(t("queueSave"), /* @__PURE__ */ jsx(IconCheckOutline16, { size: 14 }), () => {
|
|
15354
|
+
saveEdit();
|
|
15355
|
+
}, busy !== void 0 || editing.text.trim() === ""), action(t("queueCancelEdit"), /* @__PURE__ */ jsx(IconCloseOutline16, { size: 14 }), () => {
|
|
15356
|
+
setEditing(void 0);
|
|
15357
|
+
}, busy !== void 0)] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
15358
|
+
action(t("queueEdit"), /* @__PURE__ */ jsx(IconEditOutline16, { size: 14 }), () => {
|
|
15359
|
+
if (row.text !== null) setEditing({
|
|
15360
|
+
id: row.id,
|
|
15361
|
+
text: row.text
|
|
15362
|
+
});
|
|
15363
|
+
}, busy !== void 0 || row.text === null, row.text === null ? t("queueEditUnsupported") : void 0),
|
|
15364
|
+
action(t("queueRemove"), /* @__PURE__ */ jsx(IconTrashOutline16, { size: 14 }), () => {
|
|
15365
|
+
apply(row.id, { kind: "remove" }, t("queueRemoveFailed"));
|
|
15366
|
+
}, busy !== void 0),
|
|
15367
|
+
action(t("queueSteer"), /* @__PURE__ */ jsx(IconSendOutline14, {}), () => {
|
|
15368
|
+
apply(row.id, { kind: "steer" }, t("queueSteerFailed"));
|
|
15369
|
+
}, busy !== void 0 || !running, running ? void 0 : t("queueSteerUnavailable"))
|
|
15370
|
+
] })
|
|
15371
|
+
}) : null
|
|
15372
|
+
]
|
|
15373
|
+
}, row.id)) : null
|
|
15374
|
+
})]
|
|
15375
|
+
})]
|
|
14376
15376
|
});
|
|
14377
15377
|
}
|
|
14378
15378
|
//#endregion
|
|
@@ -15112,13 +16112,19 @@ window.__ModuleLoader__.load({
|
|
|
15112
16112
|
//#endregion
|
|
15113
16113
|
//#region src/client/rewind-api.ts
|
|
15114
16114
|
/** 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
|
-
|
|
16115
|
+
* this session's transcript and Claude resumes before that turn.
|
|
16116
|
+
*
|
|
16117
|
+
* With `restoreFiles`, the checkout is put back to the tree that turn was
|
|
16118
|
+
* admitted against. The answer reports whether that half actually landed:
|
|
16119
|
+
* a turn from before this session captured trees, or a tree git has since
|
|
16120
|
+
* collected, still rewinds the conversation and leaves the files alone. */
|
|
16121
|
+
async function rewindSession(sessionId, seq, restoreFiles = false) {
|
|
15117
16122
|
try {
|
|
15118
|
-
await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
16123
|
+
return { filesRestored: (await pluginWrite(CLAUDE_REWIND_PATH, "fast", void 0, { json: {
|
|
15119
16124
|
sessionId,
|
|
15120
|
-
seq
|
|
15121
|
-
|
|
16125
|
+
seq,
|
|
16126
|
+
restoreFiles
|
|
16127
|
+
} }))?.filesRestored === true };
|
|
15122
16128
|
} catch (error) {
|
|
15123
16129
|
if (!(error instanceof PluginRequestError)) throw error;
|
|
15124
16130
|
throw new Error(error.code ?? error.reason);
|
|
@@ -15187,6 +16193,8 @@ window.__ModuleLoader__.load({
|
|
|
15187
16193
|
const [target, setTarget] = useState();
|
|
15188
16194
|
const [submitting, setSubmitting] = useState(false);
|
|
15189
16195
|
const [error, setError] = useState();
|
|
16196
|
+
const [restoreFiles, setRestoreFiles] = useState(true);
|
|
16197
|
+
const { toast, report } = useActionToast();
|
|
15190
16198
|
const ranges = projection.rewind?.ranges ?? EMPTY_RANGES;
|
|
15191
16199
|
const owned = projection.owned;
|
|
15192
16200
|
const unavailable = snapshot.running;
|
|
@@ -15254,6 +16262,7 @@ window.__ModuleLoader__.load({
|
|
|
15254
16262
|
setTarget(void 0);
|
|
15255
16263
|
setSubmitting(false);
|
|
15256
16264
|
setError(void 0);
|
|
16265
|
+
setRestoreFiles(true);
|
|
15257
16266
|
}, [sessionId]);
|
|
15258
16267
|
if (sessionId === void 0 || !owned) return /* @__PURE__ */ jsx("span", {
|
|
15259
16268
|
"data-dsh-claude-rewind-armed": "armed",
|
|
@@ -15268,10 +16277,11 @@ window.__ModuleLoader__.load({
|
|
|
15268
16277
|
if (target === void 0 || submitting) return;
|
|
15269
16278
|
setSubmitting(true);
|
|
15270
16279
|
setError(void 0);
|
|
15271
|
-
rewindSession(sessionId, target.seq).then(() => {
|
|
16280
|
+
rewindSession(sessionId, target.seq, restoreFiles).then(({ filesRestored }) => {
|
|
15272
16281
|
setSubmitting(false);
|
|
15273
16282
|
setTarget(void 0);
|
|
15274
16283
|
if (target.text !== "") setDraft?.(sessionId, target.text);
|
|
16284
|
+
if (restoreFiles && !filesRestored) report(t("rewindFilesUnavailable"));
|
|
15275
16285
|
}, (reason) => {
|
|
15276
16286
|
setSubmitting(false);
|
|
15277
16287
|
const code = reason instanceof Error && reason.message !== "" ? reason.message : "unknown";
|
|
@@ -15279,6 +16289,7 @@ window.__ModuleLoader__.load({
|
|
|
15279
16289
|
});
|
|
15280
16290
|
};
|
|
15281
16291
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
16292
|
+
toast,
|
|
15282
16293
|
/* @__PURE__ */ jsx("style", {
|
|
15283
16294
|
"data-dsh-claude-rewind-styles": true,
|
|
15284
16295
|
children: `${rewindActionCss}${rewindHiddenCss(hiddenKeys)}`
|
|
@@ -15343,9 +16354,20 @@ window.__ModuleLoader__.load({
|
|
|
15343
16354
|
style: rewindModalMessage,
|
|
15344
16355
|
children: target.text.slice(0, 2e3)
|
|
15345
16356
|
}),
|
|
16357
|
+
/* @__PURE__ */ jsxs("label", {
|
|
16358
|
+
style: diffModalCheckbox,
|
|
16359
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
16360
|
+
type: "checkbox",
|
|
16361
|
+
checked: restoreFiles,
|
|
16362
|
+
disabled: submitting,
|
|
16363
|
+
onChange: (event) => {
|
|
16364
|
+
setRestoreFiles(event.currentTarget.checked);
|
|
16365
|
+
}
|
|
16366
|
+
}), t("rewindRestoreFiles")]
|
|
16367
|
+
}),
|
|
15346
16368
|
/* @__PURE__ */ jsx("p", {
|
|
15347
16369
|
style: diffModalStatus,
|
|
15348
|
-
children: t("rewindHint")
|
|
16370
|
+
children: restoreFiles ? t("rewindRestoreFilesHint") : t("rewindHint")
|
|
15349
16371
|
}),
|
|
15350
16372
|
error === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
15351
16373
|
style: diffModalError,
|
|
@@ -16674,19 +17696,19 @@ window.__ModuleLoader__.load({
|
|
|
16674
17696
|
});
|
|
16675
17697
|
}
|
|
16676
17698
|
//#endregion
|
|
16677
|
-
//#region src/client/
|
|
16678
|
-
var
|
|
17699
|
+
//#region src/client/panel-open-store.ts
|
|
17700
|
+
var PanelOpenStore = class {
|
|
16679
17701
|
#sessionId;
|
|
16680
17702
|
#listeners = /* @__PURE__ */ new Set();
|
|
16681
|
-
/** Mark
|
|
17703
|
+
/** Mark this panel open for one session, replacing any previous holder. */
|
|
16682
17704
|
open(sessionId) {
|
|
16683
17705
|
this.#set(sessionId);
|
|
16684
17706
|
}
|
|
16685
|
-
/** Mark
|
|
17707
|
+
/** Mark this panel closed. */
|
|
16686
17708
|
close() {
|
|
16687
17709
|
this.#set(void 0);
|
|
16688
17710
|
}
|
|
16689
|
-
/** Whether
|
|
17711
|
+
/** Whether this panel is currently open for this session. */
|
|
16690
17712
|
isOpen(sessionId) {
|
|
16691
17713
|
return this.#sessionId === sessionId;
|
|
16692
17714
|
}
|
|
@@ -16791,6 +17813,428 @@ window.__ModuleLoader__.load({
|
|
|
16791
17813
|
}
|
|
16792
17814
|
}
|
|
16793
17815
|
//#endregion
|
|
17816
|
+
//#region src/client/prompt-api.ts
|
|
17817
|
+
/** Prompt files live in one global directory, so one module-level cache serves
|
|
17818
|
+
* every session. The TTL is what lets a file added outside DSH appear without
|
|
17819
|
+
* a reload, while a burst of keystrokes through the menu still costs one
|
|
17820
|
+
* directory scan. */
|
|
17821
|
+
const TTL_MS = 5e3;
|
|
17822
|
+
let cached;
|
|
17823
|
+
function invalidateClaudePrompts() {
|
|
17824
|
+
cached = void 0;
|
|
17825
|
+
}
|
|
17826
|
+
/** The user's prompt snippets, at most one read per TTL window. */
|
|
17827
|
+
async function claudePrompts() {
|
|
17828
|
+
const at = Date.now();
|
|
17829
|
+
if (cached !== void 0 && at - cached.at < TTL_MS) return await cached.prompts;
|
|
17830
|
+
const prompts = pluginRead(CLAUDE_PROMPTS_PATH, "fast").then((payload) => payload.prompts).catch(() => {
|
|
17831
|
+
invalidateClaudePrompts();
|
|
17832
|
+
return [];
|
|
17833
|
+
});
|
|
17834
|
+
cached = {
|
|
17835
|
+
at,
|
|
17836
|
+
prompts
|
|
17837
|
+
};
|
|
17838
|
+
return await prompts;
|
|
17839
|
+
}
|
|
17840
|
+
/** Save one snippet, answering where it landed. Rejects with a
|
|
17841
|
+
* `PluginRequestError` whose `code` is `name-taken` when the file already
|
|
17842
|
+
* exists; nothing is overwritten. */
|
|
17843
|
+
async function saveClaudePrompt(name, body) {
|
|
17844
|
+
const saved = await pluginWrite(CLAUDE_PROMPTS_PATH, "fast", void 0, { json: {
|
|
17845
|
+
name,
|
|
17846
|
+
body
|
|
17847
|
+
} });
|
|
17848
|
+
invalidateClaudePrompts();
|
|
17849
|
+
return saved.prompt;
|
|
17850
|
+
}
|
|
17851
|
+
/** Ask the host to name a draft with Claude's cheapest model. Answers
|
|
17852
|
+
* undefined whenever no name could be had — the caller already holds the
|
|
17853
|
+
* locally derived one, so a failure here is never worth reporting. */
|
|
17854
|
+
async function suggestClaudePromptName(draft, cancel) {
|
|
17855
|
+
try {
|
|
17856
|
+
return (await pluginWrite(CLAUDE_PROMPT_NAME_PATH, "git", cancel, { json: { draft } })).name;
|
|
17857
|
+
} catch {
|
|
17858
|
+
return;
|
|
17859
|
+
}
|
|
17860
|
+
}
|
|
17861
|
+
/** Rewrite a draft into something an agent can act on. Unlike the name
|
|
17862
|
+
* suggestion this one throws: the user asked for it and is waiting, so a
|
|
17863
|
+
* failure is theirs to see rather than ours to swallow. */
|
|
17864
|
+
async function refineClaudePrompt(draft, cancel) {
|
|
17865
|
+
return (await pluginWrite(CLAUDE_PROMPT_REFINE_PATH, "git", cancel, { json: { draft } })).text;
|
|
17866
|
+
}
|
|
17867
|
+
/** Characters the host's `PROMPT_NAME` guard rejects; scrubbed rather than
|
|
17868
|
+
* re-implemented here, so a drift in the guard cannot let a bad name through
|
|
17869
|
+
* (the host still validates, and answers `invalid-name`). */
|
|
17870
|
+
const FOREIGN_NAME_CHARS = /[^\p{L}\p{M}\p{N} ._()\[\]-]/gu;
|
|
17871
|
+
const MAX_NAME_CHARS = 40;
|
|
17872
|
+
function stamp(now) {
|
|
17873
|
+
const pad = (value) => String(value).padStart(2, "0");
|
|
17874
|
+
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
|
|
17875
|
+
}
|
|
17876
|
+
/** The file name to offer for a draft: its opening line, scrubbed to what a
|
|
17877
|
+
* file name may hold. A draft that opens with punctuation or an emoji leaves
|
|
17878
|
+
* nothing usable, so those fall back to a timestamp the user can rename. */
|
|
17879
|
+
function defaultPromptName(draft, now = /* @__PURE__ */ new Date()) {
|
|
17880
|
+
const collapsed = (draft.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "").replace(FOREIGN_NAME_CHARS, " ").replace(/\s+/gu, " ").trim();
|
|
17881
|
+
const cut = collapsed.slice(0, MAX_NAME_CHARS);
|
|
17882
|
+
const boundary = collapsed.length > MAX_NAME_CHARS ? cut.search(/[\s\-_][^\s\-_]*$/u) : -1;
|
|
17883
|
+
const scrubbed = (boundary > 0 ? cut.slice(0, boundary) : cut).trim();
|
|
17884
|
+
return /^[\p{L}\p{N}]/u.test(scrubbed) ? scrubbed : `prompt-${stamp(now)}`;
|
|
17885
|
+
}
|
|
17886
|
+
//#endregion
|
|
17887
|
+
//#region src/client/ClaudePromptSaveAction.tsx
|
|
17888
|
+
const CARD_GAP = 8;
|
|
17889
|
+
const CARD_MARGIN = 12;
|
|
17890
|
+
/**
|
|
17891
|
+
* Place the card above its trigger, clamped into the viewport, and dismiss it
|
|
17892
|
+
* on a pointer that lands outside both.
|
|
17893
|
+
*
|
|
17894
|
+
* The primitives ship `useAnchoredPosition` and `useDismissOnOutsidePointer`
|
|
17895
|
+
* for exactly this, and both are used here through their published types —
|
|
17896
|
+
* but the Host's build of that package predates the `side` option of one and
|
|
17897
|
+
* the portal argument of the other. Neither omission is visible to `tsc`,
|
|
17898
|
+
* which reads this checkout's newer copy, and both are silent at runtime: the
|
|
17899
|
+
* card hangs below a composer pinned to the bottom of the window, and every
|
|
17900
|
+
* pointer landing in the naming field counts as outside and closes it. Owning
|
|
17901
|
+
* twenty lines beats behaviour that depends on which Desktop the plugin was
|
|
17902
|
+
* installed into.
|
|
17903
|
+
*/
|
|
17904
|
+
function useAnchoredCard(open, anchor, card, onDismiss) {
|
|
17905
|
+
const [position, setPosition] = useState();
|
|
17906
|
+
useLayoutEffect(() => {
|
|
17907
|
+
if (!open) {
|
|
17908
|
+
setPosition(void 0);
|
|
17909
|
+
return;
|
|
17910
|
+
}
|
|
17911
|
+
const place = () => {
|
|
17912
|
+
const trigger = anchor.current?.getBoundingClientRect();
|
|
17913
|
+
const panel = card.current?.getBoundingClientRect();
|
|
17914
|
+
if (trigger === void 0 || panel === void 0) return;
|
|
17915
|
+
const above = trigger.top - CARD_GAP - panel.height;
|
|
17916
|
+
setPosition({
|
|
17917
|
+
left: Math.max(CARD_MARGIN, Math.min(trigger.left, window.innerWidth - panel.width - CARD_MARGIN)),
|
|
17918
|
+
top: above >= CARD_MARGIN ? above : Math.min(trigger.bottom + CARD_GAP, window.innerHeight - panel.height - CARD_MARGIN)
|
|
17919
|
+
});
|
|
17920
|
+
};
|
|
17921
|
+
place();
|
|
17922
|
+
window.addEventListener("resize", place);
|
|
17923
|
+
window.addEventListener("scroll", place, true);
|
|
17924
|
+
return () => {
|
|
17925
|
+
window.removeEventListener("resize", place);
|
|
17926
|
+
window.removeEventListener("scroll", place, true);
|
|
17927
|
+
};
|
|
17928
|
+
}, [
|
|
17929
|
+
open,
|
|
17930
|
+
anchor,
|
|
17931
|
+
card
|
|
17932
|
+
]);
|
|
17933
|
+
useEffect(() => {
|
|
17934
|
+
if (!open) return void 0;
|
|
17935
|
+
const dismiss = (event) => {
|
|
17936
|
+
const target = event.target;
|
|
17937
|
+
if (!(target instanceof Node)) return;
|
|
17938
|
+
if (anchor.current?.contains(target) === true || card.current?.contains(target) === true) return;
|
|
17939
|
+
onDismiss();
|
|
17940
|
+
};
|
|
17941
|
+
document.addEventListener("pointerdown", dismiss);
|
|
17942
|
+
return () => {
|
|
17943
|
+
document.removeEventListener("pointerdown", dismiss);
|
|
17944
|
+
};
|
|
17945
|
+
}, [
|
|
17946
|
+
open,
|
|
17947
|
+
anchor,
|
|
17948
|
+
card,
|
|
17949
|
+
onDismiss
|
|
17950
|
+
]);
|
|
17951
|
+
return position;
|
|
17952
|
+
}
|
|
17953
|
+
/**
|
|
17954
|
+
* Keep the draft you just wrote, from the composer's own tool row.
|
|
17955
|
+
*
|
|
17956
|
+
* It sits beside the attach and access controls rather than in a row of its
|
|
17957
|
+
* own: a band above the composer moves the repository bar and the composer
|
|
17958
|
+
* itself every time a draft appears, and this is a once-in-a-while action that
|
|
17959
|
+
* has not earned that. The naming card is portaled and anchored, so opening it
|
|
17960
|
+
* displaces nothing either.
|
|
17961
|
+
*
|
|
17962
|
+
* The field opens on a name derived from the draft's first line, which costs
|
|
17963
|
+
* nothing and is there instantly, and a Claude-written name replaces it when
|
|
17964
|
+
* one arrives. That ordering is the whole naming design: the suggestion is an
|
|
17965
|
+
* improvement on a working answer, never something the user waits for.
|
|
17966
|
+
*/
|
|
17967
|
+
function ClaudePromptSaveAction({ t, useClaudeProjection, input, savePrompt = saveClaudePrompt, suggestName = suggestClaudePromptName }) {
|
|
17968
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
17969
|
+
const anchor = useRef(null);
|
|
17970
|
+
const panelRef = useRef(null);
|
|
17971
|
+
const suggestion = useRef(void 0);
|
|
17972
|
+
const [panel, setPanel] = useState();
|
|
17973
|
+
const [saving, setSaving] = useState(false);
|
|
17974
|
+
const close = useCallback(() => {
|
|
17975
|
+
suggestion.current?.abort();
|
|
17976
|
+
suggestion.current = void 0;
|
|
17977
|
+
setPanel(void 0);
|
|
17978
|
+
}, []);
|
|
17979
|
+
const position = useAnchoredCard(panel !== void 0, anchor, panelRef, close);
|
|
17980
|
+
useEffect(() => () => {
|
|
17981
|
+
suggestion.current?.abort();
|
|
17982
|
+
}, []);
|
|
17983
|
+
if (!owned) return null;
|
|
17984
|
+
const draft = input?.draft ?? "";
|
|
17985
|
+
const label = t("promptSave");
|
|
17986
|
+
const open = () => {
|
|
17987
|
+
setPanel({
|
|
17988
|
+
kind: "naming",
|
|
17989
|
+
name: defaultPromptName(draft),
|
|
17990
|
+
touched: false,
|
|
17991
|
+
suggesting: true
|
|
17992
|
+
});
|
|
17993
|
+
const attempt = new AbortController();
|
|
17994
|
+
suggestion.current = attempt;
|
|
17995
|
+
suggestName(draft, attempt.signal).then((suggested) => {
|
|
17996
|
+
if (attempt.signal.aborted) return;
|
|
17997
|
+
setPanel((current) => current?.kind !== "naming" ? current : {
|
|
17998
|
+
...current,
|
|
17999
|
+
suggesting: false,
|
|
18000
|
+
...suggested === void 0 || current.touched ? {} : { name: suggested }
|
|
18001
|
+
});
|
|
18002
|
+
});
|
|
18003
|
+
};
|
|
18004
|
+
const save = () => {
|
|
18005
|
+
const name = panel?.kind === "naming" ? panel.name.trim() : "";
|
|
18006
|
+
if (saving || name === "" || draft.trim() === "") return;
|
|
18007
|
+
setSaving(true);
|
|
18008
|
+
savePrompt(name, draft).then((prompt) => {
|
|
18009
|
+
setPanel({
|
|
18010
|
+
kind: "saved",
|
|
18011
|
+
prompt
|
|
18012
|
+
});
|
|
18013
|
+
}, (error) => {
|
|
18014
|
+
setPanel({
|
|
18015
|
+
kind: "naming",
|
|
18016
|
+
name,
|
|
18017
|
+
touched: true,
|
|
18018
|
+
suggesting: false,
|
|
18019
|
+
failure: error instanceof PluginRequestError && error.code === "name-taken" ? t("promptSaveExists") : t("promptSaveFailed", { message: error instanceof Error ? error.message : String(error) })
|
|
18020
|
+
});
|
|
18021
|
+
}).finally(() => {
|
|
18022
|
+
setSaving(false);
|
|
18023
|
+
});
|
|
18024
|
+
};
|
|
18025
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
18026
|
+
ref: anchor,
|
|
18027
|
+
style: {
|
|
18028
|
+
position: "relative",
|
|
18029
|
+
display: "inline-flex"
|
|
18030
|
+
},
|
|
18031
|
+
children: [
|
|
18032
|
+
/* @__PURE__ */ jsx("style", {
|
|
18033
|
+
"data-dsh-claude-prompt-save-styles": true,
|
|
18034
|
+
children: promptSaveTriggerCss
|
|
18035
|
+
}),
|
|
18036
|
+
/* @__PURE__ */ jsx(Tooltip, {
|
|
18037
|
+
label,
|
|
18038
|
+
side: "top",
|
|
18039
|
+
delayMs: 250,
|
|
18040
|
+
disabled: panel !== void 0,
|
|
18041
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
18042
|
+
type: "button",
|
|
18043
|
+
className: promptSaveTriggerClass,
|
|
18044
|
+
"aria-label": label,
|
|
18045
|
+
"aria-haspopup": "dialog",
|
|
18046
|
+
"aria-expanded": panel !== void 0,
|
|
18047
|
+
disabled: draft.trim() === "",
|
|
18048
|
+
onClick: () => {
|
|
18049
|
+
if (panel === void 0) open();
|
|
18050
|
+
else close();
|
|
18051
|
+
},
|
|
18052
|
+
children: /* @__PURE__ */ jsx(IconListPenOutline16, { size: 14 })
|
|
18053
|
+
})
|
|
18054
|
+
}),
|
|
18055
|
+
panel === void 0 || typeof document === "undefined" ? null : createPortal(/* @__PURE__ */ jsx("div", {
|
|
18056
|
+
ref: panelRef,
|
|
18057
|
+
style: {
|
|
18058
|
+
...promptSaveCard,
|
|
18059
|
+
...position
|
|
18060
|
+
},
|
|
18061
|
+
role: "dialog",
|
|
18062
|
+
"aria-label": label,
|
|
18063
|
+
children: panel.kind === "saved" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
18064
|
+
/* @__PURE__ */ jsx("span", { children: t("promptSaved", { name: panel.prompt.name }) }),
|
|
18065
|
+
/* @__PURE__ */ jsx("span", {
|
|
18066
|
+
style: promptSaveLocation,
|
|
18067
|
+
children: panel.prompt.location
|
|
18068
|
+
}),
|
|
18069
|
+
/* @__PURE__ */ jsx("span", {
|
|
18070
|
+
style: promptSaveActions,
|
|
18071
|
+
children: /* @__PURE__ */ jsx(Button, {
|
|
18072
|
+
variant: "primary",
|
|
18073
|
+
size: "sm",
|
|
18074
|
+
onClick: close,
|
|
18075
|
+
children: t("promptSaveDone")
|
|
18076
|
+
})
|
|
18077
|
+
})
|
|
18078
|
+
] }) : /* @__PURE__ */ jsxs("form", {
|
|
18079
|
+
style: { display: "contents" },
|
|
18080
|
+
onSubmit: (event) => {
|
|
18081
|
+
event.preventDefault();
|
|
18082
|
+
save();
|
|
18083
|
+
},
|
|
18084
|
+
children: [
|
|
18085
|
+
/* @__PURE__ */ jsx("span", {
|
|
18086
|
+
style: promptSaveHeading,
|
|
18087
|
+
children: panel.suggesting ? t("promptSaveNaming") : label
|
|
18088
|
+
}),
|
|
18089
|
+
/* @__PURE__ */ jsx("input", {
|
|
18090
|
+
className: promptSaveFieldClass,
|
|
18091
|
+
"aria-label": t("promptSaveName"),
|
|
18092
|
+
placeholder: t("promptSaveName"),
|
|
18093
|
+
value: panel.name,
|
|
18094
|
+
maxLength: 128,
|
|
18095
|
+
autoFocus: true,
|
|
18096
|
+
disabled: saving,
|
|
18097
|
+
onChange: (event) => setPanel({
|
|
18098
|
+
...panel,
|
|
18099
|
+
name: event.currentTarget.value,
|
|
18100
|
+
touched: true
|
|
18101
|
+
}),
|
|
18102
|
+
onKeyDown: (event) => {
|
|
18103
|
+
if (event.key === "Escape") close();
|
|
18104
|
+
}
|
|
18105
|
+
}),
|
|
18106
|
+
panel.failure === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
18107
|
+
style: promptSaveError,
|
|
18108
|
+
children: panel.failure
|
|
18109
|
+
}),
|
|
18110
|
+
/* @__PURE__ */ jsxs("span", {
|
|
18111
|
+
style: promptSaveActions,
|
|
18112
|
+
children: [/* @__PURE__ */ jsx(Button, {
|
|
18113
|
+
variant: "ghost",
|
|
18114
|
+
size: "sm",
|
|
18115
|
+
type: "button",
|
|
18116
|
+
disabled: saving,
|
|
18117
|
+
onClick: close,
|
|
18118
|
+
children: t("promptSaveCancel")
|
|
18119
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
18120
|
+
variant: "primary",
|
|
18121
|
+
size: "sm",
|
|
18122
|
+
type: "submit",
|
|
18123
|
+
disabled: saving,
|
|
18124
|
+
children: t("promptSaveConfirm")
|
|
18125
|
+
})]
|
|
18126
|
+
})
|
|
18127
|
+
]
|
|
18128
|
+
})
|
|
18129
|
+
}), document.body)
|
|
18130
|
+
]
|
|
18131
|
+
});
|
|
18132
|
+
}
|
|
18133
|
+
//#endregion
|
|
18134
|
+
//#region src/client/ClaudePromptRefineAction.tsx
|
|
18135
|
+
/**
|
|
18136
|
+
* Rewrite the draft in place, from the composer's own tool row.
|
|
18137
|
+
*
|
|
18138
|
+
* The rewrite replaces the draft outright, which is what makes the undo state
|
|
18139
|
+
* load-bearing rather than a nicety: `SessionInput.setDraft` documents itself
|
|
18140
|
+
* as "merged into history so a seed is not an undoable step of its own", so
|
|
18141
|
+
* Ctrl/Cmd+Z does NOT bring the original back. Without somewhere to put it,
|
|
18142
|
+
* one press of this button would silently destroy a draft the user may have
|
|
18143
|
+
* spent minutes on. So the original is held for exactly as long as it is still
|
|
18144
|
+
* recoverable — until the rewrite is edited or sent — and the same button
|
|
18145
|
+
* offers it back over that window.
|
|
18146
|
+
*/
|
|
18147
|
+
function ClaudePromptRefineAction({ t, useClaudeProjection, input, replaceDraft, notify, refine = refineClaudePrompt }) {
|
|
18148
|
+
const owned = useClaudeProjection((projection) => projection.owned);
|
|
18149
|
+
const attempt = useRef(void 0);
|
|
18150
|
+
const [busy, setBusy] = useState(false);
|
|
18151
|
+
const [applied, setApplied] = useState();
|
|
18152
|
+
useEffect(() => () => {
|
|
18153
|
+
attempt.current?.abort();
|
|
18154
|
+
}, []);
|
|
18155
|
+
if (!owned || replaceDraft === void 0) return null;
|
|
18156
|
+
const draft = input?.draft ?? "";
|
|
18157
|
+
const undoable = applied !== void 0 && draft === applied.refined;
|
|
18158
|
+
const label = busy ? t("promptRefineBusy") : undoable ? t("promptRefineUndo") : t("promptRefine");
|
|
18159
|
+
const run = () => {
|
|
18160
|
+
if (busy || draft.trim() === "") return;
|
|
18161
|
+
setBusy(true);
|
|
18162
|
+
const running = new AbortController();
|
|
18163
|
+
attempt.current = running;
|
|
18164
|
+
refine(draft, running.signal).then((text) => {
|
|
18165
|
+
if (running.signal.aborted) return;
|
|
18166
|
+
setApplied({
|
|
18167
|
+
original: draft,
|
|
18168
|
+
refined: text
|
|
18169
|
+
});
|
|
18170
|
+
replaceDraft(text);
|
|
18171
|
+
}, (error) => {
|
|
18172
|
+
if (running.signal.aborted) return;
|
|
18173
|
+
notify?.("error", t("promptRefineFailed", { message: error instanceof Error ? error.message : String(error) }));
|
|
18174
|
+
}).finally(() => {
|
|
18175
|
+
setBusy(false);
|
|
18176
|
+
});
|
|
18177
|
+
};
|
|
18178
|
+
const undo = () => {
|
|
18179
|
+
if (applied === void 0) return;
|
|
18180
|
+
replaceDraft(applied.original);
|
|
18181
|
+
setApplied(void 0);
|
|
18182
|
+
};
|
|
18183
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
18184
|
+
label,
|
|
18185
|
+
side: "top",
|
|
18186
|
+
delayMs: 250,
|
|
18187
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
18188
|
+
type: "button",
|
|
18189
|
+
className: promptSaveTriggerClass,
|
|
18190
|
+
"aria-label": label,
|
|
18191
|
+
disabled: busy || !undoable && draft.trim() === "",
|
|
18192
|
+
onClick: undoable ? undo : run,
|
|
18193
|
+
children: [/* @__PURE__ */ jsx("style", {
|
|
18194
|
+
"data-dsh-claude-prompt-refine-styles": true,
|
|
18195
|
+
children: promptSaveTriggerCss
|
|
18196
|
+
}), busy ? /* @__PURE__ */ jsx(IconLoadingOutline16, {
|
|
18197
|
+
size: 14,
|
|
18198
|
+
className: promptSpinClass
|
|
18199
|
+
}) : undoable ? /* @__PURE__ */ jsx(IconRefreshOutline16, {
|
|
18200
|
+
size: 14,
|
|
18201
|
+
className: promptUndoClass
|
|
18202
|
+
}) : /* @__PURE__ */ jsx(IconSparkle16, { size: 14 })]
|
|
18203
|
+
})
|
|
18204
|
+
});
|
|
18205
|
+
}
|
|
18206
|
+
//#endregion
|
|
18207
|
+
//#region src/client/claude-prompt-source.ts
|
|
18208
|
+
/**
|
|
18209
|
+
* The user's own prompt snippets as a second `/` group.
|
|
18210
|
+
*
|
|
18211
|
+
* Unlike {@link createClaudeCommandSource}, a pick here settles as plain text:
|
|
18212
|
+
* the pipeline replaces the trigger token with the snippet body and leaves the
|
|
18213
|
+
* caret after it, so the draft stays editable and nothing is sent. A snippet is
|
|
18214
|
+
* a half-written message, not a command.
|
|
18215
|
+
*/
|
|
18216
|
+
function createClaudePromptSource(groupName, load = claudePrompts) {
|
|
18217
|
+
let known = [];
|
|
18218
|
+
return {
|
|
18219
|
+
trigger: "/",
|
|
18220
|
+
name: groupName,
|
|
18221
|
+
order: 20,
|
|
18222
|
+
async candidates(_session, request) {
|
|
18223
|
+
if (request.position !== "leading") return [];
|
|
18224
|
+
known = await load();
|
|
18225
|
+
const query = request.query.toLocaleLowerCase();
|
|
18226
|
+
return known.filter((prompt) => prompt.name.toLocaleLowerCase().includes(query)).map((prompt) => ({
|
|
18227
|
+
name: prompt.name,
|
|
18228
|
+
description: prompt.description
|
|
18229
|
+
}));
|
|
18230
|
+
},
|
|
18231
|
+
onPick(pick) {
|
|
18232
|
+
const prompt = known.find((item) => item.name === pick.candidate.name);
|
|
18233
|
+
return prompt === void 0 ? void 0 : { text: prompt.body };
|
|
18234
|
+
}
|
|
18235
|
+
};
|
|
18236
|
+
}
|
|
18237
|
+
//#endregion
|
|
16794
18238
|
//#region src/client/preset-seat-mark.ts
|
|
16795
18239
|
/** Flags the Host's agent-preset seat while it names the Claude preset.
|
|
16796
18240
|
*
|
|
@@ -17089,7 +18533,7 @@ window.__ModuleLoader__.load({
|
|
|
17089
18533
|
worktreeBranchPrefixEffect: "分支前缀修改会应用于之后自动创建的 Worktree 分支;显式指定的完整分支名不会被修改。",
|
|
17090
18534
|
maxProcessesSetting: "Claude 进程上限",
|
|
17091
18535
|
idleTimeoutSetting: "闲置回收时长(分钟)",
|
|
17092
|
-
maxProcessesEffect: "
|
|
18536
|
+
maxProcessesEffect: "保存后立即回收超额空闲进程;运行中的进程完成后再收敛。满载时,新会话会等待可用容量。留空或非法值会被拒绝。",
|
|
17093
18537
|
idleTimeoutEffect: "闲置回收时长在下一次回合结束后生效。留空或非法值会被拒绝。",
|
|
17094
18538
|
settingHint: "查看说明",
|
|
17095
18539
|
pluginUpdate: "插件更新",
|
|
@@ -17222,10 +18666,45 @@ window.__ModuleLoader__.load({
|
|
|
17222
18666
|
yes: "是",
|
|
17223
18667
|
no: "否",
|
|
17224
18668
|
diffOpen: "查看分支改动",
|
|
18669
|
+
planPanelTitle: "Plan",
|
|
18670
|
+
planClose: "关闭方案面板",
|
|
18671
|
+
planOpen: "查看方案",
|
|
18672
|
+
planMaximize: "最大化方案面板",
|
|
18673
|
+
planRestore: "还原方案面板",
|
|
18674
|
+
planNth: "{index} / {total}",
|
|
18675
|
+
planHistory: "本会话的方案",
|
|
18676
|
+
planNotePlaceholder: "写下要改的地方,Enter 记一条,⌘/Ctrl+Enter 发送",
|
|
18677
|
+
planNoteQuotedPlaceholder: "针对选中的这段,要改成什么?",
|
|
18678
|
+
planNoteHint: "在正文里选中一段可以引用它",
|
|
18679
|
+
planNoteRemove: "删掉这条意见",
|
|
18680
|
+
planQuoteClear: "取消引用",
|
|
18681
|
+
planSendForChanges: "发送并让 AI 修改",
|
|
18682
|
+
planSending: "发送中…",
|
|
18683
|
+
planSettled: "这个方案已经在审批弹框里被决定了,意见没能发出。",
|
|
18684
|
+
planFeedbackFailed: "意见发送失败,请重试。",
|
|
18685
|
+
planPending: "待审批",
|
|
18686
|
+
planApproved: "已批准",
|
|
18687
|
+
planRejected: "已拒绝",
|
|
18688
|
+
planEmpty: "本会话还没有待查看的方案。",
|
|
18689
|
+
planPendingHint: "在 DSH 的审批弹框里批准或拒绝这个方案。",
|
|
17225
18690
|
diffClose: "关闭 Diff 面板",
|
|
17226
18691
|
sessionMenu: "会话菜单",
|
|
17227
18692
|
sessionMenuOpenIn: "打开方式",
|
|
17228
18693
|
sessionMenuOpenFailed: "无法打开:{message}",
|
|
18694
|
+
promptSource: "常用提示词",
|
|
18695
|
+
promptSave: "存为常用提示词",
|
|
18696
|
+
promptSaveName: "名字",
|
|
18697
|
+
promptSaveNaming: "正在起名…",
|
|
18698
|
+
promptRefine: "AI 优化提示词",
|
|
18699
|
+
promptRefineBusy: "优化中…",
|
|
18700
|
+
promptRefineUndo: "还原成优化前的内容",
|
|
18701
|
+
promptRefineFailed: "优化失败:{message}",
|
|
18702
|
+
promptSaveConfirm: "保存",
|
|
18703
|
+
promptSaveCancel: "取消",
|
|
18704
|
+
promptSaveDone: "知道了",
|
|
18705
|
+
promptSaved: "已存为「{name}」",
|
|
18706
|
+
promptSaveExists: "已有同名提示词,换个名字。",
|
|
18707
|
+
promptSaveFailed: "保存失败:{message}",
|
|
17229
18708
|
presetHeaderHint: "当前会话运行的 Agent 预设",
|
|
17230
18709
|
diffWorkingTree: "分支改动",
|
|
17231
18710
|
diffFiles: "{count} 个已修改文件",
|
|
@@ -17403,6 +18882,17 @@ window.__ModuleLoader__.load({
|
|
|
17403
18882
|
rewindFailed: "回退失败({code})。",
|
|
17404
18883
|
rewindBusy: "这个会话正在运行,请等本轮结束后再回退。",
|
|
17405
18884
|
rewindStale: "宿主进程里的插件还是旧版本(没有回退接口)。重启 DSH 后再试。",
|
|
18885
|
+
alerts: "会话提醒",
|
|
18886
|
+
alertsOff: "关闭",
|
|
18887
|
+
alertsOn: "开启",
|
|
18888
|
+
alertsEffect: "当另一个 Claude 会话需要你——等待权限确认、等待回答,或者这一轮跑完了——弹出系统通知。正在看的那个会话不会提醒。点击通知会切到对应会话。首次使用时系统会询问通知权限;拒绝后不再提醒。改动立即生效。",
|
|
18889
|
+
alertNeedsPermission: "等待你确认权限",
|
|
18890
|
+
alertNeedsAnswer: "等待你回答",
|
|
18891
|
+
alertTurnFinished: "这一轮跑完了",
|
|
18892
|
+
alertFallbackTitle: "Claude 会话",
|
|
18893
|
+
rewindRestoreFiles: "同时把文件改回这一轮开始前的样子",
|
|
18894
|
+
rewindRestoreFilesHint: "被删除的内容会从这个会话中隐藏,Claude 也会忘记它们;工作区会回到这一轮开始前的状态——这之后新建的文件会被删除,被改动的文件会被还原,被 .gitignore 忽略的文件不受影响。消息原文会放回输入框,方便修改后重新发送。",
|
|
18895
|
+
rewindFilesUnavailable: "对话已回退,但文件没有还原:这一轮没有留下工作区快照。",
|
|
17406
18896
|
turnUsage: "本回合用量",
|
|
17407
18897
|
turnUsageTokens: "{count} tok",
|
|
17408
18898
|
turnUsageCache: "缓存命中 {percent}%",
|
|
@@ -17467,7 +18957,7 @@ window.__ModuleLoader__.load({
|
|
|
17467
18957
|
worktreeBranchPrefixEffect: "Prefix changes apply to subsequently generated Worktree branches. Explicit full branch names remain unchanged.",
|
|
17468
18958
|
maxProcessesSetting: "Claude process limit",
|
|
17469
18959
|
idleTimeoutSetting: "Idle timeout (minutes)",
|
|
17470
|
-
maxProcessesEffect: "
|
|
18960
|
+
maxProcessesEffect: "Saving immediately reclaims excess idle processes; running processes converge after they finish. New sessions wait for capacity while every process is busy. Blank or invalid values are rejected.",
|
|
17471
18961
|
idleTimeoutEffect: "The idle timeout applies after the next completed turn. Blank or invalid values are rejected.",
|
|
17472
18962
|
settingHint: "Show details",
|
|
17473
18963
|
pluginUpdate: "Plugin updates",
|
|
@@ -17600,10 +19090,45 @@ window.__ModuleLoader__.load({
|
|
|
17600
19090
|
yes: "Yes",
|
|
17601
19091
|
no: "No",
|
|
17602
19092
|
diffOpen: "View branch changes",
|
|
19093
|
+
planPanelTitle: "Plan",
|
|
19094
|
+
planClose: "Close plan panel",
|
|
19095
|
+
planOpen: "View plan",
|
|
19096
|
+
planMaximize: "Maximize plan panel",
|
|
19097
|
+
planRestore: "Restore plan panel",
|
|
19098
|
+
planNth: "{index} / {total}",
|
|
19099
|
+
planHistory: "Plans in this session",
|
|
19100
|
+
planNotePlaceholder: "What should change? Enter files a note, ⌘/Ctrl+Enter sends",
|
|
19101
|
+
planNoteQuotedPlaceholder: "What should change about the selected passage?",
|
|
19102
|
+
planNoteHint: "Select a passage above to quote it",
|
|
19103
|
+
planNoteRemove: "Remove this note",
|
|
19104
|
+
planQuoteClear: "Clear the quote",
|
|
19105
|
+
planSendForChanges: "Send for changes",
|
|
19106
|
+
planSending: "Sending…",
|
|
19107
|
+
planSettled: "This plan was already decided in the approval dialog; the notes were not sent.",
|
|
19108
|
+
planFeedbackFailed: "The notes could not be sent. Try again.",
|
|
19109
|
+
planPending: "Awaiting approval",
|
|
19110
|
+
planApproved: "Approved",
|
|
19111
|
+
planRejected: "Rejected",
|
|
19112
|
+
planEmpty: "This session has no plan to read yet.",
|
|
19113
|
+
planPendingHint: "Approve or reject this plan in the DSH approval dialog.",
|
|
17603
19114
|
diffClose: "Close diff panel",
|
|
17604
19115
|
sessionMenu: "Session menu",
|
|
17605
19116
|
sessionMenuOpenIn: "Open in",
|
|
17606
19117
|
sessionMenuOpenFailed: "Could not open: {message}",
|
|
19118
|
+
promptSource: "Prompts",
|
|
19119
|
+
promptSave: "Save as a prompt",
|
|
19120
|
+
promptSaveName: "Name",
|
|
19121
|
+
promptSaveNaming: "Naming it…",
|
|
19122
|
+
promptRefine: "Rewrite with AI",
|
|
19123
|
+
promptRefineBusy: "Rewriting…",
|
|
19124
|
+
promptRefineUndo: "Put the original back",
|
|
19125
|
+
promptRefineFailed: "Could not rewrite: {message}",
|
|
19126
|
+
promptSaveConfirm: "Save",
|
|
19127
|
+
promptSaveCancel: "Cancel",
|
|
19128
|
+
promptSaveDone: "Got it",
|
|
19129
|
+
promptSaved: "Saved as \"{name}\"",
|
|
19130
|
+
promptSaveExists: "A prompt with that name already exists. Try another.",
|
|
19131
|
+
promptSaveFailed: "Could not save: {message}",
|
|
17607
19132
|
presetHeaderHint: "The agent preset this session runs",
|
|
17608
19133
|
diffWorkingTree: "Branch changes",
|
|
17609
19134
|
diffFiles: "{count} modified file(s)",
|
|
@@ -17781,6 +19306,17 @@ window.__ModuleLoader__.load({
|
|
|
17781
19306
|
rewindFailed: "The rewind failed ({code}).",
|
|
17782
19307
|
rewindBusy: "This session is running; wait for the turn to finish before rewinding.",
|
|
17783
19308
|
rewindStale: "The Host process is still running the previous plugin build, which has no rewind route. Restart DSH and try again.",
|
|
19309
|
+
alerts: "Session alerts",
|
|
19310
|
+
alertsOff: "Off",
|
|
19311
|
+
alertsOn: "On",
|
|
19312
|
+
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.",
|
|
19313
|
+
alertNeedsPermission: "Waiting on your approval",
|
|
19314
|
+
alertNeedsAnswer: "Waiting on your answer",
|
|
19315
|
+
alertTurnFinished: "Finished its turn",
|
|
19316
|
+
alertFallbackTitle: "Claude session",
|
|
19317
|
+
rewindRestoreFiles: "Also put the files back to where this turn found them",
|
|
19318
|
+
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.",
|
|
19319
|
+
rewindFilesUnavailable: "The conversation was rewound, but the files were left alone: that turn recorded no working-tree snapshot.",
|
|
17784
19320
|
turnUsage: "Turn usage",
|
|
17785
19321
|
turnUsageTokens: "{count} tok",
|
|
17786
19322
|
turnUsageCache: "Cache hit {percent}%",
|
|
@@ -17792,7 +19328,7 @@ window.__ModuleLoader__.load({
|
|
|
17792
19328
|
function MaximizedDiff({ source, t, sessionId, closeDetails, restore, submitPrompt }) {
|
|
17793
19329
|
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
17794
19330
|
const useClaudeProjection = (selector) => selector(snapshot);
|
|
17795
|
-
return /* @__PURE__ */ jsx(
|
|
19331
|
+
return /* @__PURE__ */ jsx(ClaudePanelOverlay, {
|
|
17796
19332
|
onRestore: restore,
|
|
17797
19333
|
children: /* @__PURE__ */ jsx(ClaudeDiffPanel, {
|
|
17798
19334
|
useClaudeProjection,
|
|
@@ -17806,6 +19342,21 @@ window.__ModuleLoader__.load({
|
|
|
17806
19342
|
});
|
|
17807
19343
|
}
|
|
17808
19344
|
const name = "dsh-claude-client";
|
|
19345
|
+
function MaximizedPlan({ source, t, sessionId, closeDetails, restore }) {
|
|
19346
|
+
const snapshot = useSyncExternalStore(source.subscribe, source.getSnapshot, source.getSnapshot);
|
|
19347
|
+
const useClaudeProjection = (selector) => selector(snapshot);
|
|
19348
|
+
return /* @__PURE__ */ jsx(ClaudePanelOverlay, {
|
|
19349
|
+
onRestore: restore,
|
|
19350
|
+
children: /* @__PURE__ */ jsx(ClaudePlanPanel, {
|
|
19351
|
+
useClaudeProjection,
|
|
19352
|
+
t,
|
|
19353
|
+
sessionId,
|
|
19354
|
+
maximized: true,
|
|
19355
|
+
closeDetails,
|
|
19356
|
+
toggleMaximized: restore
|
|
19357
|
+
})
|
|
19358
|
+
});
|
|
19359
|
+
}
|
|
17809
19360
|
const inject = [
|
|
17810
19361
|
"slots",
|
|
17811
19362
|
"locale",
|
|
@@ -17847,12 +19398,15 @@ window.__ModuleLoader__.load({
|
|
|
17847
19398
|
const t = ctx.locale.bind(namespace);
|
|
17848
19399
|
ctx.effect(() => restyleHostChrome(), "dsh-claude: Host chrome restyling");
|
|
17849
19400
|
pluginRead(CLAUDE_GLOBAL_SETTINGS_PATH, "fast").then((payload) => {
|
|
17850
|
-
if (isGlobalSettingsView(payload))
|
|
19401
|
+
if (!isGlobalSettingsView(payload)) return;
|
|
19402
|
+
applyClaudeMarkdownTheme(proseModeOf(payload.settings));
|
|
19403
|
+
setClaudeAlertsEnabled(alertModeOf(payload.settings) === "on");
|
|
17851
19404
|
}).catch(() => {});
|
|
17852
19405
|
const projections = new ClaudeProjectionStore({ report: (kind, detail) => {
|
|
17853
19406
|
diagnostics.report(kind, detail);
|
|
17854
19407
|
} });
|
|
17855
19408
|
ctx.effect(() => ctx.inputTriggers.registerSource(createClaudeCommandSource(ctx, projections)), "dsh-claude: Claude slash source");
|
|
19409
|
+
ctx.effect(() => ctx.inputTriggers.registerSource(createClaudePromptSource(t("promptSource"))), "dsh-claude: Claude prompt source");
|
|
17856
19410
|
const sessions = ctx.get("sessions");
|
|
17857
19411
|
const workspaces = ctx.get("workspaces");
|
|
17858
19412
|
const uiWorkspace = ctx.get("uiWorkspace");
|
|
@@ -17892,6 +19446,17 @@ window.__ModuleLoader__.load({
|
|
|
17892
19446
|
resolve: (binding) => ({ hooks: { claudeProjection: projections.source(binding.sessionId) } })
|
|
17893
19447
|
}), "dsh-claude: sidecar projection provider");
|
|
17894
19448
|
ctx.effect(() => () => projections.dispose(), "dsh-claude: sidecar projection lifecycle");
|
|
19449
|
+
if (sessions !== void 0) ctx.effect(() => startClaudeSessionAlerts({
|
|
19450
|
+
sessions: {
|
|
19451
|
+
subscribe: (listener) => sessions.list.subscribe(listener),
|
|
19452
|
+
getSnapshot: () => sessions.list.getSnapshot()
|
|
19453
|
+
},
|
|
19454
|
+
projectionFor: (id) => projections.source(id),
|
|
19455
|
+
open: (id) => {
|
|
19456
|
+
sessions.open(id);
|
|
19457
|
+
},
|
|
19458
|
+
t
|
|
19459
|
+
}), "dsh-claude: session alerts");
|
|
17895
19460
|
const uiConversation = ctx.get("uiConversation");
|
|
17896
19461
|
if (uiConversation !== void 0) {
|
|
17897
19462
|
ctx.effect(() => uiConversation.events.register(claudeTurnDefinition), "dsh-claude: Claude turn marker");
|
|
@@ -17906,9 +19471,11 @@ window.__ModuleLoader__.load({
|
|
|
17906
19471
|
const layout = ctx.get("layout");
|
|
17907
19472
|
let disposePluginDetails;
|
|
17908
19473
|
let disposeDiffOverlay;
|
|
19474
|
+
let disposePlanOverlay;
|
|
17909
19475
|
let disposeExpandedDetailsResize;
|
|
17910
19476
|
let detailsSessionId;
|
|
17911
|
-
const diffOpen = new
|
|
19477
|
+
const diffOpen = new PanelOpenStore();
|
|
19478
|
+
const planOpen = new PanelOpenStore();
|
|
17912
19479
|
const restoreDiff = () => {
|
|
17913
19480
|
if (disposeDiffOverlay === void 0) return;
|
|
17914
19481
|
disposeDiffOverlay();
|
|
@@ -17916,16 +19483,26 @@ window.__ModuleLoader__.load({
|
|
|
17916
19483
|
layout?.openDetails();
|
|
17917
19484
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17918
19485
|
};
|
|
19486
|
+
const restorePlan = () => {
|
|
19487
|
+
if (disposePlanOverlay === void 0) return;
|
|
19488
|
+
disposePlanOverlay();
|
|
19489
|
+
disposePlanOverlay = void 0;
|
|
19490
|
+
layout?.openDetails();
|
|
19491
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
19492
|
+
};
|
|
17919
19493
|
const closePluginDetails = () => {
|
|
17920
|
-
if (disposePluginDetails === void 0 && disposeDiffOverlay === void 0 && disposeExpandedDetailsResize === void 0 && detailsSessionId === void 0) return;
|
|
19494
|
+
if (disposePluginDetails === void 0 && disposeDiffOverlay === void 0 && disposePlanOverlay === void 0 && disposeExpandedDetailsResize === void 0 && detailsSessionId === void 0) return;
|
|
17921
19495
|
disposeDiffOverlay?.();
|
|
17922
19496
|
disposeDiffOverlay = void 0;
|
|
19497
|
+
disposePlanOverlay?.();
|
|
19498
|
+
disposePlanOverlay = void 0;
|
|
17923
19499
|
disposeExpandedDetailsResize?.();
|
|
17924
19500
|
disposeExpandedDetailsResize = void 0;
|
|
17925
19501
|
disposePluginDetails?.();
|
|
17926
19502
|
disposePluginDetails = void 0;
|
|
17927
19503
|
detailsSessionId = void 0;
|
|
17928
19504
|
diffOpen.close();
|
|
19505
|
+
planOpen.close();
|
|
17929
19506
|
layout?.closeDetails();
|
|
17930
19507
|
};
|
|
17931
19508
|
ctx.effect(() => ctx.slots.onEntryError((key, entry, error) => {
|
|
@@ -17934,6 +19511,7 @@ window.__ModuleLoader__.load({
|
|
|
17934
19511
|
${error.stack ?? ""}` : String(error);
|
|
17935
19512
|
diagnostics.report("slot-entry-crashed", `slot "${key}"${id}: ${message}`);
|
|
17936
19513
|
if (key === "shell.overlay" && entry.options.id === "claude-diff-overlay") restoreDiff();
|
|
19514
|
+
if (key === "shell.overlay" && entry.options.id === "claude-plan-overlay") restorePlan();
|
|
17937
19515
|
}), "dsh-claude: Slot entry failure reporting");
|
|
17938
19516
|
const openTasksPanel = (sessionId, turn) => {
|
|
17939
19517
|
closePluginDetails();
|
|
@@ -17955,6 +19533,55 @@ window.__ModuleLoader__.load({
|
|
|
17955
19533
|
layout?.openDetails();
|
|
17956
19534
|
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
17957
19535
|
};
|
|
19536
|
+
const openPlanPanel = (sessionId) => {
|
|
19537
|
+
closePluginDetails();
|
|
19538
|
+
const maximizePlan = () => {
|
|
19539
|
+
if (disposePlanOverlay !== void 0) {
|
|
19540
|
+
restorePlan();
|
|
19541
|
+
return;
|
|
19542
|
+
}
|
|
19543
|
+
disposeExpandedDetailsResize?.();
|
|
19544
|
+
disposeExpandedDetailsResize = void 0;
|
|
19545
|
+
layout?.closeDetails();
|
|
19546
|
+
try {
|
|
19547
|
+
disposePlanOverlay = ctx.slots.register({
|
|
19548
|
+
name: "shell.overlay",
|
|
19549
|
+
id: "claude-plan-overlay",
|
|
19550
|
+
locale: namespace
|
|
19551
|
+
}, () => /* @__PURE__ */ jsx(MaximizedPlan, {
|
|
19552
|
+
source: projections.source(sessionId),
|
|
19553
|
+
t,
|
|
19554
|
+
sessionId,
|
|
19555
|
+
closeDetails: closePluginDetails,
|
|
19556
|
+
restore: restorePlan
|
|
19557
|
+
}));
|
|
19558
|
+
} catch {
|
|
19559
|
+
disposePlanOverlay = void 0;
|
|
19560
|
+
layout?.openDetails();
|
|
19561
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
19562
|
+
}
|
|
19563
|
+
};
|
|
19564
|
+
try {
|
|
19565
|
+
disposePluginDetails = ctx.slots.register({
|
|
19566
|
+
name: "details",
|
|
19567
|
+
priority: -10,
|
|
19568
|
+
locale: namespace,
|
|
19569
|
+
inject: () => ({
|
|
19570
|
+
t,
|
|
19571
|
+
sessionId,
|
|
19572
|
+
closeDetails: closePluginDetails,
|
|
19573
|
+
maximized: false,
|
|
19574
|
+
toggleMaximized: maximizePlan
|
|
19575
|
+
})
|
|
19576
|
+
}, ClaudePlanPanel);
|
|
19577
|
+
} catch {
|
|
19578
|
+
return;
|
|
19579
|
+
}
|
|
19580
|
+
detailsSessionId = sessionId;
|
|
19581
|
+
planOpen.open(sessionId);
|
|
19582
|
+
layout?.openDetails();
|
|
19583
|
+
disposeExpandedDetailsResize = enableExpandedDetailsResize();
|
|
19584
|
+
};
|
|
17958
19585
|
const openOverviewPanel = (sessionId) => {
|
|
17959
19586
|
if (sessions === void 0) return;
|
|
17960
19587
|
closePluginDetails();
|
|
@@ -18045,7 +19672,7 @@ window.__ModuleLoader__.load({
|
|
|
18045
19672
|
ctx.effect(() => {
|
|
18046
19673
|
if (typeof document === "undefined" || typeof MutationObserver === "undefined") return () => closePluginDetails();
|
|
18047
19674
|
const observer = new MutationObserver(() => {
|
|
18048
|
-
if (detailsSessionId !== void 0 && disposeDiffOverlay === void 0 && document.querySelector("[data-details-collapsed]") !== null) closePluginDetails();
|
|
19675
|
+
if (detailsSessionId !== void 0 && disposeDiffOverlay === void 0 && disposePlanOverlay === void 0 && document.querySelector("[data-details-collapsed]") !== null) closePluginDetails();
|
|
18049
19676
|
});
|
|
18050
19677
|
observer.observe(document.body, {
|
|
18051
19678
|
attributes: true,
|
|
@@ -18091,6 +19718,20 @@ window.__ModuleLoader__.load({
|
|
|
18091
19718
|
})
|
|
18092
19719
|
}, ClaudeAgentPresetLabel));
|
|
18093
19720
|
}
|
|
19721
|
+
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
19722
|
+
name: "conversation.session.header.utilities",
|
|
19723
|
+
id: "claude-plan",
|
|
19724
|
+
order: 29,
|
|
19725
|
+
locale: namespace,
|
|
19726
|
+
inject: (sessionId) => ({
|
|
19727
|
+
t,
|
|
19728
|
+
togglePlan: () => {
|
|
19729
|
+
if (planOpen.isOpen(sessionId)) closePluginDetails();
|
|
19730
|
+
else openPlanPanel(sessionId);
|
|
19731
|
+
},
|
|
19732
|
+
planOpen: planOpen.sourceFor(sessionId)
|
|
19733
|
+
})
|
|
19734
|
+
}, ClaudePlanHeaderAction));
|
|
18094
19735
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
18095
19736
|
name: "conversation.session.header.utilities",
|
|
18096
19737
|
id: "claude-diff",
|
|
@@ -18112,6 +19753,34 @@ window.__ModuleLoader__.load({
|
|
|
18112
19753
|
locale: namespace,
|
|
18113
19754
|
inject: () => ({ t })
|
|
18114
19755
|
}, ClaudeSessionMenu));
|
|
19756
|
+
ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
|
|
19757
|
+
name: "conversation.input.left",
|
|
19758
|
+
id: "claude-prompt-save",
|
|
19759
|
+
order: 40,
|
|
19760
|
+
locale: namespace,
|
|
19761
|
+
inject: () => ({ t })
|
|
19762
|
+
}, ClaudePromptSaveAction));
|
|
19763
|
+
ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
|
|
19764
|
+
name: "conversation.input.left",
|
|
19765
|
+
id: "claude-prompt-refine",
|
|
19766
|
+
order: 41,
|
|
19767
|
+
locale: namespace,
|
|
19768
|
+
inject: (sessionId) => {
|
|
19769
|
+
if (sessions === void 0 || conversation === void 0) return { t };
|
|
19770
|
+
const scope = sessions.scope(sessionId);
|
|
19771
|
+
if (scope === void 0) return { t };
|
|
19772
|
+
const facade = sessionInput(conversation, scope);
|
|
19773
|
+
return {
|
|
19774
|
+
t,
|
|
19775
|
+
replaceDraft: (text) => {
|
|
19776
|
+
facade.setDraft(text);
|
|
19777
|
+
},
|
|
19778
|
+
notify: (level, text) => {
|
|
19779
|
+
facade.notify(level, text);
|
|
19780
|
+
}
|
|
19781
|
+
};
|
|
19782
|
+
}
|
|
19783
|
+
}, ClaudePromptRefineAction));
|
|
18115
19784
|
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
18116
19785
|
name: "conversation.input.dock",
|
|
18117
19786
|
id: "claude-review-comments",
|