@norman-else/dsh-claude 0.1.41 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/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
@@ -28,6 +28,9 @@ window.__ModuleLoader__.load({
28
28
  const CLAUDE_EDITOR_OPEN_PATH = "/plugins/dsh-claude/editor/open";
29
29
  const CLAUDE_REWIND_PATH = "/plugins/dsh-claude/rewind";
30
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";
31
34
  function isClaudeRenderMode(value) {
32
35
  return value === "plugin" || value === "native";
33
36
  }
@@ -349,6 +352,22 @@ window.__ModuleLoader__.load({
349
352
  function nativelyRenderedStep(activities, turn, step) {
350
353
  return activities.some((activity) => activity.turn === turn && activity.step === step && activity.renderer === "native");
351
354
  }
355
+ /** The turn's own accounting, drawn as a footer.
356
+ *
357
+ * It hangs off the turn rather than the step that reported it: a turn waiting
358
+ * on background tasks reports usage per settled segment, and the task badge
359
+ * the plugin draws at the turn's foot would otherwise sit *under* a line that
360
+ * reads as a closing total. Every report is cumulative, so the newest one
361
+ * supersedes the ones before it. Natively drawn steps keep the Host's footer.
362
+ */
363
+ function latestTurnUsage(activities, turn) {
364
+ let latest;
365
+ for (const activity of activities) {
366
+ if (activity.turn !== turn || activity.kind !== "usage" || activity.renderer === "native") continue;
367
+ if (activity.usage !== void 0) latest = activity.usage;
368
+ }
369
+ return latest;
370
+ }
352
371
  /** Fold one step's shared ordinal stream into Claude Code-style prose and tool groups. */
353
372
  function transcriptItemsForStep(activities, turn, step, tasks = []) {
354
373
  if (nativelyRenderedStep(activities, turn, step)) return [];
@@ -410,15 +429,6 @@ window.__ModuleLoader__.load({
410
429
  });
411
430
  continue;
412
431
  }
413
- if (activity.kind === "usage" && activity.usage !== void 0) {
414
- flushGroup();
415
- items.push({
416
- kind: "usage",
417
- ordinal: activity.ordinal,
418
- usage: activity.usage
419
- });
420
- continue;
421
- }
422
432
  if (activity.kind === "tool-call" && activity.toolUseId !== void 0 && activity.toolName !== void 0) {
423
433
  group ??= {
424
434
  ordinal: activity.ordinal,
@@ -1834,7 +1844,7 @@ window.__ModuleLoader__.load({
1834
1844
  whiteSpace: "nowrap"
1835
1845
  };
1836
1846
  const repositoryBarFrame = {
1837
- width: "calc(100% - 64px)",
1847
+ width: "calc(100% - 2 * var(--dsh-composer-side-clearance, 16px))",
1838
1848
  maxWidth: "var(--dsh-composer-card-max-width, 782px)",
1839
1849
  minWidth: 0,
1840
1850
  margin: "0 auto",
@@ -2231,6 +2241,14 @@ window.__ModuleLoader__.load({
2231
2241
  whiteSpace: "nowrap",
2232
2242
  cursor: "pointer"
2233
2243
  };
2244
+ /** A stopped rebase is the one bar control that reports a blocked checkout
2245
+ * rather than an available action, so it carries the warning tone. */
2246
+ const repositoryConflictTrigger = {
2247
+ ...repositoryUpdateTrigger,
2248
+ borderColor: "color-mix(in srgb, var(--dsw-alias-state-warning-primary, #d69e2e) 55%, transparent)",
2249
+ background: "color-mix(in srgb, var(--dsw-alias-state-warning-primary, #d69e2e) 14%, transparent)",
2250
+ color: "var(--dsw-alias-state-warning-primary, #d69e2e)"
2251
+ };
2234
2252
  const repositoryChecksFrame = {
2235
2253
  position: "relative",
2236
2254
  display: "inline-flex",
@@ -2994,7 +3012,7 @@ window.__ModuleLoader__.load({
2994
3012
  fontSize: 11
2995
3013
  };
2996
3014
  const reviewCommentBarFrame = {
2997
- width: "calc(100% - 64px)",
3015
+ width: "calc(100% - 2 * var(--dsh-composer-side-clearance, 16px))",
2998
3016
  maxWidth: "var(--dsh-composer-card-max-width, 782px)",
2999
3017
  margin: "0 auto",
3000
3018
  boxSizing: "border-box"
@@ -3085,6 +3103,113 @@ window.__ModuleLoader__.load({
3085
3103
  whiteSpace: "pre-wrap",
3086
3104
  overflowWrap: "anywhere"
3087
3105
  };
3106
+ const promptSaveTriggerClass = "dshClaudePromptSaveTrigger";
3107
+ const promptSpinClass = "dshClaudePromptSpin";
3108
+ const promptUndoClass = "dshClaudePromptUndo";
3109
+ const promptSaveFieldClass = "dshClaudePromptSaveField";
3110
+ /** The composer's own attach button geometry (InputBar.module.css `.add`:
3111
+ * 28x28 circle on --dsw-specific-selector, 14px glyph). The primitives'
3112
+ * Button has no icon-only form — its `sm` is a 36px-wide capsule around a
3113
+ * 16px slot, which is why this reads oversized beside its neighbours. */
3114
+ const promptSaveTriggerCss = `
3115
+ .${promptSaveTriggerClass} {
3116
+ width: 28px;
3117
+ height: 28px;
3118
+ flex: none;
3119
+ display: grid;
3120
+ place-items: center;
3121
+ padding: 0;
3122
+ border: none;
3123
+ border-radius: 999px;
3124
+ background: var(--dsw-specific-selector);
3125
+ color: var(--dsw-alias-label-primary);
3126
+ cursor: pointer;
3127
+ transition: background-color 120ms ease, color 120ms ease;
3128
+ }
3129
+ /* With no draft there is nothing to keep, so the control drops its seat
3130
+ * entirely rather than sitting there as a filled-but-dead circle. */
3131
+ .${promptSaveTriggerClass}:disabled {
3132
+ cursor: default;
3133
+ background: transparent;
3134
+ color: var(--dsw-alias-label-tertiary);
3135
+ }
3136
+ .${promptSaveTriggerClass}:focus-visible { outline: none; }
3137
+ .${promptSaveFieldClass} {
3138
+ box-sizing: border-box;
3139
+ width: 100%;
3140
+ height: 30px;
3141
+ padding: 0 9px;
3142
+ border: 1px solid var(--dsw-alias-border-inverted);
3143
+ border-radius: 7px;
3144
+ background: var(--dsw-alias-interactive-bg-hover);
3145
+ color: var(--dsw-alias-label-primary);
3146
+ font-family: var(--dsw-font-family);
3147
+ font-size: 13px;
3148
+ line-height: 20px;
3149
+ }
3150
+ /* Not --dsw-alias-brand-primary: in the dark theme that alias resolves to
3151
+ * neutral-bluish-50, so a focused field draws a near-white slab around
3152
+ * itself. The focus ring is a step up in the same greys the card is made of. */
3153
+ .${promptSaveFieldClass}:focus-visible {
3154
+ outline: none;
3155
+ border-color: var(--dsw-alias-label-tertiary);
3156
+ background: var(--dsw-alias-interactive-bg-active);
3157
+ }
3158
+ .${promptSaveFieldClass}:disabled { opacity: 0.6; }
3159
+ /* ic_ds_loading_outline_16 is a bare open ring and nothing in the primitives
3160
+ * turns it, so a four-second rewrite sat behind a frozen glyph that reads as
3161
+ * the letter C. The spin is what makes it a spinner. */
3162
+ .${promptSpinClass} { animation: dshClaudePromptSpin 900ms linear infinite; }
3163
+ @keyframes dshClaudePromptSpin { to { transform: rotate(360deg); } }
3164
+ /* The icon set has no undo arrow, and ic_ds_refresh_outline_16 turns
3165
+ * clockwise — the redo direction. Mirrored, it is the ordinary undo glyph. */
3166
+ .${promptUndoClass} { transform: scaleX(-1); }
3167
+ @media (prefers-reduced-motion: reduce) {
3168
+ .${promptSpinClass} { animation: none; }
3169
+ }
3170
+ `;
3171
+ /** The naming card hangs off the composer tool row, portaled so the composer
3172
+ * card cannot clip it and positioned by useAnchoredPosition. */
3173
+ const promptSaveCard = {
3174
+ position: "fixed",
3175
+ zIndex: 120,
3176
+ boxSizing: "border-box",
3177
+ display: "flex",
3178
+ flexDirection: "column",
3179
+ gap: 8,
3180
+ width: 340,
3181
+ maxWidth: "calc(100vw - 32px)",
3182
+ padding: "10px 12px",
3183
+ border: "1px solid var(--dsw-alias-border-inverted)",
3184
+ borderRadius: 11,
3185
+ background: "var(--dsw-specific-menu)",
3186
+ boxShadow: "var(--dsw-shadow-lv3)",
3187
+ color: "var(--dsw-alias-label-primary)",
3188
+ fontSize: 12,
3189
+ lineHeight: "20px"
3190
+ };
3191
+ const promptSaveHeading = {
3192
+ color: "var(--dsw-alias-label-tertiary)",
3193
+ fontSize: 11,
3194
+ lineHeight: "16px"
3195
+ };
3196
+ const promptSaveActions = {
3197
+ display: "flex",
3198
+ justifyContent: "flex-end",
3199
+ gap: 6
3200
+ };
3201
+ /** The saved file, wrapped rather than truncated: a path the user cannot read
3202
+ * in full does not tell them where to go and edit it. */
3203
+ const promptSaveLocation = {
3204
+ color: "var(--dsw-alias-label-tertiary)",
3205
+ fontSize: 11,
3206
+ lineHeight: "16px",
3207
+ overflowWrap: "anywhere"
3208
+ };
3209
+ const promptSaveError = {
3210
+ color: "var(--dsw-static-red-450, #d64545)",
3211
+ overflowWrap: "anywhere"
3212
+ };
3088
3213
  const diffAhead = { color: "var(--dsw-static-blue-450)" };
3089
3214
  const diffAheadMuted = { color: "color-mix(in srgb, var(--dsw-static-blue-450) 62%, var(--dsw-alias-label-tertiary))" };
3090
3215
  const diffAddMuted = { color: "color-mix(in srgb, var(--dsw-alias-state-success-primary) 62%, var(--dsw-alias-label-tertiary))" };
@@ -4008,6 +4133,16 @@ window.__ModuleLoader__.load({
4008
4133
  laneHeld[lane] -= 1;
4009
4134
  pump();
4010
4135
  }
4136
+ /** Runs at most once, so a release can be wired to every ending of a stream
4137
+ * without any of them having to know about the others. */
4138
+ function once(action) {
4139
+ let done = false;
4140
+ return () => {
4141
+ if (done) return;
4142
+ done = true;
4143
+ action();
4144
+ };
4145
+ }
4011
4146
  function acquire(lane) {
4012
4147
  let released = false;
4013
4148
  const releaseOnce = () => {
@@ -4111,11 +4246,53 @@ window.__ModuleLoader__.load({
4111
4246
  writeId += 1;
4112
4247
  return writeId;
4113
4248
  }
4249
+ /**
4250
+ * A reader that gives its permit back however the stream ends.
4251
+ *
4252
+ * The permit used to be released by one event only -- the caller's abort --
4253
+ * and a stream has three endings, two of which the caller does not cause: the
4254
+ * server closing the response, and the body failing mid-read. Either one left
4255
+ * the permit held. For the reserved carrier that permit is a single boolean,
4256
+ * so one such ending closed the transcript stream for the life of the page and
4257
+ * every reopen answered `starved`, silently, forever.
4258
+ *
4259
+ * Binding the release to the reader rather than to the signal is what makes
4260
+ * that unrepresentable: a caller cannot hold the permit past the stream it is
4261
+ * reading, because every way of leaving the read passes through here. `free`
4262
+ * is idempotent, so the abort listener below stays as the belt to this brace.
4263
+ */
4264
+ function releasingReader(reader, free) {
4265
+ return {
4266
+ get closed() {
4267
+ return reader.closed;
4268
+ },
4269
+ read: async () => {
4270
+ try {
4271
+ const chunk = await reader.read();
4272
+ if (chunk.done) free();
4273
+ return chunk;
4274
+ } catch (error) {
4275
+ free();
4276
+ throw error;
4277
+ }
4278
+ },
4279
+ cancel: async (reason) => {
4280
+ try {
4281
+ await reader.cancel(reason);
4282
+ } finally {
4283
+ free();
4284
+ }
4285
+ },
4286
+ releaseLock: () => {
4287
+ reader.releaseLock();
4288
+ }
4289
+ };
4290
+ }
4114
4291
  async function openStream(lane, path, cancel, options, reserved) {
4115
4292
  const url = withQuery(path, options?.query);
4116
- const free = reserved ? () => {
4293
+ const free = reserved ? once(() => {
4117
4294
  projectionHeld = false;
4118
- } : await acquire(lane);
4295
+ }) : await acquire(lane);
4119
4296
  try {
4120
4297
  const response = await send(url, {
4121
4298
  method: options?.method ?? "GET",
@@ -4133,7 +4310,7 @@ window.__ModuleLoader__.load({
4133
4310
  throw new PluginRequestError("http", `HTTP ${response.status}`, response.status);
4134
4311
  }
4135
4312
  cancel.addEventListener("abort", free, { once: true });
4136
- return response.body.getReader();
4313
+ return releasingReader(response.body.getReader(), free);
4137
4314
  } catch (error) {
4138
4315
  free();
4139
4316
  throw failureOf(error, cancel);
@@ -4188,6 +4365,7 @@ window.__ModuleLoader__.load({
4188
4365
  const MAX_COMMANDS = 2e3;
4189
4366
  const MAX_REPOSITORY_TEXT_CHARS = 1024;
4190
4367
  const MAX_DIFF_CHARS = 262144;
4368
+ const MAX_CONFLICT_PATHS = 100;
4191
4369
  const MAX_REVIEW_COMMENTS = 50;
4192
4370
  const MAX_REVIEW_COMMENT_CHARS = 2e3;
4193
4371
  const MAX_TRANSCRIPT_CHARS = 64e3;
@@ -4206,7 +4384,12 @@ window.__ModuleLoader__.load({
4206
4384
  "ready",
4207
4385
  "not-repository",
4208
4386
  "unavailable"
4209
- ].includes(String(repository.status)) || typeof repository.cwd !== "string" || repository.cwd.length > MAX_REPOSITORY_TEXT_CHARS || !optionalBoundedString(repository.root) || !optionalBoundedString(repository.branch) || !optionalBoundedString(repository.remote) || repository.detached !== void 0 && typeof repository.detached !== "boolean" || repository.worktree !== void 0 && typeof repository.worktree !== "boolean" || repository.dirty !== void 0 && typeof repository.dirty !== "boolean" || repository.upstream !== void 0 && typeof repository.upstream !== "boolean" || repository.ahead !== void 0 && !nonNegativeInteger(repository.ahead) || repository.behind !== void 0 && !nonNegativeInteger(repository.behind)) return false;
4387
+ ].includes(String(repository.status)) || typeof repository.cwd !== "string" || repository.cwd.length > MAX_REPOSITORY_TEXT_CHARS || !optionalBoundedString(repository.root) || !optionalBoundedString(repository.branch) || !optionalBoundedString(repository.remote) || repository.detached !== void 0 && typeof repository.detached !== "boolean" || repository.worktree !== void 0 && typeof repository.worktree !== "boolean" || repository.dirty !== void 0 && typeof repository.dirty !== "boolean" || repository.upstream !== void 0 && typeof repository.upstream !== "boolean" || repository.ahead !== void 0 && !nonNegativeInteger(repository.ahead) || repository.behind !== void 0 && !nonNegativeInteger(repository.behind) || repository.operation !== void 0 && ![
4388
+ "rebase",
4389
+ "merge",
4390
+ "cherry-pick",
4391
+ "revert"
4392
+ ].includes(String(repository.operation)) || repository.conflicts !== void 0 && (!Array.isArray(repository.conflicts) || repository.conflicts.length > MAX_CONFLICT_PATHS || repository.conflicts.some((path) => typeof path !== "string" || path.length > MAX_REPOSITORY_TEXT_CHARS))) return false;
4210
4393
  if (repository.diff !== void 0) {
4211
4394
  const diff = record$7(repository.diff);
4212
4395
  if (diff === void 0 || !nonNegativeInteger(diff.additions) || !nonNegativeInteger(diff.deletions) || !nonNegativeInteger(diff.files) || typeof diff.truncated !== "boolean" || diff.patch !== void 0 && (typeof diff.patch !== "string" || diff.patch.length > MAX_DIFF_CHARS)) return false;
@@ -4598,6 +4781,10 @@ window.__ModuleLoader__.load({
4598
4781
  #report;
4599
4782
  #resyncCooldownMs;
4600
4783
  #resyncedAt = 0;
4784
+ /** Whether the carrier is in a run of failures. One report per outage: the
4785
+ * retry loop runs every couple of seconds and a beacon per attempt would
4786
+ * be the plugin reporting its own noise. */
4787
+ #carrierFailing = false;
4601
4788
  #controller;
4602
4789
  #settle;
4603
4790
  #running = false;
@@ -4683,6 +4870,7 @@ window.__ModuleLoader__.load({
4683
4870
  let superseded = false;
4684
4871
  try {
4685
4872
  const reader = await this.#open(`${CLAUDE_PROJECTION_PATH}/multi?sessions=${lanes.map(encodeURIComponent).join(",")}`, controller.signal);
4873
+ this.#carrierFailing = false;
4686
4874
  const stop = () => {
4687
4875
  superseded = true;
4688
4876
  reader.cancel().catch(() => void 0);
@@ -4705,6 +4893,10 @@ window.__ModuleLoader__.load({
4705
4893
  if (this.#controller !== controller) continue;
4706
4894
  return;
4707
4895
  }
4896
+ if (!this.#carrierFailing) {
4897
+ this.#carrierFailing = true;
4898
+ this.#report("projection-carrier-unavailable", error instanceof Error ? error.message : String(error));
4899
+ }
4708
4900
  } finally {
4709
4901
  if (this.#controller === controller) this.#controller = void 0;
4710
4902
  }
@@ -5208,8 +5400,10 @@ window.__ModuleLoader__.load({
5208
5400
  return parts;
5209
5401
  }
5210
5402
  /** The footer the Host draws under its own assistant message, drawn here for
5211
- * the steps the Host never had a message for. */
5403
+ * the steps the Host never had a message for. Mounted at the turn's foot, so
5404
+ * it closes the turn under the task badge rather than above it. */
5212
5405
  function ClaudeTurnUsage({ usage, t }) {
5406
+ ensureCss$4();
5213
5407
  const parts = turnUsageParts(usage, t);
5214
5408
  if (parts.length === 0) return null;
5215
5409
  return /* @__PURE__ */ jsxs("div", {
@@ -5251,10 +5445,7 @@ window.__ModuleLoader__.load({
5251
5445
  }, `text:${item.ordinal}`) : item.kind === "compaction" ? /* @__PURE__ */ jsx(ClaudeCompactionDivider, {
5252
5446
  compaction: item.compaction,
5253
5447
  t
5254
- }, `compaction:${item.ordinal}`) : item.kind === "usage" ? /* @__PURE__ */ jsx(ClaudeTurnUsage, {
5255
- usage: item.usage,
5256
- t
5257
- }, `usage:${item.ordinal}`) : item.kind === "tools" ? /* @__PURE__ */ jsx(ClaudeTranscriptToolGroup, {
5448
+ }, `compaction:${item.ordinal}`) : item.kind === "tools" ? /* @__PURE__ */ jsx(ClaudeTranscriptToolGroup, {
5258
5449
  tools: item.tools,
5259
5450
  ...item.additions === void 0 ? {} : { additions: item.additions },
5260
5451
  ...item.deletions === void 0 ? {} : { deletions: item.deletions },
@@ -5549,6 +5740,7 @@ window.__ModuleLoader__.load({
5549
5740
  //#endregion
5550
5741
  //#region src/client/ClaudeActivityTail.tsx
5551
5742
  const MAX_HOVER_TASKS = 6;
5743
+ const EMPTY_TASKS = [];
5552
5744
  function taskGlyph(status) {
5553
5745
  if (status === "failed") return {
5554
5746
  glyph: "×",
@@ -5671,24 +5863,36 @@ window.__ModuleLoader__.load({
5671
5863
  })
5672
5864
  });
5673
5865
  }
5866
+ /** Everything that closes a turn, in the order it reads: what the turn is
5867
+ * still doing, then what it cost. */
5868
+ function ClaudeTurnFooter({ turn, useClaudeProjection, t, openTasks }) {
5869
+ const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS);
5870
+ const usage = useClaudeProjection((value) => latestTurnUsage(value.activities, turn));
5871
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ClaudeTaskLauncher, {
5872
+ turn,
5873
+ tasks,
5874
+ t,
5875
+ openTasks
5876
+ }), usage === void 0 ? null : /* @__PURE__ */ jsx(ClaudeTurnUsage, {
5877
+ usage,
5878
+ t
5879
+ })] });
5880
+ }
5674
5881
  function ClaudeActivityTail({ matched, useClaudeProjection, t, openTasks }) {
5675
- const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? []);
5676
- return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
5882
+ return /* @__PURE__ */ jsx(ClaudeTurnFooter, {
5677
5883
  turn: matched.turn,
5678
- tasks,
5884
+ useClaudeProjection,
5679
5885
  t,
5680
5886
  openTasks
5681
5887
  });
5682
5888
  }
5683
5889
  //#endregion
5684
5890
  //#region src/client/ClaudeActiveTasksNode.tsx
5685
- const EMPTY_TASKS = [];
5686
- /** Render the task launcher while the owning DSH turn is still open. */
5891
+ /** Render the turn footer while the owning DSH turn is still open. */
5687
5892
  function ClaudeActiveTasksNode({ node, useClaudeProjection, t, openTasks }) {
5688
- const tasks = useClaudeProjection((value) => value.tasks?.tasks ?? EMPTY_TASKS);
5689
- return /* @__PURE__ */ jsx(ClaudeTaskLauncher, {
5893
+ return /* @__PURE__ */ jsx(ClaudeTurnFooter, {
5690
5894
  turn: node.data.turn,
5691
- tasks,
5895
+ useClaudeProjection,
5692
5896
  t,
5693
5897
  openTasks
5694
5898
  });
@@ -5806,6 +6010,22 @@ window.__ModuleLoader__.load({
5806
6010
  }
5807
6011
  return checks;
5808
6012
  }
6013
+ /** Review bots sign a comment with an attribution line and an actions checklist
6014
+ * aimed at the bot itself; in a prompt both are noise, and "apply fix" reads as
6015
+ * an instruction Claude cannot follow. */
6016
+ function commentText(body) {
6017
+ const lines = body.replaceAll(/<!--[\s\S]*?-->/g, "").split("\n").filter((line) => !/^\s*<sup>[\s\S]*<\/sup>\s*$/.test(line));
6018
+ let end = lines.length;
6019
+ while (end > 0 && /^\s*(?:-{3,}|\*\*[^*]+\*\*|[-*] \[[ xX]\].*)?\s*$/.test(lines[end - 1] ?? "")) end -= 1;
6020
+ return (lines.slice(end).some((line) => /^\s*[-*] \[[ xX]\]/.test(line)) ? lines.slice(0, end) : lines).join("\n").trim();
6021
+ }
6022
+ /** A one-line comment sits after the author; anything longer starts on its own
6023
+ * line, so its headings and lists keep meaning instead of running into ours. */
6024
+ function attributed(prefix, body) {
6025
+ const text = commentText(body);
6026
+ const indented = text.split("\n").map((line) => line.length === 0 ? line : ` ${line}`).join("\n");
6027
+ return text.includes("\n") ? `${prefix}\n\n${indented}` : `${prefix} ${text}`;
6028
+ }
5809
6029
  /** Draft handed to Claude when the user forwards GitHub review comments. A
5810
6030
  * resolved thread is a settled conversation: forwarding it would ask Claude to
5811
6031
  * redo work the reviewers already signed off. */
@@ -5815,8 +6035,8 @@ window.__ModuleLoader__.load({
5815
6035
  return `Please address the following GitHub pull request review comments. Make the requested changes, or explain briefly when a comment should not be applied.\n\n${open.map((thread) => {
5816
6036
  const [first, ...rest] = thread.comments;
5817
6037
  if (first === void 0) return "";
5818
- return [`- ${`${thread.path}${thread.line === void 0 ? "" : `:${thread.line}`}`} (@${first.author}): ${first.body.replaceAll("\n", "\n ")}`, ...rest.map((reply) => ` (@${reply.author}): ${reply.body.replaceAll("\n", "\n ")}`)].join("\n");
5819
- }).filter((block) => block.length > 0).join("\n")}`;
6038
+ return [attributed(`- ${`${thread.path}${thread.line === void 0 ? "" : `:${thread.line}`}`} (@${first.author}):`, first.body), ...rest.map((reply) => attributed(` (@${reply.author}):`, reply.body))].join("\n");
6039
+ }).filter((block) => block.length > 0).join("\n\n")}`;
5820
6040
  }
5821
6041
  /** Draft handed to Claude when the user forwards failing CI checks. */
5822
6042
  function composeChecksPrompt(checks) {
@@ -5824,11 +6044,13 @@ window.__ModuleLoader__.load({
5824
6044
  return `${`## ${check.name}${check.link === void 0 ? "" : ` (${check.link})`}`}${check.description === void 0 ? "" : `\n${check.description}`}${check.log === void 0 ? "" : `\n\n\`\`\`\n${check.log}\n\`\`\``}`;
5825
6045
  }).join("\n\n")}`;
5826
6046
  }
5827
- /** Draft handed to Claude after an update-branch merge left conflicts behind. */
5828
- function composeConflictsPrompt(baseBranch, conflicts, method = "merge") {
6047
+ /** Draft handed to Claude for a stopped merge, rebase, cherry-pick or revert --
6048
+ * from the update-branch dialog that caused one, or from the repository bar
6049
+ * for one already in the tree, where the base branch is not always known. */
6050
+ function composeConflictsPrompt(conflicts, operation = "merge", baseBranch) {
5829
6051
  const list = conflicts.map((file) => `- ${file}`).join("\n");
5830
- if (method === "rebase") return `Rebasing the current branch onto origin/${baseBranch} stopped on conflicts in the files below. Resolve each conflict preserving the intent of both sides, stage the files, run \`git rebase --continue\` until the rebase finishes, then push with \`git push --force-with-lease\`.\n\n${list}`;
5831
- return `Merging origin/${baseBranch} into the current branch left merge conflicts in the files below. Resolve each conflict preserving the intent of both sides, then commit the merge.\n\n${list}`;
6052
+ const base = baseBranch === void 0 ? void 0 : `origin/${baseBranch}`;
6053
+ return `${operation === "rebase" ? `Rebasing the current branch${base === void 0 ? "" : ` onto ${base}`} stopped on conflicts in the files below.` : operation === "merge" ? `Merging ${base ?? "the base branch"} into the current branch left conflicts in the files below.` : `A ${operation} stopped on conflicts in the files below.`} Resolve each conflict preserving the intent of both sides, stage the resolved files, then run \`git ${operation} --continue\` until the ${operation} finishes.${operation === "rebase" && baseBranch !== void 0 ? " Once it finishes, push with `git push --force-with-lease`." : ""}\n\n${list}`;
5832
6054
  }
5833
6055
  //#endregion
5834
6056
  //#region src/client/auto-fix.ts
@@ -5883,6 +6105,15 @@ window.__ModuleLoader__.load({
5883
6105
  };
5884
6106
  }
5885
6107
  //#endregion
6108
+ //#region src/client/branch-label.ts
6109
+ /** `Detached HEAD` names a git implementation detail, not the user's checkout:
6110
+ * a stopped rebase still knows which branch it parked, and that name is what
6111
+ * every panel should show. Only a genuinely nameless HEAD falls back. */
6112
+ function branchLabel(repository, t) {
6113
+ if (repository.branch !== void 0) return repository.branch;
6114
+ return repository.detached === true ? t("repositoryDetached") : t("repositoryUnknownBranch");
6115
+ }
6116
+ //#endregion
5886
6117
  //#region src/client/session-preset.ts
5887
6118
  /**
5888
6119
  * Resolve one row's preset id, newest seat first.
@@ -6020,7 +6251,7 @@ window.__ModuleLoader__.load({
6020
6251
  }) : rows.map((row) => {
6021
6252
  const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
6022
6253
  const pullRequest = repository?.pullRequest;
6023
- const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
6254
+ const branch = repository?.status === "ready" ? branchLabel(repository, t) : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
6024
6255
  return /* @__PURE__ */ jsxs("button", {
6025
6256
  type: "button",
6026
6257
  style: overviewRow,
@@ -8421,6 +8652,169 @@ window.__ModuleLoader__.load({
8421
8652
  }) : null]
8422
8653
  });
8423
8654
  }
8655
+ /** The way out of a stopped merge or rebase. A rebase detaches HEAD, which
8656
+ * hides every other control on this bar, and the update-branch dialog that
8657
+ * started it takes its conflict list along when it closes -- so this one is
8658
+ * mounted from repository state instead, and survives being dismissed. */
8659
+ function ConflictControl({ sessionId, repository, t, report, submitPrompt }) {
8660
+ const [dialog, setDialog] = useState();
8661
+ const operation = repository.operation;
8662
+ if (operation === void 0) return null;
8663
+ const conflicts = repository.conflicts ?? [];
8664
+ const operationName = t(`conflictOperation_${operation}`);
8665
+ const closeDialog = () => {
8666
+ if (dialog?.submitting !== true) setDialog(void 0);
8667
+ };
8668
+ const run = (action) => {
8669
+ if (dialog === void 0 || dialog.submitting) return;
8670
+ const { error: _error, ...pending } = dialog;
8671
+ setDialog({
8672
+ ...pending,
8673
+ submitting: true
8674
+ });
8675
+ executeRepositoryAction(sessionId, {
8676
+ action,
8677
+ fingerprint: "",
8678
+ message: "",
8679
+ includeUnstaged: false,
8680
+ push: dialog.push
8681
+ }).then((result) => {
8682
+ if (result.conflicts !== void 0 && result.conflicts.length > 0) {
8683
+ setDialog({
8684
+ ...pending,
8685
+ submitting: false,
8686
+ confirmAbort: false
8687
+ });
8688
+ return;
8689
+ }
8690
+ report(action === "resolve-abort" ? t("conflictAborted", { operation: operationName }) : t(result.pushed ? "conflictPushed" : "conflictContinued", { operation: operationName }));
8691
+ setDialog(void 0);
8692
+ }, (reason) => {
8693
+ setDialog({
8694
+ ...pending,
8695
+ submitting: false,
8696
+ error: reason instanceof Error ? reason.message : t("diffActionFailed")
8697
+ });
8698
+ });
8699
+ };
8700
+ const openDialog = () => {
8701
+ setDialog({
8702
+ submitting: false,
8703
+ confirmAbort: false,
8704
+ push: repository.remote !== void 0 && repository.pullRequest?.state === "open"
8705
+ });
8706
+ };
8707
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
8708
+ /* @__PURE__ */ jsx("button", {
8709
+ type: "button",
8710
+ style: repositoryConflictTrigger,
8711
+ title: t("conflictDescription", { operation: operationName }),
8712
+ onClick: openDialog,
8713
+ children: conflicts.length > 0 ? t("conflictBadge", {
8714
+ operation: operationName,
8715
+ count: conflicts.length
8716
+ }) : t("conflictBadgeReady", { operation: operationName })
8717
+ }),
8718
+ dialog === void 0 ? null : /* @__PURE__ */ jsx("style", {
8719
+ "data-dsh-claude-repository-modal-styles": true,
8720
+ children: diffModalCss
8721
+ }),
8722
+ /* @__PURE__ */ jsx(Modal, {
8723
+ className: "dshClaudeRepositoryActionModal",
8724
+ contentClassName: "dshClaudeRepositoryActionModalContent",
8725
+ open: dialog !== void 0,
8726
+ onClose: closeDialog,
8727
+ title: t("conflictTitle"),
8728
+ closeLabel: t("diffCancel"),
8729
+ description: t("conflictDescription", { operation: operationName }),
8730
+ footer: /* @__PURE__ */ jsxs("div", {
8731
+ style: diffModalFooter,
8732
+ children: [/* @__PURE__ */ jsx("button", {
8733
+ type: "button",
8734
+ style: {
8735
+ ...button,
8736
+ ...diffModalButton
8737
+ },
8738
+ disabled: dialog?.submitting === true,
8739
+ onClick: () => {
8740
+ if (dialog?.confirmAbort === true) run("resolve-abort");
8741
+ else setDialog((current) => current === void 0 ? current : {
8742
+ ...current,
8743
+ confirmAbort: true
8744
+ });
8745
+ },
8746
+ children: t("conflictAbort", { operation: operationName })
8747
+ }), /* @__PURE__ */ jsx("button", {
8748
+ type: "button",
8749
+ style: {
8750
+ ...primaryButton,
8751
+ ...diffModalButton
8752
+ },
8753
+ disabled: dialog?.submitting === true || conflicts.length > 0,
8754
+ onClick: () => run("resolve-continue"),
8755
+ children: dialog?.submitting === true ? t("diffSubmitting") : t("conflictContinue", { operation: operationName })
8756
+ })]
8757
+ }),
8758
+ children: dialog === void 0 ? null : /* @__PURE__ */ jsxs("div", {
8759
+ style: diffModalBody,
8760
+ children: [
8761
+ /* @__PURE__ */ jsxs("div", {
8762
+ style: diffModalMeta,
8763
+ children: [/* @__PURE__ */ jsxs("strong", {
8764
+ style: diffModalMetaText,
8765
+ children: [
8766
+ operationName,
8767
+ " · ",
8768
+ branchLabel(repository, t)
8769
+ ]
8770
+ }), /* @__PURE__ */ jsx("span", {
8771
+ style: diffModalFileState,
8772
+ children: conflicts.length > 0 ? t("conflictFiles") : t("conflictReady")
8773
+ })]
8774
+ }),
8775
+ conflicts.length === 0 ? null : /* @__PURE__ */ jsx("ul", {
8776
+ style: diffModalConflicts,
8777
+ children: conflicts.map((file) => /* @__PURE__ */ jsx("li", { children: file }, file))
8778
+ }),
8779
+ conflicts.length === 0 || submitPrompt === void 0 ? null : /* @__PURE__ */ jsx("button", {
8780
+ type: "button",
8781
+ style: diffModalConflictResolve,
8782
+ onClick: () => {
8783
+ submitPrompt(composeConflictsPrompt(conflicts, operation, repository.pullRequest?.baseBranch));
8784
+ closeDialog();
8785
+ },
8786
+ children: t("conflictResolve")
8787
+ }),
8788
+ repository.remote === void 0 ? null : /* @__PURE__ */ jsxs("label", {
8789
+ style: diffModalCheckbox,
8790
+ children: [/* @__PURE__ */ jsx("input", {
8791
+ type: "checkbox",
8792
+ checked: dialog.push,
8793
+ disabled: dialog.submitting,
8794
+ onChange: (event) => {
8795
+ const { checked } = event.currentTarget;
8796
+ setDialog((current) => current === void 0 ? current : {
8797
+ ...current,
8798
+ push: checked
8799
+ });
8800
+ }
8801
+ }), t("conflictPush")]
8802
+ }),
8803
+ !dialog.confirmAbort ? null : /* @__PURE__ */ jsx("p", {
8804
+ role: "alert",
8805
+ style: diffModalStatus,
8806
+ children: t("conflictAbortConfirm", { operation: operationName })
8807
+ }),
8808
+ dialog.error === void 0 ? null : /* @__PURE__ */ jsx("p", {
8809
+ role: "alert",
8810
+ style: diffModalError,
8811
+ children: dialog.error
8812
+ })
8813
+ ]
8814
+ })
8815
+ })
8816
+ ] });
8817
+ }
8424
8818
  /** The trigger only shows on a clean branch that is behind its base -- but a
8425
8819
  * conflicted rebase leaves the tree dirty on a detached HEAD, so an open
8426
8820
  * dialog (and its resolve button) has to outlive that. */
@@ -8592,7 +8986,7 @@ window.__ModuleLoader__.load({
8592
8986
  type: "button",
8593
8987
  style: diffModalConflictResolve,
8594
8988
  onClick: () => {
8595
- submitPrompt(composeConflictsPrompt(base, dialog.conflicts ?? [], method));
8989
+ submitPrompt(composeConflictsPrompt(dialog.conflicts ?? [], method, base));
8596
8990
  closeDialog();
8597
8991
  },
8598
8992
  children: t("diffUpdateBranchResolve")
@@ -8863,7 +9257,7 @@ window.__ModuleLoader__.load({
8863
9257
  const repository = projection.repository;
8864
9258
  const { toast, report } = useActionToast();
8865
9259
  if (blank || !projection.owned || repository === void 0) return null;
8866
- const branch = repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch");
9260
+ const branch = branchLabel(repository, t);
8867
9261
  const pullRequest = repository.pullRequest;
8868
9262
  const merged = pullRequest?.state === "merged";
8869
9263
  const mergedAge = merged ? relativeAge(pullRequest.mergedAt) : void 0;
@@ -8935,79 +9329,89 @@ window.__ModuleLoader__.load({
8935
9329
  }) : null,
8936
9330
  /* @__PURE__ */ jsxs("span", {
8937
9331
  style: repositoryStatusItems,
8938
- children: [hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
8939
- type: "button",
8940
- style: {
8941
- ...diffTrigger,
8942
- ...merged ? diffTriggerMuted : {}
8943
- },
8944
- onClick: openDiff,
8945
- "aria-label": t("diffOpen"),
8946
- children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
8947
- style: merged ? diffAddMuted : diffAdd,
8948
- children: ["+", repository.diff.additions]
8949
- }), /* @__PURE__ */ jsxs("span", {
8950
- style: merged ? diffDeleteMuted : diffDelete,
8951
- children: ["−", repository.diff.deletions]
8952
- })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
8953
- style: merged ? diffAheadMuted : diffAhead,
8954
- children: ["↑", aheadCount > 0 ? aheadCount : ""]
8955
- }) : null]
8956
- }) : null, pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
8957
- style: repositoryMergedStatus,
8958
- children: [
8959
- /* @__PURE__ */ jsx("span", {
8960
- style: repositoryMergedDot,
8961
- "aria-hidden": "true"
8962
- }),
8963
- t("repositoryState_merged"),
8964
- mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
8965
- style: repositoryMergedAge,
8966
- children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
8967
- })
8968
- ]
8969
- }), /* @__PURE__ */ jsx(CleanupControl, {
8970
- repository,
8971
- t,
8972
- report,
8973
- ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
8974
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
8975
- pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
8976
- sessionId,
8977
- pullNumber: pullRequest.number,
8978
- t,
8979
- ...submitPrompt === void 0 ? {} : { submitPrompt }
8980
- }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
8981
- label: t(`repositoryChecks_${pullRequest.checks}`),
8982
- tone: pullRequest.checks === "passing" ? "success" : "warning",
8983
- children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
8984
- }),
8985
- pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
8986
- label: t(`repositoryReview_${pullRequest.review}`),
8987
- tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
8988
- children: /* @__PURE__ */ jsx(ReviewGlyph, {})
8989
- }),
8990
- /* @__PURE__ */ jsx(AutoFixControl, {
8991
- sessionId,
8992
- repository,
8993
- running,
8994
- t,
8995
- ...submitPrompt === void 0 ? {} : { submitPrompt }
8996
- }),
8997
- /* @__PURE__ */ jsx(UpdateBranchControl, {
9332
+ children: [
9333
+ /* @__PURE__ */ jsx(ConflictControl, {
8998
9334
  sessionId,
8999
9335
  repository,
9000
9336
  t,
9001
9337
  report,
9002
9338
  ...submitPrompt === void 0 ? {} : { submitPrompt }
9003
9339
  }),
9004
- /* @__PURE__ */ jsx(MergePullRequestControl, {
9005
- sessionId,
9340
+ hasDiff || pushable ? /* @__PURE__ */ jsxs("button", {
9341
+ type: "button",
9342
+ style: {
9343
+ ...diffTrigger,
9344
+ ...merged ? diffTriggerMuted : {}
9345
+ },
9346
+ onClick: openDiff,
9347
+ "aria-label": t("diffOpen"),
9348
+ children: [hasDiff && repository.diff !== void 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9349
+ style: merged ? diffAddMuted : diffAdd,
9350
+ children: ["+", repository.diff.additions]
9351
+ }), /* @__PURE__ */ jsxs("span", {
9352
+ style: merged ? diffDeleteMuted : diffDelete,
9353
+ children: ["−", repository.diff.deletions]
9354
+ })] }) : null, pushable ? /* @__PURE__ */ jsxs("span", {
9355
+ style: merged ? diffAheadMuted : diffAhead,
9356
+ children: ["↑", aheadCount > 0 ? aheadCount : ""]
9357
+ }) : null]
9358
+ }) : null,
9359
+ pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
9360
+ style: repositoryMergedStatus,
9361
+ children: [
9362
+ /* @__PURE__ */ jsx("span", {
9363
+ style: repositoryMergedDot,
9364
+ "aria-hidden": "true"
9365
+ }),
9366
+ t("repositoryState_merged"),
9367
+ mergedAge === void 0 ? null : /* @__PURE__ */ jsxs("span", {
9368
+ style: repositoryMergedAge,
9369
+ children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
9370
+ })
9371
+ ]
9372
+ }), /* @__PURE__ */ jsx(CleanupControl, {
9006
9373
  repository,
9007
9374
  t,
9008
- report
9009
- })
9010
- ] })]
9375
+ report,
9376
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
9377
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
9378
+ pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
9379
+ sessionId,
9380
+ pullNumber: pullRequest.number,
9381
+ t,
9382
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
9383
+ }) : pullRequest.checks === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9384
+ label: t(`repositoryChecks_${pullRequest.checks}`),
9385
+ tone: pullRequest.checks === "passing" ? "success" : "warning",
9386
+ children: /* @__PURE__ */ jsx(ChecksGlyph, { state: pullRequest.checks })
9387
+ }),
9388
+ pullRequest.review === "none" ? null : /* @__PURE__ */ jsx(StatusGlyph, {
9389
+ label: t(`repositoryReview_${pullRequest.review}`),
9390
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral",
9391
+ children: /* @__PURE__ */ jsx(ReviewGlyph, {})
9392
+ }),
9393
+ /* @__PURE__ */ jsx(AutoFixControl, {
9394
+ sessionId,
9395
+ repository,
9396
+ running,
9397
+ t,
9398
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
9399
+ }),
9400
+ /* @__PURE__ */ jsx(UpdateBranchControl, {
9401
+ sessionId,
9402
+ repository,
9403
+ t,
9404
+ report,
9405
+ ...submitPrompt === void 0 ? {} : { submitPrompt }
9406
+ }),
9407
+ /* @__PURE__ */ jsx(MergePullRequestControl, {
9408
+ sessionId,
9409
+ repository,
9410
+ t,
9411
+ report
9412
+ })
9413
+ ] })
9414
+ ]
9011
9415
  })
9012
9416
  ]
9013
9417
  })]
@@ -14216,7 +14620,7 @@ window.__ModuleLoader__.load({
14216
14620
  return (action === "commit" ? t("diffCommit") : action === "commit-push" ? t("diffCommitPush") : action === "push" ? t("diffPush") : action === "merge-pr" ? t("diffMergePr") : action === "update-branch" ? t("diffUpdateBranch") : t("diffCreatePr")).replace(/[….]+$/u, "");
14217
14621
  }
14218
14622
  function repositoryActionAvailability(repository) {
14219
- const ready = repository?.status === "ready" && repository.detached !== true;
14623
+ const ready = repository?.status === "ready" && repository.detached !== true && (repository.conflicts ?? []).length === 0;
14220
14624
  const committable = ready && repository.dirty === true;
14221
14625
  const hasRemote = repository?.remote !== void 0;
14222
14626
  const hasOpenPullRequest = repository?.pullRequest?.state === "open";
@@ -14536,7 +14940,7 @@ window.__ModuleLoader__.load({
14536
14940
  repository?.status
14537
14941
  ]);
14538
14942
  if (!projection.owned || repository?.status !== "ready" || diff === void 0) return null;
14539
- const branch = repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch");
14943
+ const branch = branchLabel(repository, t);
14540
14944
  const availability = repositoryActionAvailability(repository);
14541
14945
  const anyActionAvailable = availability["commit"] || availability["commit-push"] || availability["push"] || availability["create-pr"];
14542
14946
  const menuItems = [
@@ -17642,6 +18046,428 @@ window.__ModuleLoader__.load({
17642
18046
  }
17643
18047
  }
17644
18048
  //#endregion
18049
+ //#region src/client/prompt-api.ts
18050
+ /** Prompt files live in one global directory, so one module-level cache serves
18051
+ * every session. The TTL is what lets a file added outside DSH appear without
18052
+ * a reload, while a burst of keystrokes through the menu still costs one
18053
+ * directory scan. */
18054
+ const TTL_MS = 5e3;
18055
+ let cached;
18056
+ function invalidateClaudePrompts() {
18057
+ cached = void 0;
18058
+ }
18059
+ /** The user's prompt snippets, at most one read per TTL window. */
18060
+ async function claudePrompts() {
18061
+ const at = Date.now();
18062
+ if (cached !== void 0 && at - cached.at < TTL_MS) return await cached.prompts;
18063
+ const prompts = pluginRead(CLAUDE_PROMPTS_PATH, "fast").then((payload) => payload.prompts).catch(() => {
18064
+ invalidateClaudePrompts();
18065
+ return [];
18066
+ });
18067
+ cached = {
18068
+ at,
18069
+ prompts
18070
+ };
18071
+ return await prompts;
18072
+ }
18073
+ /** Save one snippet, answering where it landed. Rejects with a
18074
+ * `PluginRequestError` whose `code` is `name-taken` when the file already
18075
+ * exists; nothing is overwritten. */
18076
+ async function saveClaudePrompt(name, body) {
18077
+ const saved = await pluginWrite(CLAUDE_PROMPTS_PATH, "fast", void 0, { json: {
18078
+ name,
18079
+ body
18080
+ } });
18081
+ invalidateClaudePrompts();
18082
+ return saved.prompt;
18083
+ }
18084
+ /** Ask the host to name a draft with Claude's cheapest model. Answers
18085
+ * undefined whenever no name could be had — the caller already holds the
18086
+ * locally derived one, so a failure here is never worth reporting. */
18087
+ async function suggestClaudePromptName(draft, cancel) {
18088
+ try {
18089
+ return (await pluginWrite(CLAUDE_PROMPT_NAME_PATH, "git", cancel, { json: { draft } })).name;
18090
+ } catch {
18091
+ return;
18092
+ }
18093
+ }
18094
+ /** Rewrite a draft into something an agent can act on. Unlike the name
18095
+ * suggestion this one throws: the user asked for it and is waiting, so a
18096
+ * failure is theirs to see rather than ours to swallow. */
18097
+ async function refineClaudePrompt(draft, cancel) {
18098
+ return (await pluginWrite(CLAUDE_PROMPT_REFINE_PATH, "git", cancel, { json: { draft } })).text;
18099
+ }
18100
+ /** Characters the host's `PROMPT_NAME` guard rejects; scrubbed rather than
18101
+ * re-implemented here, so a drift in the guard cannot let a bad name through
18102
+ * (the host still validates, and answers `invalid-name`). */
18103
+ const FOREIGN_NAME_CHARS = /[^\p{L}\p{M}\p{N} ._()\[\]-]/gu;
18104
+ const MAX_NAME_CHARS = 40;
18105
+ function stamp(now) {
18106
+ const pad = (value) => String(value).padStart(2, "0");
18107
+ return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}`;
18108
+ }
18109
+ /** The file name to offer for a draft: its opening line, scrubbed to what a
18110
+ * file name may hold. A draft that opens with punctuation or an emoji leaves
18111
+ * nothing usable, so those fall back to a timestamp the user can rename. */
18112
+ function defaultPromptName(draft, now = /* @__PURE__ */ new Date()) {
18113
+ const collapsed = (draft.split("\n").map((line) => line.trim()).find((line) => line.length > 0) ?? "").replace(FOREIGN_NAME_CHARS, " ").replace(/\s+/gu, " ").trim();
18114
+ const cut = collapsed.slice(0, MAX_NAME_CHARS);
18115
+ const boundary = collapsed.length > MAX_NAME_CHARS ? cut.search(/[\s\-_][^\s\-_]*$/u) : -1;
18116
+ const scrubbed = (boundary > 0 ? cut.slice(0, boundary) : cut).trim();
18117
+ return /^[\p{L}\p{N}]/u.test(scrubbed) ? scrubbed : `prompt-${stamp(now)}`;
18118
+ }
18119
+ //#endregion
18120
+ //#region src/client/ClaudePromptSaveAction.tsx
18121
+ const CARD_GAP = 8;
18122
+ const CARD_MARGIN = 12;
18123
+ /**
18124
+ * Place the card above its trigger, clamped into the viewport, and dismiss it
18125
+ * on a pointer that lands outside both.
18126
+ *
18127
+ * The primitives ship `useAnchoredPosition` and `useDismissOnOutsidePointer`
18128
+ * for exactly this, and both are used here through their published types —
18129
+ * but the Host's build of that package predates the `side` option of one and
18130
+ * the portal argument of the other. Neither omission is visible to `tsc`,
18131
+ * which reads this checkout's newer copy, and both are silent at runtime: the
18132
+ * card hangs below a composer pinned to the bottom of the window, and every
18133
+ * pointer landing in the naming field counts as outside and closes it. Owning
18134
+ * twenty lines beats behaviour that depends on which Desktop the plugin was
18135
+ * installed into.
18136
+ */
18137
+ function useAnchoredCard(open, anchor, card, onDismiss) {
18138
+ const [position, setPosition] = useState();
18139
+ useLayoutEffect(() => {
18140
+ if (!open) {
18141
+ setPosition(void 0);
18142
+ return;
18143
+ }
18144
+ const place = () => {
18145
+ const trigger = anchor.current?.getBoundingClientRect();
18146
+ const panel = card.current?.getBoundingClientRect();
18147
+ if (trigger === void 0 || panel === void 0) return;
18148
+ const above = trigger.top - CARD_GAP - panel.height;
18149
+ setPosition({
18150
+ left: Math.max(CARD_MARGIN, Math.min(trigger.left, window.innerWidth - panel.width - CARD_MARGIN)),
18151
+ top: above >= CARD_MARGIN ? above : Math.min(trigger.bottom + CARD_GAP, window.innerHeight - panel.height - CARD_MARGIN)
18152
+ });
18153
+ };
18154
+ place();
18155
+ window.addEventListener("resize", place);
18156
+ window.addEventListener("scroll", place, true);
18157
+ return () => {
18158
+ window.removeEventListener("resize", place);
18159
+ window.removeEventListener("scroll", place, true);
18160
+ };
18161
+ }, [
18162
+ open,
18163
+ anchor,
18164
+ card
18165
+ ]);
18166
+ useEffect(() => {
18167
+ if (!open) return void 0;
18168
+ const dismiss = (event) => {
18169
+ const target = event.target;
18170
+ if (!(target instanceof Node)) return;
18171
+ if (anchor.current?.contains(target) === true || card.current?.contains(target) === true) return;
18172
+ onDismiss();
18173
+ };
18174
+ document.addEventListener("pointerdown", dismiss);
18175
+ return () => {
18176
+ document.removeEventListener("pointerdown", dismiss);
18177
+ };
18178
+ }, [
18179
+ open,
18180
+ anchor,
18181
+ card,
18182
+ onDismiss
18183
+ ]);
18184
+ return position;
18185
+ }
18186
+ /**
18187
+ * Keep the draft you just wrote, from the composer's own tool row.
18188
+ *
18189
+ * It sits beside the attach and access controls rather than in a row of its
18190
+ * own: a band above the composer moves the repository bar and the composer
18191
+ * itself every time a draft appears, and this is a once-in-a-while action that
18192
+ * has not earned that. The naming card is portaled and anchored, so opening it
18193
+ * displaces nothing either.
18194
+ *
18195
+ * The field opens on a name derived from the draft's first line, which costs
18196
+ * nothing and is there instantly, and a Claude-written name replaces it when
18197
+ * one arrives. That ordering is the whole naming design: the suggestion is an
18198
+ * improvement on a working answer, never something the user waits for.
18199
+ */
18200
+ function ClaudePromptSaveAction({ t, useClaudeProjection, input, savePrompt = saveClaudePrompt, suggestName = suggestClaudePromptName }) {
18201
+ const owned = useClaudeProjection((projection) => projection.owned);
18202
+ const anchor = useRef(null);
18203
+ const panelRef = useRef(null);
18204
+ const suggestion = useRef(void 0);
18205
+ const [panel, setPanel] = useState();
18206
+ const [saving, setSaving] = useState(false);
18207
+ const close = useCallback(() => {
18208
+ suggestion.current?.abort();
18209
+ suggestion.current = void 0;
18210
+ setPanel(void 0);
18211
+ }, []);
18212
+ const position = useAnchoredCard(panel !== void 0, anchor, panelRef, close);
18213
+ useEffect(() => () => {
18214
+ suggestion.current?.abort();
18215
+ }, []);
18216
+ if (!owned) return null;
18217
+ const draft = input?.draft ?? "";
18218
+ const label = t("promptSave");
18219
+ const open = () => {
18220
+ setPanel({
18221
+ kind: "naming",
18222
+ name: defaultPromptName(draft),
18223
+ touched: false,
18224
+ suggesting: true
18225
+ });
18226
+ const attempt = new AbortController();
18227
+ suggestion.current = attempt;
18228
+ suggestName(draft, attempt.signal).then((suggested) => {
18229
+ if (attempt.signal.aborted) return;
18230
+ setPanel((current) => current?.kind !== "naming" ? current : {
18231
+ ...current,
18232
+ suggesting: false,
18233
+ ...suggested === void 0 || current.touched ? {} : { name: suggested }
18234
+ });
18235
+ });
18236
+ };
18237
+ const save = () => {
18238
+ const name = panel?.kind === "naming" ? panel.name.trim() : "";
18239
+ if (saving || name === "" || draft.trim() === "") return;
18240
+ setSaving(true);
18241
+ savePrompt(name, draft).then((prompt) => {
18242
+ setPanel({
18243
+ kind: "saved",
18244
+ prompt
18245
+ });
18246
+ }, (error) => {
18247
+ setPanel({
18248
+ kind: "naming",
18249
+ name,
18250
+ touched: true,
18251
+ suggesting: false,
18252
+ failure: error instanceof PluginRequestError && error.code === "name-taken" ? t("promptSaveExists") : t("promptSaveFailed", { message: error instanceof Error ? error.message : String(error) })
18253
+ });
18254
+ }).finally(() => {
18255
+ setSaving(false);
18256
+ });
18257
+ };
18258
+ return /* @__PURE__ */ jsxs("span", {
18259
+ ref: anchor,
18260
+ style: {
18261
+ position: "relative",
18262
+ display: "inline-flex"
18263
+ },
18264
+ children: [
18265
+ /* @__PURE__ */ jsx("style", {
18266
+ "data-dsh-claude-prompt-save-styles": true,
18267
+ children: promptSaveTriggerCss
18268
+ }),
18269
+ /* @__PURE__ */ jsx(Tooltip, {
18270
+ label,
18271
+ side: "top",
18272
+ delayMs: 250,
18273
+ disabled: panel !== void 0,
18274
+ children: /* @__PURE__ */ jsx("button", {
18275
+ type: "button",
18276
+ className: promptSaveTriggerClass,
18277
+ "aria-label": label,
18278
+ "aria-haspopup": "dialog",
18279
+ "aria-expanded": panel !== void 0,
18280
+ disabled: draft.trim() === "",
18281
+ onClick: () => {
18282
+ if (panel === void 0) open();
18283
+ else close();
18284
+ },
18285
+ children: /* @__PURE__ */ jsx(IconListPenOutline16, { size: 14 })
18286
+ })
18287
+ }),
18288
+ panel === void 0 || typeof document === "undefined" ? null : createPortal(/* @__PURE__ */ jsx("div", {
18289
+ ref: panelRef,
18290
+ style: {
18291
+ ...promptSaveCard,
18292
+ ...position
18293
+ },
18294
+ role: "dialog",
18295
+ "aria-label": label,
18296
+ children: panel.kind === "saved" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
18297
+ /* @__PURE__ */ jsx("span", { children: t("promptSaved", { name: panel.prompt.name }) }),
18298
+ /* @__PURE__ */ jsx("span", {
18299
+ style: promptSaveLocation,
18300
+ children: panel.prompt.location
18301
+ }),
18302
+ /* @__PURE__ */ jsx("span", {
18303
+ style: promptSaveActions,
18304
+ children: /* @__PURE__ */ jsx(Button, {
18305
+ variant: "primary",
18306
+ size: "sm",
18307
+ onClick: close,
18308
+ children: t("promptSaveDone")
18309
+ })
18310
+ })
18311
+ ] }) : /* @__PURE__ */ jsxs("form", {
18312
+ style: { display: "contents" },
18313
+ onSubmit: (event) => {
18314
+ event.preventDefault();
18315
+ save();
18316
+ },
18317
+ children: [
18318
+ /* @__PURE__ */ jsx("span", {
18319
+ style: promptSaveHeading,
18320
+ children: panel.suggesting ? t("promptSaveNaming") : label
18321
+ }),
18322
+ /* @__PURE__ */ jsx("input", {
18323
+ className: promptSaveFieldClass,
18324
+ "aria-label": t("promptSaveName"),
18325
+ placeholder: t("promptSaveName"),
18326
+ value: panel.name,
18327
+ maxLength: 128,
18328
+ autoFocus: true,
18329
+ disabled: saving,
18330
+ onChange: (event) => setPanel({
18331
+ ...panel,
18332
+ name: event.currentTarget.value,
18333
+ touched: true
18334
+ }),
18335
+ onKeyDown: (event) => {
18336
+ if (event.key === "Escape") close();
18337
+ }
18338
+ }),
18339
+ panel.failure === void 0 ? null : /* @__PURE__ */ jsx("span", {
18340
+ style: promptSaveError,
18341
+ children: panel.failure
18342
+ }),
18343
+ /* @__PURE__ */ jsxs("span", {
18344
+ style: promptSaveActions,
18345
+ children: [/* @__PURE__ */ jsx(Button, {
18346
+ variant: "ghost",
18347
+ size: "sm",
18348
+ type: "button",
18349
+ disabled: saving,
18350
+ onClick: close,
18351
+ children: t("promptSaveCancel")
18352
+ }), /* @__PURE__ */ jsx(Button, {
18353
+ variant: "primary",
18354
+ size: "sm",
18355
+ type: "submit",
18356
+ disabled: saving,
18357
+ children: t("promptSaveConfirm")
18358
+ })]
18359
+ })
18360
+ ]
18361
+ })
18362
+ }), document.body)
18363
+ ]
18364
+ });
18365
+ }
18366
+ //#endregion
18367
+ //#region src/client/ClaudePromptRefineAction.tsx
18368
+ /**
18369
+ * Rewrite the draft in place, from the composer's own tool row.
18370
+ *
18371
+ * The rewrite replaces the draft outright, which is what makes the undo state
18372
+ * load-bearing rather than a nicety: `SessionInput.setDraft` documents itself
18373
+ * as "merged into history so a seed is not an undoable step of its own", so
18374
+ * Ctrl/Cmd+Z does NOT bring the original back. Without somewhere to put it,
18375
+ * one press of this button would silently destroy a draft the user may have
18376
+ * spent minutes on. So the original is held for exactly as long as it is still
18377
+ * recoverable — until the rewrite is edited or sent — and the same button
18378
+ * offers it back over that window.
18379
+ */
18380
+ function ClaudePromptRefineAction({ t, useClaudeProjection, input, replaceDraft, notify, refine = refineClaudePrompt }) {
18381
+ const owned = useClaudeProjection((projection) => projection.owned);
18382
+ const attempt = useRef(void 0);
18383
+ const [busy, setBusy] = useState(false);
18384
+ const [applied, setApplied] = useState();
18385
+ useEffect(() => () => {
18386
+ attempt.current?.abort();
18387
+ }, []);
18388
+ if (!owned || replaceDraft === void 0) return null;
18389
+ const draft = input?.draft ?? "";
18390
+ const undoable = applied !== void 0 && draft === applied.refined;
18391
+ const label = busy ? t("promptRefineBusy") : undoable ? t("promptRefineUndo") : t("promptRefine");
18392
+ const run = () => {
18393
+ if (busy || draft.trim() === "") return;
18394
+ setBusy(true);
18395
+ const running = new AbortController();
18396
+ attempt.current = running;
18397
+ refine(draft, running.signal).then((text) => {
18398
+ if (running.signal.aborted) return;
18399
+ setApplied({
18400
+ original: draft,
18401
+ refined: text
18402
+ });
18403
+ replaceDraft(text);
18404
+ }, (error) => {
18405
+ if (running.signal.aborted) return;
18406
+ notify?.("error", t("promptRefineFailed", { message: error instanceof Error ? error.message : String(error) }));
18407
+ }).finally(() => {
18408
+ setBusy(false);
18409
+ });
18410
+ };
18411
+ const undo = () => {
18412
+ if (applied === void 0) return;
18413
+ replaceDraft(applied.original);
18414
+ setApplied(void 0);
18415
+ };
18416
+ return /* @__PURE__ */ jsx(Tooltip, {
18417
+ label,
18418
+ side: "top",
18419
+ delayMs: 250,
18420
+ children: /* @__PURE__ */ jsxs("button", {
18421
+ type: "button",
18422
+ className: promptSaveTriggerClass,
18423
+ "aria-label": label,
18424
+ disabled: busy || !undoable && draft.trim() === "",
18425
+ onClick: undoable ? undo : run,
18426
+ children: [/* @__PURE__ */ jsx("style", {
18427
+ "data-dsh-claude-prompt-refine-styles": true,
18428
+ children: promptSaveTriggerCss
18429
+ }), busy ? /* @__PURE__ */ jsx(IconLoadingOutline16, {
18430
+ size: 14,
18431
+ className: promptSpinClass
18432
+ }) : undoable ? /* @__PURE__ */ jsx(IconRefreshOutline16, {
18433
+ size: 14,
18434
+ className: promptUndoClass
18435
+ }) : /* @__PURE__ */ jsx(IconSparkle16, { size: 14 })]
18436
+ })
18437
+ });
18438
+ }
18439
+ //#endregion
18440
+ //#region src/client/claude-prompt-source.ts
18441
+ /**
18442
+ * The user's own prompt snippets as a second `/` group.
18443
+ *
18444
+ * Unlike {@link createClaudeCommandSource}, a pick here settles as plain text:
18445
+ * the pipeline replaces the trigger token with the snippet body and leaves the
18446
+ * caret after it, so the draft stays editable and nothing is sent. A snippet is
18447
+ * a half-written message, not a command.
18448
+ */
18449
+ function createClaudePromptSource(groupName, load = claudePrompts) {
18450
+ let known = [];
18451
+ return {
18452
+ trigger: "/",
18453
+ name: groupName,
18454
+ order: 20,
18455
+ async candidates(_session, request) {
18456
+ if (request.position !== "leading") return [];
18457
+ known = await load();
18458
+ const query = request.query.toLocaleLowerCase();
18459
+ return known.filter((prompt) => prompt.name.toLocaleLowerCase().includes(query)).map((prompt) => ({
18460
+ name: prompt.name,
18461
+ description: prompt.description
18462
+ }));
18463
+ },
18464
+ onPick(pick) {
18465
+ const prompt = known.find((item) => item.name === pick.candidate.name);
18466
+ return prompt === void 0 ? void 0 : { text: prompt.body };
18467
+ }
18468
+ };
18469
+ }
18470
+ //#endregion
17645
18471
  //#region src/client/preset-seat-mark.ts
17646
18472
  /** Flags the Host's agent-preset seat while it names the Claude preset.
17647
18473
  *
@@ -17940,7 +18766,7 @@ window.__ModuleLoader__.load({
17940
18766
  worktreeBranchPrefixEffect: "分支前缀修改会应用于之后自动创建的 Worktree 分支;显式指定的完整分支名不会被修改。",
17941
18767
  maxProcessesSetting: "Claude 进程上限",
17942
18768
  idleTimeoutSetting: "闲置回收时长(分钟)",
17943
- maxProcessesEffect: "进程上限在下次启动 Claude 进程时生效。留空或非法值会被拒绝。",
18769
+ maxProcessesEffect: "保存后立即回收超额空闲进程;运行中的进程完成后再收敛。满载时,新会话会等待可用容量。留空或非法值会被拒绝。",
17944
18770
  idleTimeoutEffect: "闲置回收时长在下一次回合结束后生效。留空或非法值会被拒绝。",
17945
18771
  settingHint: "查看说明",
17946
18772
  pluginUpdate: "插件更新",
@@ -18098,6 +18924,20 @@ window.__ModuleLoader__.load({
18098
18924
  sessionMenu: "会话菜单",
18099
18925
  sessionMenuOpenIn: "打开方式",
18100
18926
  sessionMenuOpenFailed: "无法打开:{message}",
18927
+ promptSource: "常用提示词",
18928
+ promptSave: "存为常用提示词",
18929
+ promptSaveName: "名字",
18930
+ promptSaveNaming: "正在起名…",
18931
+ promptRefine: "AI 优化提示词",
18932
+ promptRefineBusy: "优化中…",
18933
+ promptRefineUndo: "还原成优化前的内容",
18934
+ promptRefineFailed: "优化失败:{message}",
18935
+ promptSaveConfirm: "保存",
18936
+ promptSaveCancel: "取消",
18937
+ promptSaveDone: "知道了",
18938
+ promptSaved: "已存为「{name}」",
18939
+ promptSaveExists: "已有同名提示词,换个名字。",
18940
+ promptSaveFailed: "保存失败:{message}",
18101
18941
  presetHeaderHint: "当前会话运行的 Agent 预设",
18102
18942
  diffWorkingTree: "分支改动",
18103
18943
  diffFiles: "{count} 个已修改文件",
@@ -18160,6 +19000,24 @@ window.__ModuleLoader__.load({
18160
19000
  diffUpdateBranchCompleted: "已更新并推送 commit {commit}",
18161
19001
  diffUpdateBranchConflicts: "更新产生冲突,以下文件需要解决:",
18162
19002
  diffUpdateBranchResolve: "让 Claude 解决冲突",
19003
+ conflictTitle: "解决冲突",
19004
+ conflictOperation_rebase: "Rebase",
19005
+ conflictOperation_merge: "合并",
19006
+ "conflictOperation_cherry-pick": "Cherry-pick",
19007
+ conflictOperation_revert: "Revert",
19008
+ conflictBadge: "{operation} 冲突 {count}",
19009
+ conflictBadgeReady: "继续 {operation}",
19010
+ conflictDescription: "{operation} 尚未完成。解决冲突后继续,或中止回到操作前的状态。",
19011
+ conflictFiles: "以下文件存在冲突,需要解决并 git add:",
19012
+ conflictReady: "冲突已全部解决,可以继续。",
19013
+ conflictResolve: "让 Claude 解决冲突",
19014
+ conflictContinue: "继续 {operation}",
19015
+ conflictAbort: "中止 {operation}",
19016
+ conflictAbortConfirm: "再次点击「中止 {operation}」确认:{operation} 期间的改动会被丢弃。",
19017
+ conflictPush: "完成后推送到远端",
19018
+ conflictContinued: "{operation} 已完成",
19019
+ conflictPushed: "{operation} 已完成并推送",
19020
+ conflictAborted: "已中止 {operation}",
18163
19021
  repositoryChecksOpen: "查看失败的检查",
18164
19022
  checksCardTitle: "失败的检查",
18165
19023
  checksCardLoading: "正在读取检查详情…",
@@ -18350,7 +19208,7 @@ window.__ModuleLoader__.load({
18350
19208
  worktreeBranchPrefixEffect: "Prefix changes apply to subsequently generated Worktree branches. Explicit full branch names remain unchanged.",
18351
19209
  maxProcessesSetting: "Claude process limit",
18352
19210
  idleTimeoutSetting: "Idle timeout (minutes)",
18353
- maxProcessesEffect: "The process limit applies when the next Claude process is admitted. Blank or invalid values are rejected.",
19211
+ 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.",
18354
19212
  idleTimeoutEffect: "The idle timeout applies after the next completed turn. Blank or invalid values are rejected.",
18355
19213
  settingHint: "Show details",
18356
19214
  pluginUpdate: "Plugin updates",
@@ -18508,6 +19366,20 @@ window.__ModuleLoader__.load({
18508
19366
  sessionMenu: "Session menu",
18509
19367
  sessionMenuOpenIn: "Open in",
18510
19368
  sessionMenuOpenFailed: "Could not open: {message}",
19369
+ promptSource: "Prompts",
19370
+ promptSave: "Save as a prompt",
19371
+ promptSaveName: "Name",
19372
+ promptSaveNaming: "Naming it…",
19373
+ promptRefine: "Rewrite with AI",
19374
+ promptRefineBusy: "Rewriting…",
19375
+ promptRefineUndo: "Put the original back",
19376
+ promptRefineFailed: "Could not rewrite: {message}",
19377
+ promptSaveConfirm: "Save",
19378
+ promptSaveCancel: "Cancel",
19379
+ promptSaveDone: "Got it",
19380
+ promptSaved: "Saved as \"{name}\"",
19381
+ promptSaveExists: "A prompt with that name already exists. Try another.",
19382
+ promptSaveFailed: "Could not save: {message}",
18511
19383
  presetHeaderHint: "The agent preset this session runs",
18512
19384
  diffWorkingTree: "Branch changes",
18513
19385
  diffFiles: "{count} modified file(s)",
@@ -18570,6 +19442,24 @@ window.__ModuleLoader__.load({
18570
19442
  diffUpdateBranchCompleted: "Updated and pushed commit {commit}",
18571
19443
  diffUpdateBranchConflicts: "The update left conflicts in these files:",
18572
19444
  diffUpdateBranchResolve: "Have Claude resolve the conflicts",
19445
+ conflictTitle: "Resolve conflicts",
19446
+ conflictOperation_rebase: "Rebase",
19447
+ conflictOperation_merge: "Merge",
19448
+ "conflictOperation_cherry-pick": "Cherry-pick",
19449
+ conflictOperation_revert: "Revert",
19450
+ conflictBadge: "{operation}: {count} conflict(s)",
19451
+ conflictBadgeReady: "Continue {operation}",
19452
+ conflictDescription: "The {operation} is unfinished. Resolve the conflicts and continue, or abort to return to the state before it.",
19453
+ conflictFiles: "These files conflict and must be resolved and staged:",
19454
+ conflictReady: "Every conflict is resolved. The operation can continue.",
19455
+ conflictResolve: "Have Claude resolve the conflicts",
19456
+ conflictContinue: "Continue {operation}",
19457
+ conflictAbort: "Abort {operation}",
19458
+ conflictAbortConfirm: "Press \"Abort {operation}\" again to confirm: work done during the {operation} is discarded.",
19459
+ conflictPush: "Push once it finishes",
19460
+ conflictContinued: "{operation} finished",
19461
+ conflictPushed: "{operation} finished and pushed",
19462
+ conflictAborted: "Aborted the {operation}",
18573
19463
  repositoryChecksOpen: "Show failing checks",
18574
19464
  checksCardTitle: "Failing checks",
18575
19465
  checksCardLoading: "Loading check details…",
@@ -18785,6 +19675,7 @@ window.__ModuleLoader__.load({
18785
19675
  diagnostics.report(kind, detail);
18786
19676
  } });
18787
19677
  ctx.effect(() => ctx.inputTriggers.registerSource(createClaudeCommandSource(ctx, projections)), "dsh-claude: Claude slash source");
19678
+ ctx.effect(() => ctx.inputTriggers.registerSource(createClaudePromptSource(t("promptSource"))), "dsh-claude: Claude prompt source");
18788
19679
  const sessions = ctx.get("sessions");
18789
19680
  const workspaces = ctx.get("workspaces");
18790
19681
  const uiWorkspace = ctx.get("uiWorkspace");
@@ -19131,6 +20022,34 @@ window.__ModuleLoader__.load({
19131
20022
  locale: namespace,
19132
20023
  inject: () => ({ t })
19133
20024
  }, ClaudeSessionMenu));
20025
+ ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
20026
+ name: "conversation.input.left",
20027
+ id: "claude-prompt-save",
20028
+ order: 40,
20029
+ locale: namespace,
20030
+ inject: () => ({ t })
20031
+ }, ClaudePromptSaveAction));
20032
+ ctx.slots.inject("conversation.input.left", () => ctx.slots.register({
20033
+ name: "conversation.input.left",
20034
+ id: "claude-prompt-refine",
20035
+ order: 41,
20036
+ locale: namespace,
20037
+ inject: (sessionId) => {
20038
+ if (sessions === void 0 || conversation === void 0) return { t };
20039
+ const scope = sessions.scope(sessionId);
20040
+ if (scope === void 0) return { t };
20041
+ const facade = sessionInput(conversation, scope);
20042
+ return {
20043
+ t,
20044
+ replaceDraft: (text) => {
20045
+ facade.setDraft(text);
20046
+ },
20047
+ notify: (level, text) => {
20048
+ facade.notify(level, text);
20049
+ }
20050
+ };
20051
+ }
20052
+ }, ClaudePromptRefineAction));
19134
20053
  ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
19135
20054
  name: "conversation.input.dock",
19136
20055
  id: "claude-review-comments",