@norman-else/dsh-claude 0.1.23 → 0.1.25

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, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconChevronDownOutline14, IconCloseOutline16, IconFullscreenOutline16, IconSearchOutline16, IconThinkOutline14, MarkdownText, Menu, Modal, StateDot, Tooltip } = require("@deepseek-ai/dsh-client-ui-primitives");
7
+ var { DiffBlock, DisclosureRow, HoverCard, IconApiOutline14, IconBranchOutline16, IconCheckOutline14, IconCheckOutline16, IconChevronDownOutline14, IconChevronUpOutline14, IconCloseOutline16, IconEditOutline16, IconFullscreenOutline16, IconQueueOutline14, IconSearchOutline16, IconSendOutline14, IconThinkOutline14, IconTrashOutline16, MarkdownText, Menu, Modal, StateDot, Tooltip } = 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
@@ -19,6 +19,8 @@ window.__ModuleLoader__.load({
19
19
  const CLAUDE_REPOSITORY_ACTION_PATH = "/plugins/dsh-claude/repository/action";
20
20
  const CLAUDE_REVIEW_COMMENT_PATH = "/plugins/dsh-claude/review-comments";
21
21
  const CLAUDE_REPOSITORY_FEEDBACK_PATH = "/plugins/dsh-claude/repository/feedback";
22
+ const CLAUDE_REPOSITORY_STATUS_PATH = "/plugins/dsh-claude/repository/status";
23
+ const CLAUDE_JIRA_PATH = "/plugins/dsh-claude/jira";
22
24
  //#endregion
23
25
  //#region src/client/task-projection.ts
24
26
  /** Tasks UI is reserved for detached work and genuine Claude subagents. */
@@ -712,6 +714,8 @@ window.__ModuleLoader__.load({
712
714
  const settingsActions = {
713
715
  display: "flex",
714
716
  flexWrap: "wrap",
717
+ alignItems: "center",
718
+ justifyContent: "flex-end",
715
719
  gap: 8
716
720
  };
717
721
  const notice = {
@@ -1411,8 +1415,93 @@ window.__ModuleLoader__.load({
1411
1415
  lineHeight: "22px",
1412
1416
  textAlign: "left"
1413
1417
  };
1418
+ const queueBar = {
1419
+ width: "100%",
1420
+ minWidth: 0,
1421
+ boxSizing: "border-box",
1422
+ display: "flex",
1423
+ flexDirection: "column",
1424
+ padding: "3px 6px 3px 11px",
1425
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
1426
+ borderRadius: 11,
1427
+ background: "var(--dsw-alias-bg-layer-1)",
1428
+ color: "var(--dsw-alias-label-primary)",
1429
+ boxShadow: "0 1px 2px color-mix(in srgb, var(--dsw-alias-label-primary) 4%, transparent)",
1430
+ font: "inherit",
1431
+ fontSize: 13,
1432
+ lineHeight: "20px"
1433
+ };
1434
+ const queueHeader = {
1435
+ width: "100%",
1436
+ minHeight: 30,
1437
+ display: "flex",
1438
+ alignItems: "center",
1439
+ gap: 8,
1440
+ padding: "2px 0",
1441
+ border: "none",
1442
+ outline: "none",
1443
+ background: "transparent",
1444
+ color: "inherit",
1445
+ font: "inherit",
1446
+ fontSize: 13,
1447
+ textAlign: "left",
1448
+ cursor: "pointer"
1449
+ };
1450
+ const queueLead = {
1451
+ flex: "none",
1452
+ display: "grid",
1453
+ placeItems: "center",
1454
+ color: "var(--dsw-alias-label-tertiary)"
1455
+ };
1456
+ const queueCount = {
1457
+ minWidth: 0,
1458
+ flex: 1,
1459
+ fontWeight: 550
1460
+ };
1461
+ const queueList = {
1462
+ maxHeight: 180,
1463
+ margin: 0,
1464
+ padding: 0,
1465
+ listStyle: "none",
1466
+ overflowY: "auto"
1467
+ };
1468
+ const queueRow = {
1469
+ minHeight: 30,
1470
+ display: "flex",
1471
+ alignItems: "center",
1472
+ gap: 8
1473
+ };
1474
+ const queueRowDivider = { borderTop: "1px solid var(--dsw-alias-border-l1, color-mix(in srgb, currentColor 12%, transparent))" };
1475
+ const queuePreview = {
1476
+ minWidth: 0,
1477
+ flex: 1,
1478
+ overflow: "hidden",
1479
+ color: "var(--dsw-alias-label-secondary)",
1480
+ textOverflow: "ellipsis",
1481
+ whiteSpace: "nowrap"
1482
+ };
1483
+ const queueEditor = {
1484
+ minWidth: 0,
1485
+ flex: 1,
1486
+ height: 26,
1487
+ boxSizing: "border-box",
1488
+ padding: "0 8px",
1489
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
1490
+ borderRadius: 6,
1491
+ outline: "none",
1492
+ background: "var(--dsw-alias-bg-layer-2)",
1493
+ color: "var(--dsw-alias-label-primary)",
1494
+ font: "inherit",
1495
+ fontSize: 13
1496
+ };
1497
+ const queueActions = {
1498
+ flex: "none",
1499
+ display: "inline-flex",
1500
+ alignItems: "center",
1501
+ gap: 2
1502
+ };
1414
1503
  const repositoryBarMerged = {
1415
- borderColor: "color-mix(in srgb, #a78bfa 30%, var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent)))",
1504
+ borderColor: "color-mix(in srgb, #a78bfa 35%, transparent)",
1416
1505
  background: "color-mix(in srgb, #a78bfa 5%, var(--dsw-alias-bg-layer-1))"
1417
1506
  };
1418
1507
  const repositoryPrIcon = {
@@ -1820,6 +1909,105 @@ window.__ModuleLoader__.load({
1820
1909
  fontWeight: 650,
1821
1910
  cursor: "pointer"
1822
1911
  };
1912
+ const heroTicketKey = {
1913
+ flex: "none",
1914
+ color: "var(--dsw-alias-label-tertiary)",
1915
+ fontSize: 11,
1916
+ fontWeight: 650,
1917
+ letterSpacing: .2
1918
+ };
1919
+ const heroTicketStatus = {
1920
+ flex: "none",
1921
+ maxWidth: 90,
1922
+ overflow: "hidden",
1923
+ color: "var(--dsw-alias-label-tertiary)",
1924
+ fontSize: 11,
1925
+ textOverflow: "ellipsis",
1926
+ whiteSpace: "nowrap"
1927
+ };
1928
+ const settingsLink = {
1929
+ color: "var(--dsw-static-blue-450)",
1930
+ fontSize: 12,
1931
+ textDecoration: "none",
1932
+ marginRight: "auto"
1933
+ };
1934
+ const repositoryPrIconButton = {
1935
+ padding: 0,
1936
+ border: "none",
1937
+ outline: "none",
1938
+ background: "transparent",
1939
+ font: "inherit",
1940
+ cursor: "pointer"
1941
+ };
1942
+ const overviewBody = {
1943
+ flex: 1,
1944
+ minHeight: 0,
1945
+ display: "flex",
1946
+ flexDirection: "column",
1947
+ gap: 6,
1948
+ padding: 10,
1949
+ overflowY: "auto"
1950
+ };
1951
+ const overviewRow = {
1952
+ width: "100%",
1953
+ minWidth: 0,
1954
+ boxSizing: "border-box",
1955
+ display: "flex",
1956
+ flexDirection: "column",
1957
+ gap: 4,
1958
+ padding: "8px 10px",
1959
+ border: "1px solid var(--dsw-alias-border-l2, color-mix(in srgb, currentColor 16%, transparent))",
1960
+ borderRadius: 10,
1961
+ outline: "none",
1962
+ background: "var(--dsw-alias-bg-layer-2)",
1963
+ color: "var(--dsw-alias-label-primary)",
1964
+ font: "inherit",
1965
+ textAlign: "left",
1966
+ cursor: "pointer"
1967
+ };
1968
+ const overviewRowTop = {
1969
+ display: "flex",
1970
+ alignItems: "center",
1971
+ gap: 8,
1972
+ minWidth: 0
1973
+ };
1974
+ const overviewTitle = {
1975
+ minWidth: 0,
1976
+ flex: 1,
1977
+ overflow: "hidden",
1978
+ fontSize: 13,
1979
+ fontWeight: 600,
1980
+ textOverflow: "ellipsis",
1981
+ whiteSpace: "nowrap"
1982
+ };
1983
+ const overviewMeta = {
1984
+ display: "flex",
1985
+ alignItems: "center",
1986
+ gap: 8,
1987
+ minWidth: 0,
1988
+ overflow: "hidden",
1989
+ color: "var(--dsw-alias-label-tertiary)",
1990
+ fontSize: 12,
1991
+ whiteSpace: "nowrap"
1992
+ };
1993
+ const overviewBranch = {
1994
+ minWidth: 0,
1995
+ overflow: "hidden",
1996
+ textOverflow: "ellipsis"
1997
+ };
1998
+ const overviewRunningDot = {
1999
+ width: 7,
2000
+ height: 7,
2001
+ flex: "none",
2002
+ borderRadius: 999,
2003
+ background: "var(--dsw-static-blue-450)"
2004
+ };
2005
+ const overviewEmpty = {
2006
+ padding: 16,
2007
+ color: "var(--dsw-alias-label-tertiary)",
2008
+ fontSize: 13,
2009
+ textAlign: "center"
2010
+ };
1823
2011
  const repositoryMergeTrigger = {
1824
2012
  flex: "none",
1825
2013
  display: "inline-flex",
@@ -2830,7 +3018,7 @@ window.__ModuleLoader__.load({
2830
3018
  const MAX_REVIEW_COMMENTS = 50;
2831
3019
  const MAX_REVIEW_COMMENT_CHARS = 2e3;
2832
3020
  const MAX_TRANSCRIPT_CHARS = 64e3;
2833
- function record$5(value) {
3021
+ function record$6(value) {
2834
3022
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2835
3023
  }
2836
3024
  function nonNegativeInteger(value) {
@@ -2840,18 +3028,18 @@ window.__ModuleLoader__.load({
2840
3028
  return value === void 0 || typeof value === "string" && value.length <= MAX_REPOSITORY_TEXT_CHARS;
2841
3029
  }
2842
3030
  function validateRepository(value) {
2843
- const repository = record$5(value);
3031
+ const repository = record$6(value);
2844
3032
  if (repository === void 0 || ![
2845
3033
  "ready",
2846
3034
  "not-repository",
2847
3035
  "unavailable"
2848
3036
  ].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;
2849
3037
  if (repository.diff !== void 0) {
2850
- const diff = record$5(repository.diff);
3038
+ const diff = record$6(repository.diff);
2851
3039
  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;
2852
3040
  }
2853
3041
  if (repository.pullRequest === void 0) return true;
2854
- const pullRequest = record$5(repository.pullRequest);
3042
+ const pullRequest = record$6(repository.pullRequest);
2855
3043
  if (pullRequest === void 0 || !Number.isSafeInteger(pullRequest.number) || Number(pullRequest.number) <= 0 || typeof pullRequest.title !== "string" || pullRequest.title.length > MAX_REPOSITORY_TEXT_CHARS || typeof pullRequest.url !== "string" || pullRequest.url.length > MAX_REPOSITORY_TEXT_CHARS || ![
2856
3044
  "open",
2857
3045
  "closed",
@@ -2876,24 +3064,24 @@ window.__ModuleLoader__.load({
2876
3064
  }
2877
3065
  /** Validate the public route envelope before publishing it to UI components. */
2878
3066
  function parseClaudeClientProjection(value) {
2879
- const input = record$5(value);
3067
+ const input = record$6(value);
2880
3068
  if (input === void 0 || input.schemaVersion !== 1 || !nonNegativeInteger(input.revision) || typeof input.owned !== "boolean" || !Array.isArray(input.commands) || input.commands.length > MAX_COMMANDS || !Array.isArray(input.activities) || input.activities.length > MAX_ACTIVITIES) throw new Error("invalid Claude sidecar projection");
2881
3069
  for (const item of input.commands) {
2882
- const command = record$5(item);
3070
+ const command = record$6(item);
2883
3071
  if (command === void 0 || typeof command.publicName !== "string" || typeof command.claudeName !== "string" || typeof command.description !== "string" || command.hint !== void 0 && typeof command.hint !== "string" || typeof command.prefixed !== "boolean") throw new Error("invalid Claude command projection");
2884
3072
  }
2885
3073
  for (const item of input.activities) {
2886
- const activity = record$5(item);
3074
+ const activity = record$6(item);
2887
3075
  if (activity === void 0 || !nonNegativeInteger(activity.turn) || !nonNegativeInteger(activity.step) || !nonNegativeInteger(activity.ordinal) || typeof activity.kind !== "string") throw new Error("invalid Claude sidecar activity");
2888
3076
  }
2889
- if (input.contextUsage !== void 0 && record$5(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
2890
- const tasks = input.tasks === void 0 ? void 0 : record$5(input.tasks);
3077
+ if (input.contextUsage !== void 0 && record$6(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
3078
+ const tasks = input.tasks === void 0 ? void 0 : record$6(input.tasks);
2891
3079
  if (tasks !== void 0 && !Array.isArray(tasks.tasks)) throw new Error("invalid Claude tasks projection");
2892
3080
  if (input.repository !== void 0 && !validateRepository(input.repository)) throw new Error("invalid Claude repository projection");
2893
3081
  if (input.reviewComments !== void 0) {
2894
3082
  if (!Array.isArray(input.reviewComments) || input.reviewComments.length > MAX_REVIEW_COMMENTS) throw new Error("invalid Claude review comment projection");
2895
3083
  for (const item of input.reviewComments) {
2896
- const comment = record$5(item);
3084
+ const comment = record$6(item);
2897
3085
  if (comment === void 0 || typeof comment.id !== "string" || comment.id.length === 0 || comment.id.length > 128 || typeof comment.path !== "string" || comment.path.length === 0 || comment.path.length > MAX_REPOSITORY_TEXT_CHARS || !nonNegativeInteger(comment.line) || comment.side !== "old" && comment.side !== "new" || typeof comment.text !== "string" || comment.text.length > MAX_REVIEW_COMMENT_CHARS) throw new Error("invalid Claude review comment projection");
2898
3086
  }
2899
3087
  }
@@ -3091,7 +3279,7 @@ window.__ModuleLoader__.load({
3091
3279
  } catch {
3092
3280
  return;
3093
3281
  }
3094
- const event = record$5(value);
3282
+ const event = record$6(value);
3095
3283
  if (event === void 0 || typeof event.type !== "string") return;
3096
3284
  try {
3097
3285
  switch (event.type) {
@@ -3350,7 +3538,7 @@ window.__ModuleLoader__.load({
3350
3538
  return value;
3351
3539
  }
3352
3540
  }
3353
- function record$4(value) {
3541
+ function record$5(value) {
3354
3542
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3355
3543
  }
3356
3544
  function text(value) {
@@ -3364,7 +3552,7 @@ window.__ModuleLoader__.load({
3364
3552
  if (typeof value === "string") return value;
3365
3553
  if (typeof value === "number" || typeof value === "boolean") return String(value);
3366
3554
  if (Array.isArray(value)) return value.map(displayValue).join(", ");
3367
- return record$4(value) === void 0 ? String(value) : Object.entries(value).map(([key, item]) => `${key}: ${displayValue(item)}`).join("\n");
3555
+ return record$5(value) === void 0 ? String(value) : Object.entries(value).map(([key, item]) => `${key}: ${displayValue(item)}`).join("\n");
3368
3556
  }
3369
3557
  function Section({ title, children }) {
3370
3558
  return /* @__PURE__ */ jsxs("section", {
@@ -3434,15 +3622,15 @@ window.__ModuleLoader__.load({
3434
3622
  function ToolPresentation({ tool, t }) {
3435
3623
  const inputValue = parsedValue(tool.input);
3436
3624
  const outputValue = parsedValue(tool.output);
3437
- const input = record$4(inputValue);
3438
- const output = record$4(outputValue);
3625
+ const input = record$5(inputValue);
3626
+ const output = record$5(outputValue);
3439
3627
  const outputTitle = tool.isError === true ? t("toolError") : t("toolOutput");
3440
3628
  if (tool.diffs !== void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DiffBlock, { diffs: [...tool.diffs] }), /* @__PURE__ */ jsx(TextDetail, {
3441
3629
  title: outputTitle,
3442
3630
  value: typeof outputValue === "string" ? outputValue : void 0
3443
3631
  })] });
3444
3632
  if (tool.toolName === "Read") {
3445
- const file = record$4(output?.file);
3633
+ const file = record$5(output?.file);
3446
3634
  const path = text(file?.filePath) ?? text(input?.file_path);
3447
3635
  const content = text(file?.content) ?? (typeof outputValue === "string" ? outputValue : void 0);
3448
3636
  const offset = numberValue(input?.offset) ?? 1;
@@ -3631,6 +3819,89 @@ window.__ModuleLoader__.load({
3631
3819
  });
3632
3820
  }
3633
3821
  //#endregion
3822
+ //#region src/client/jira-api.ts
3823
+ var JiraClientError = class extends Error {
3824
+ code;
3825
+ constructor(message, code) {
3826
+ super(message);
3827
+ this.name = "JiraClientError";
3828
+ if (code !== void 0) this.code = code;
3829
+ }
3830
+ };
3831
+ function record$4(value) {
3832
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3833
+ }
3834
+ async function call(path, init) {
3835
+ const response = await fetch(`${CLAUDE_JIRA_PATH}${path}`, {
3836
+ credentials: "same-origin",
3837
+ ...init
3838
+ });
3839
+ const body = record$4(await response.json());
3840
+ if (!response.ok) throw new JiraClientError(typeof body?.message === "string" ? body.message : "Jira is unavailable.", typeof body?.error === "string" ? body.error : void 0);
3841
+ if (body === void 0) throw new JiraClientError("Invalid Jira response.");
3842
+ return body;
3843
+ }
3844
+ function status(body) {
3845
+ if (typeof body.connected !== "boolean") throw new JiraClientError("Invalid Jira status response.");
3846
+ return body;
3847
+ }
3848
+ async function loadJiraStatus(signal) {
3849
+ return status(await call("/status", {
3850
+ method: "GET",
3851
+ headers: { accept: "application/json" },
3852
+ ...signal === void 0 ? {} : { signal }
3853
+ }));
3854
+ }
3855
+ async function connectJira(input) {
3856
+ return status(await call("/connect", {
3857
+ method: "POST",
3858
+ headers: {
3859
+ accept: "application/json",
3860
+ "content-type": "application/json"
3861
+ },
3862
+ body: JSON.stringify(input)
3863
+ }));
3864
+ }
3865
+ async function disconnectJira() {
3866
+ await call("/disconnect", {
3867
+ method: "POST",
3868
+ headers: { accept: "application/json" }
3869
+ });
3870
+ }
3871
+ async function searchJiraTickets(query, signal) {
3872
+ const body = await call(`/search?query=${encodeURIComponent(query)}`, {
3873
+ method: "GET",
3874
+ headers: { accept: "application/json" },
3875
+ ...signal === void 0 ? {} : { signal }
3876
+ });
3877
+ if (!Array.isArray(body.tickets)) throw new JiraClientError("Invalid Jira search response.");
3878
+ const tickets = [];
3879
+ for (const item of body.tickets) {
3880
+ const ticket = record$4(item);
3881
+ if (ticket === void 0 || typeof ticket.key !== "string" || typeof ticket.summary !== "string" || typeof ticket.url !== "string") continue;
3882
+ tickets.push(ticket);
3883
+ }
3884
+ return tickets;
3885
+ }
3886
+ async function assignJiraTicket(key) {
3887
+ await call("/assign", {
3888
+ method: "POST",
3889
+ headers: {
3890
+ accept: "application/json",
3891
+ "content-type": "application/json"
3892
+ },
3893
+ body: JSON.stringify({ key })
3894
+ });
3895
+ }
3896
+ /** Draft seeded into the composer when a session starts from a ticket. */
3897
+ function ticketPrompt(ticket) {
3898
+ return `Work on Jira ticket ${ticket.key}: ${ticket.summary}\n${ticket.url}\n\nRead the ticket, implement what it asks for, and reference ${ticket.key} in the commit and pull request.`;
3899
+ }
3900
+ /** Appended to a user-written draft so the session still knows its ticket. */
3901
+ function ticketContext(ticket) {
3902
+ return `This task is for Jira ticket ${ticket.key}: ${ticket.summary}\n${ticket.url}\n\nRead the ticket for full context and reference ${ticket.key} in the commit and pull request.`;
3903
+ }
3904
+ //#endregion
3634
3905
  //#region src/client/ClaudeCodeSettings.tsx
3635
3906
  function isPluginUpdateStatus(value) {
3636
3907
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -3825,6 +4096,49 @@ window.__ModuleLoader__.load({
3825
4096
  const [globalSettings, setGlobalSettings] = useState();
3826
4097
  const [globalSettingsError, setGlobalSettingsError] = useState();
3827
4098
  const [globalSettingsBusy, setGlobalSettingsBusy] = useState(false);
4099
+ const [jiraStatus, setJiraStatus] = useState();
4100
+ const [jiraError, setJiraError] = useState();
4101
+ const [jiraBusy, setJiraBusy] = useState(false);
4102
+ const [jiraSite, setJiraSite] = useState("");
4103
+ const [jiraEmail, setJiraEmail] = useState("");
4104
+ const [jiraToken, setJiraToken] = useState("");
4105
+ useEffect(() => {
4106
+ const controller = new AbortController();
4107
+ loadJiraStatus(controller.signal).then(setJiraStatus, (reason) => {
4108
+ if (!controller.signal.aborted) setJiraError(reason instanceof Error ? reason.message : String(reason));
4109
+ });
4110
+ return () => {
4111
+ controller.abort();
4112
+ };
4113
+ }, []);
4114
+ const connectJiraNow = async () => {
4115
+ setJiraBusy(true);
4116
+ setJiraError(void 0);
4117
+ try {
4118
+ setJiraStatus(await connectJira({
4119
+ siteUrl: jiraSite,
4120
+ email: jiraEmail,
4121
+ apiToken: jiraToken
4122
+ }));
4123
+ setJiraToken("");
4124
+ } catch (cause) {
4125
+ setJiraError(cause instanceof Error ? cause.message : String(cause));
4126
+ } finally {
4127
+ setJiraBusy(false);
4128
+ }
4129
+ };
4130
+ const disconnectJiraNow = async () => {
4131
+ setJiraBusy(true);
4132
+ setJiraError(void 0);
4133
+ try {
4134
+ await disconnectJira();
4135
+ setJiraStatus({ connected: false });
4136
+ } catch (cause) {
4137
+ setJiraError(cause instanceof Error ? cause.message : String(cause));
4138
+ } finally {
4139
+ setJiraBusy(false);
4140
+ }
4141
+ };
3828
4142
  const refresh = useCallback(async () => {
3829
4143
  setBusy(true);
3830
4144
  setError(void 0);
@@ -4037,28 +4351,144 @@ window.__ModuleLoader__.load({
4037
4351
  /* @__PURE__ */ jsxs("section", {
4038
4352
  style: settingsCard,
4039
4353
  children: [
4040
- /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
4041
- style: settingsSectionHeading,
4042
- children: t("pluginUpdate")
4043
- }), /* @__PURE__ */ jsx("p", {
4044
- style: settingsBody,
4045
- children: t("pluginUpdateBody")
4046
- })] }),
4047
- updateStatus === void 0 ? /* @__PURE__ */ jsx("p", {
4354
+ /* @__PURE__ */ jsxs("div", {
4355
+ style: settingsCardHeader,
4356
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
4357
+ style: settingsSectionHeading,
4358
+ children: t("jiraTitle")
4359
+ }), /* @__PURE__ */ jsx("p", {
4360
+ style: settingsBody,
4361
+ children: t("jiraBody")
4362
+ })] }), jiraStatus?.connected === true ? /* @__PURE__ */ jsx("button", {
4363
+ type: "button",
4364
+ style: button,
4365
+ disabled: jiraBusy,
4366
+ onClick: () => {
4367
+ disconnectJiraNow();
4368
+ },
4369
+ children: t("jiraDisconnect")
4370
+ }) : null]
4371
+ }),
4372
+ jiraStatus === void 0 && jiraError === void 0 ? /* @__PURE__ */ jsx("p", {
4048
4373
  style: notice,
4049
- children: t("updateNotChecked")
4050
- }) : /* @__PURE__ */ jsxs("div", {
4374
+ children: t("jiraLoading")
4375
+ }) : null,
4376
+ jiraStatus?.connected === true ? /* @__PURE__ */ jsxs("div", {
4051
4377
  style: diagnosticGrid,
4052
- children: [
4053
- /* @__PURE__ */ jsx("span", {
4378
+ children: [/* @__PURE__ */ jsx("span", {
4379
+ style: diagnosticLabel,
4380
+ children: t("jiraConnected")
4381
+ }), /* @__PURE__ */ jsxs("span", { children: [
4382
+ jiraStatus.siteUrl,
4383
+ " · ",
4384
+ jiraStatus.displayName ?? jiraStatus.email
4385
+ ] })]
4386
+ }) : jiraStatus === void 0 ? null : /* @__PURE__ */ jsxs(Fragment$1, { children: [
4387
+ /* @__PURE__ */ jsxs("div", {
4388
+ style: diagnosticGrid,
4389
+ children: [/* @__PURE__ */ jsx("span", {
4054
4390
  style: diagnosticLabel,
4055
- children: t("installedVersion")
4056
- }),
4057
- /* @__PURE__ */ jsx("span", {
4058
- style: diagnosticValue,
4059
- children: updateStatus.currentVersion
4060
- }),
4061
- /* @__PURE__ */ jsx("span", {
4391
+ children: t("jiraSite")
4392
+ }), /* @__PURE__ */ jsx("input", {
4393
+ type: "url",
4394
+ value: jiraSite,
4395
+ placeholder: t("jiraSitePlaceholder"),
4396
+ disabled: jiraBusy,
4397
+ style: settingTextInput,
4398
+ onChange: (event) => {
4399
+ setJiraSite(event.currentTarget.value);
4400
+ }
4401
+ })]
4402
+ }),
4403
+ /* @__PURE__ */ jsxs("div", {
4404
+ style: diagnosticGrid,
4405
+ children: [/* @__PURE__ */ jsx("span", {
4406
+ style: diagnosticLabel,
4407
+ children: t("jiraEmail")
4408
+ }), /* @__PURE__ */ jsx("input", {
4409
+ type: "email",
4410
+ value: jiraEmail,
4411
+ disabled: jiraBusy,
4412
+ style: settingTextInput,
4413
+ onChange: (event) => {
4414
+ setJiraEmail(event.currentTarget.value);
4415
+ }
4416
+ })]
4417
+ }),
4418
+ /* @__PURE__ */ jsxs("div", {
4419
+ style: diagnosticGrid,
4420
+ children: [/* @__PURE__ */ jsx("span", {
4421
+ style: diagnosticLabel,
4422
+ children: t("jiraToken")
4423
+ }), /* @__PURE__ */ jsx("input", {
4424
+ type: "password",
4425
+ value: jiraToken,
4426
+ disabled: jiraBusy,
4427
+ autoComplete: "off",
4428
+ style: settingTextInput,
4429
+ onChange: (event) => {
4430
+ setJiraToken(event.currentTarget.value);
4431
+ }
4432
+ })]
4433
+ }),
4434
+ /* @__PURE__ */ jsxs("div", {
4435
+ style: settingsActions,
4436
+ children: [/* @__PURE__ */ jsx("a", {
4437
+ href: "https://id.atlassian.com/manage-profile/security/api-tokens",
4438
+ target: "_blank",
4439
+ rel: "noopener noreferrer",
4440
+ style: settingsLink,
4441
+ children: t("jiraTokenHelp")
4442
+ }), /* @__PURE__ */ jsx("button", {
4443
+ type: "button",
4444
+ style: primaryButton,
4445
+ disabled: jiraBusy || jiraSite.trim() === "" || jiraEmail.trim() === "" || jiraToken.trim() === "",
4446
+ onClick: () => {
4447
+ connectJiraNow();
4448
+ },
4449
+ children: jiraBusy ? t("jiraConnecting") : t("jiraConnect")
4450
+ })]
4451
+ })
4452
+ ] }),
4453
+ jiraError === void 0 ? null : /* @__PURE__ */ jsxs("p", {
4454
+ role: "alert",
4455
+ style: {
4456
+ ...notice,
4457
+ color: "var(--dsw-alias-state-error-primary)"
4458
+ },
4459
+ children: [
4460
+ t("jiraError"),
4461
+ ": ",
4462
+ jiraError
4463
+ ]
4464
+ })
4465
+ ]
4466
+ }),
4467
+ /* @__PURE__ */ jsxs("section", {
4468
+ style: settingsCard,
4469
+ children: [
4470
+ /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("h3", {
4471
+ style: settingsSectionHeading,
4472
+ children: t("pluginUpdate")
4473
+ }), /* @__PURE__ */ jsx("p", {
4474
+ style: settingsBody,
4475
+ children: t("pluginUpdateBody")
4476
+ })] }),
4477
+ updateStatus === void 0 ? /* @__PURE__ */ jsx("p", {
4478
+ style: notice,
4479
+ children: t("updateNotChecked")
4480
+ }) : /* @__PURE__ */ jsxs("div", {
4481
+ style: diagnosticGrid,
4482
+ children: [
4483
+ /* @__PURE__ */ jsx("span", {
4484
+ style: diagnosticLabel,
4485
+ children: t("installedVersion")
4486
+ }),
4487
+ /* @__PURE__ */ jsx("span", {
4488
+ style: diagnosticValue,
4489
+ children: updateStatus.currentVersion
4490
+ }),
4491
+ /* @__PURE__ */ jsx("span", {
4062
4492
  style: diagnosticLabel,
4063
4493
  children: t("installSource")
4064
4494
  }),
@@ -4217,8 +4647,138 @@ window.__ModuleLoader__.load({
4217
4647
  })).then(result);
4218
4648
  }
4219
4649
  //#endregion
4220
- //#region src/client/pr-feedback-api.ts
4650
+ //#region src/client/repository-setup-api.ts
4651
+ const HOST_STAGES = /* @__PURE__ */ new Set([
4652
+ "inspecting",
4653
+ "fetching",
4654
+ "creating-worktree",
4655
+ "saving-worktree",
4656
+ "switching-branch"
4657
+ ]);
4221
4658
  function record$2(value) {
4659
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4660
+ }
4661
+ function setupResult(value) {
4662
+ const item = record$2(value);
4663
+ 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;
4664
+ return item;
4665
+ }
4666
+ function parseRepositorySetupEvent(line, onProgress) {
4667
+ const event = record$2(JSON.parse(line));
4668
+ if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
4669
+ onProgress(event.stage);
4670
+ return;
4671
+ }
4672
+ if (event?.type === "complete") {
4673
+ const result = setupResult(event.result);
4674
+ if (result !== void 0) return result;
4675
+ }
4676
+ if (event?.type === "error" && typeof event.message === "string") throw new Error(event.message);
4677
+ throw new Error("Invalid repository setup progress response.");
4678
+ }
4679
+ async function response(pending) {
4680
+ const result = await pending;
4681
+ const body = await result.json();
4682
+ if (!result.ok) {
4683
+ const error = body;
4684
+ throw new Error(error.message ?? error.error ?? "Repository setup failed.");
4685
+ }
4686
+ return body;
4687
+ }
4688
+ function loadRepositoryBranches(cwd, signal) {
4689
+ return response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches?cwd=${encodeURIComponent(cwd)}`, {
4690
+ method: "GET",
4691
+ credentials: "same-origin",
4692
+ headers: { accept: "application/json" },
4693
+ ...signal === void 0 ? {} : { signal }
4694
+ }));
4695
+ }
4696
+ async function prepareRepository(cwd, branch, worktree, branchName, onProgress = () => {}) {
4697
+ const result = await fetch(CLAUDE_REPOSITORY_SETUP_PATH, {
4698
+ method: "POST",
4699
+ credentials: "same-origin",
4700
+ headers: {
4701
+ accept: "application/x-ndjson",
4702
+ "content-type": "application/json"
4703
+ },
4704
+ body: JSON.stringify({
4705
+ cwd,
4706
+ branch,
4707
+ worktree,
4708
+ ...branchName === void 0 ? {} : { branchName }
4709
+ })
4710
+ });
4711
+ if (!result.ok) {
4712
+ const body = await result.json();
4713
+ throw new Error(body.message ?? body.error ?? "Repository setup failed.");
4714
+ }
4715
+ if (result.body === null) throw new Error("Repository setup progress stream is unavailable.");
4716
+ const reader = result.body.getReader();
4717
+ const decoder = new TextDecoder();
4718
+ let buffer = "";
4719
+ let completed;
4720
+ while (true) {
4721
+ const chunk = await reader.read();
4722
+ buffer += decoder.decode(chunk.value, { stream: !chunk.done });
4723
+ const lines = buffer.split("\n");
4724
+ buffer = lines.pop() ?? "";
4725
+ for (const line of lines) {
4726
+ if (line.trim().length === 0) continue;
4727
+ const value = parseRepositorySetupEvent(line, onProgress);
4728
+ if (value !== void 0) completed = value;
4729
+ }
4730
+ if (chunk.done) break;
4731
+ }
4732
+ if (buffer.trim().length > 0) {
4733
+ const value = parseRepositorySetupEvent(buffer, onProgress);
4734
+ if (value !== void 0) completed = value;
4735
+ }
4736
+ if (completed === void 0) throw new Error("Repository setup progress ended before completion.");
4737
+ return completed;
4738
+ }
4739
+ async function bindRepositoryLease(leaseId, sessionId) {
4740
+ await response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/bind`, {
4741
+ method: "POST",
4742
+ credentials: "same-origin",
4743
+ headers: {
4744
+ accept: "application/json",
4745
+ "content-type": "application/json"
4746
+ },
4747
+ body: JSON.stringify({
4748
+ leaseId,
4749
+ sessionId
4750
+ })
4751
+ }));
4752
+ }
4753
+ async function cleanupMergedRepository(path, baseBranch) {
4754
+ const body = await response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/cleanup`, {
4755
+ method: "POST",
4756
+ credentials: "same-origin",
4757
+ headers: {
4758
+ accept: "application/json",
4759
+ "content-type": "application/json"
4760
+ },
4761
+ body: JSON.stringify({
4762
+ path,
4763
+ baseBranch
4764
+ })
4765
+ }));
4766
+ if (body.mode !== "worktree" && body.mode !== "checkout" || typeof body.root !== "string" || typeof body.branch !== "string") throw new Error("Invalid repository cleanup response.");
4767
+ return body;
4768
+ }
4769
+ async function loadRepositoryStatusFor(cwd, signal) {
4770
+ const body = await response(fetch(`${CLAUDE_REPOSITORY_STATUS_PATH}?cwd=${encodeURIComponent(cwd)}`, {
4771
+ method: "GET",
4772
+ credentials: "same-origin",
4773
+ headers: { accept: "application/json" },
4774
+ ...signal === void 0 ? {} : { signal }
4775
+ }));
4776
+ if (typeof body.status !== "string" || typeof body.cwd !== "string") throw new Error("Invalid repository status response.");
4777
+ return body;
4778
+ }
4779
+ //#endregion
4780
+ //#region src/client/pr-feedback-api.ts
4781
+ function record$1(value) {
4222
4782
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4223
4783
  }
4224
4784
  async function loadJson(path, sessionId, pullNumber, signal) {
@@ -4228,7 +4788,7 @@ window.__ModuleLoader__.load({
4228
4788
  headers: { accept: "application/json" },
4229
4789
  ...signal === void 0 ? {} : { signal }
4230
4790
  });
4231
- const body = record$2(await response.json());
4791
+ const body = record$1(await response.json());
4232
4792
  if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
4233
4793
  if (body === void 0) throw new Error("Invalid pull request feedback response.");
4234
4794
  return body;
@@ -4238,7 +4798,7 @@ window.__ModuleLoader__.load({
4238
4798
  if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
4239
4799
  const comments = [];
4240
4800
  for (const item of body.comments) {
4241
- const input = record$2(item);
4801
+ const input = record$1(item);
4242
4802
  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.side !== "new" && input.side !== "old" || input.line !== void 0 && typeof input.line !== "number") continue;
4243
4803
  comments.push(input);
4244
4804
  }
@@ -4249,7 +4809,7 @@ window.__ModuleLoader__.load({
4249
4809
  if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
4250
4810
  const checks = [];
4251
4811
  for (const item of body.checks) {
4252
- const input = record$2(item);
4812
+ const input = record$1(item);
4253
4813
  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;
4254
4814
  checks.push(input);
4255
4815
  }
@@ -4410,7 +4970,7 @@ window.__ModuleLoader__.load({
4410
4970
  ]
4411
4971
  });
4412
4972
  }
4413
- function repositoryName(remote) {
4973
+ function repositoryName$1(remote) {
4414
4974
  return remote?.split("/").at(-1);
4415
4975
  }
4416
4976
  function relativeAge(value) {
@@ -4447,7 +5007,7 @@ window.__ModuleLoader__.load({
4447
5007
  /* @__PURE__ */ jsxs("span", {
4448
5008
  style: repositoryPrHoverRepo,
4449
5009
  children: [
4450
- repositoryName(repository.remote),
5010
+ repositoryName$1(repository.remote),
4451
5011
  " #",
4452
5012
  pullRequest.number,
4453
5013
  pullRequest.baseBranch === void 0 ? "" : ` → ${pullRequest.baseBranch}`
@@ -4897,6 +5457,112 @@ window.__ModuleLoader__.load({
4897
5457
  })
4898
5458
  ] });
4899
5459
  }
5460
+ /** After a merge: remove the worktree (or switch a plain checkout back to
5461
+ * base), delete the merged branch, and drop the DSH workspace. */
5462
+ function CleanupControl({ repository, t, deleteWorkspace }) {
5463
+ const [dialog, setDialog] = useState();
5464
+ const pullRequest = repository.pullRequest;
5465
+ const base = pullRequest?.baseBranch;
5466
+ useEffect(() => {
5467
+ if (dialog?.done === void 0) return;
5468
+ const timer = setTimeout(() => setDialog(void 0), 1500);
5469
+ return () => clearTimeout(timer);
5470
+ }, [dialog?.done]);
5471
+ if (pullRequest?.state !== "merged" || base === void 0 || repository.root === void 0 || repository.detached === true) return null;
5472
+ const root = repository.root;
5473
+ const closeDialog = () => {
5474
+ if (dialog?.submitting !== true) setDialog(void 0);
5475
+ };
5476
+ const confirm = () => {
5477
+ setDialog({ submitting: true });
5478
+ cleanupMergedRepository(root, base).then(async (result) => {
5479
+ if (result.mode === "worktree" && deleteWorkspace !== void 0) await deleteWorkspace();
5480
+ setDialog({
5481
+ submitting: false,
5482
+ done: result.branch
5483
+ });
5484
+ }, (reason) => {
5485
+ setDialog({
5486
+ submitting: false,
5487
+ error: reason instanceof Error ? reason.message : t("diffActionFailed")
5488
+ });
5489
+ });
5490
+ };
5491
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
5492
+ /* @__PURE__ */ jsx("button", {
5493
+ type: "button",
5494
+ style: repositoryUpdateTrigger,
5495
+ title: t("cleanupTitle"),
5496
+ onClick: () => {
5497
+ setDialog({ submitting: false });
5498
+ },
5499
+ children: t("cleanupButton")
5500
+ }),
5501
+ dialog === void 0 ? null : /* @__PURE__ */ jsx("style", {
5502
+ "data-dsh-claude-repository-modal-styles": true,
5503
+ children: diffModalCss
5504
+ }),
5505
+ /* @__PURE__ */ jsx(Modal, {
5506
+ className: "dshClaudeRepositoryActionModal",
5507
+ contentClassName: "dshClaudeRepositoryActionModalContent",
5508
+ open: dialog !== void 0,
5509
+ onClose: closeDialog,
5510
+ title: t("cleanupTitle"),
5511
+ closeLabel: t("diffCancel"),
5512
+ description: t("cleanupDescription"),
5513
+ footer: /* @__PURE__ */ jsxs("div", {
5514
+ style: diffModalFooter,
5515
+ children: [/* @__PURE__ */ jsx("button", {
5516
+ type: "button",
5517
+ style: {
5518
+ ...button,
5519
+ ...diffModalButton
5520
+ },
5521
+ disabled: dialog?.submitting === true,
5522
+ onClick: closeDialog,
5523
+ children: dialog?.done === void 0 ? t("diffCancel") : t("diffDone")
5524
+ }), dialog?.done === void 0 ? /* @__PURE__ */ jsx("button", {
5525
+ type: "button",
5526
+ style: {
5527
+ ...primaryButton,
5528
+ ...diffModalButton
5529
+ },
5530
+ disabled: dialog?.submitting === true,
5531
+ onClick: confirm,
5532
+ children: dialog?.submitting === true ? t("diffSubmitting") : t("diffConfirm")
5533
+ }) : null]
5534
+ }),
5535
+ children: dialog === void 0 ? null : /* @__PURE__ */ jsxs("div", {
5536
+ style: diffModalBody,
5537
+ children: [
5538
+ /* @__PURE__ */ jsxs("div", {
5539
+ style: diffModalMeta,
5540
+ children: [/* @__PURE__ */ jsxs("strong", {
5541
+ style: diffModalMetaText,
5542
+ children: [
5543
+ repository.branch ?? t("repositoryUnknownBranch"),
5544
+ " → ",
5545
+ base
5546
+ ]
5547
+ }), /* @__PURE__ */ jsx("span", {
5548
+ style: diffModalFileState,
5549
+ children: repository.worktree === true ? t("repositoryWorktree") : t("repositoryLocal")
5550
+ })]
5551
+ }),
5552
+ dialog.done === void 0 ? null : /* @__PURE__ */ jsx("p", {
5553
+ style: diffModalSuccess,
5554
+ children: t("cleanupCompleted", { branch: dialog.done })
5555
+ }),
5556
+ dialog.error === void 0 ? null : /* @__PURE__ */ jsx("p", {
5557
+ role: "alert",
5558
+ style: diffModalError,
5559
+ children: dialog.error
5560
+ })
5561
+ ]
5562
+ })
5563
+ })
5564
+ ] });
5565
+ }
4900
5566
  const MERGE_METHODS = [
4901
5567
  "merge",
4902
5568
  "squash",
@@ -5066,7 +5732,7 @@ window.__ModuleLoader__.load({
5066
5732
  })
5067
5733
  ] });
5068
5734
  }
5069
- function ClaudeRepositoryStatus({ sessionId, useSessions, useClaudeProjection, t, openDiff, submitPrompt }) {
5735
+ function ClaudeRepositoryStatus({ sessionId, useSessions, useClaudeProjection, t, openDiff, submitPrompt, openOverview, deleteWorkspace }) {
5070
5736
  const blank = useSessions((value) => value.byId[sessionId]?.blank === true);
5071
5737
  const running = useSessions((value) => value.byId[sessionId]?.running === true);
5072
5738
  const projection = useClaudeProjection((value) => value);
@@ -5108,12 +5774,23 @@ window.__ModuleLoader__.load({
5108
5774
  ...merged ? repositoryBarMerged : {}
5109
5775
  },
5110
5776
  children: [
5111
- /* @__PURE__ */ jsx("span", {
5777
+ openOverview === void 0 ? /* @__PURE__ */ jsx("span", {
5112
5778
  style: {
5113
5779
  ...repositoryPrIcon,
5114
5780
  ...merged ? repositoryPrIconMerged : {}
5115
5781
  },
5116
5782
  children: /* @__PURE__ */ jsx(PullRequestIcon, { merged })
5783
+ }) : /* @__PURE__ */ jsx("button", {
5784
+ type: "button",
5785
+ style: {
5786
+ ...repositoryPrIcon,
5787
+ ...merged ? repositoryPrIconMerged : {},
5788
+ ...repositoryPrIconButton
5789
+ },
5790
+ "aria-label": t("overviewOpen"),
5791
+ title: t("overviewOpen"),
5792
+ onClick: openOverview,
5793
+ children: /* @__PURE__ */ jsx(PullRequestIcon, { merged })
5117
5794
  }),
5118
5795
  /* @__PURE__ */ jsx(PullRequestLink, {
5119
5796
  repository,
@@ -5121,7 +5798,7 @@ window.__ModuleLoader__.load({
5121
5798
  }),
5122
5799
  repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
5123
5800
  style: repositoryRemote,
5124
- children: repositoryName(repository.remote)
5801
+ children: repositoryName$1(repository.remote)
5125
5802
  }),
5126
5803
  /* @__PURE__ */ jsx("span", {
5127
5804
  style: repositoryBranch,
@@ -5157,7 +5834,7 @@ window.__ModuleLoader__.load({
5157
5834
  children: ["↑", aheadCount > 0 ? aheadCount : ""]
5158
5835
  }) : null]
5159
5836
  }) : null,
5160
- pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs("span", {
5837
+ pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
5161
5838
  style: repositoryMergedStatus,
5162
5839
  children: [
5163
5840
  /* @__PURE__ */ jsx("span", {
@@ -5170,7 +5847,11 @@ window.__ModuleLoader__.load({
5170
5847
  children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
5171
5848
  })
5172
5849
  ]
5173
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
5850
+ }), /* @__PURE__ */ jsx(CleanupControl, {
5851
+ repository,
5852
+ t,
5853
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
5854
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
5174
5855
  pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
5175
5856
  sessionId,
5176
5857
  pullNumber: pullRequest.number,
@@ -5211,7 +5892,7 @@ window.__ModuleLoader__.load({
5211
5892
  }
5212
5893
  //#endregion
5213
5894
  //#region src/client/review-comment-api.ts
5214
- function record$1(value) {
5895
+ function record(value) {
5215
5896
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5216
5897
  }
5217
5898
  async function post(path, sessionId, body) {
@@ -5226,13 +5907,13 @@ window.__ModuleLoader__.load({
5226
5907
  });
5227
5908
  const value = await response.json();
5228
5909
  if (!response.ok) {
5229
- const error = record$1(value);
5910
+ const error = record(value);
5230
5911
  throw new Error(typeof error?.message === "string" ? error.message : "Review comment request failed.");
5231
5912
  }
5232
5913
  return value;
5233
5914
  }
5234
5915
  async function addReviewComment(sessionId, comment) {
5235
- const created = record$1(record$1(await post("", sessionId, comment))?.comment);
5916
+ const created = record(record(await post("", sessionId, comment))?.comment);
5236
5917
  if (created === void 0 || typeof created.id !== "string") throw new Error("Invalid review comment response.");
5237
5918
  return created;
5238
5919
  }
@@ -6301,7 +6982,298 @@ window.__ModuleLoader__.load({
6301
6982
  background: "var(--dsw-alias-bg-base)",
6302
6983
  pointerEvents: bounds === void 0 ? "none" : "auto"
6303
6984
  },
6304
- children
6985
+ children
6986
+ });
6987
+ }
6988
+ //#endregion
6989
+ //#region src/client/ClaudeQueueDock.tsx
6990
+ /** Replaces the Host queue strip (same slot cell, lower priority) with one
6991
+ * styled like the repository status bar. Behaviour mirrors the Host strip:
6992
+ * one row inline, several rows behind a collapsible count, edit/remove/steer. */
6993
+ function ClaudeQueueDock({ useSession, t, updateQueue, notify }) {
6994
+ const inbox = useSession((session) => session.queue);
6995
+ const queue = useMemo(() => inbox.filter((row) => row.placement === "queued"), [inbox]);
6996
+ const running = useSession((session) => session.running);
6997
+ const mutable = useSession((session) => session.subagent === null);
6998
+ const [editing, setEditing] = useState();
6999
+ const [busy, setBusy] = useState();
7000
+ const [collapsed, setCollapsed] = useState(true);
7001
+ const listId = useId();
7002
+ useEffect(() => {
7003
+ if (queue.length === 0 && !collapsed) setCollapsed(true);
7004
+ if (editing !== void 0 && (!mutable || !queue.some((row) => row.id === editing.id))) setEditing(void 0);
7005
+ }, [
7006
+ collapsed,
7007
+ editing,
7008
+ mutable,
7009
+ queue
7010
+ ]);
7011
+ if (queue.length === 0) return null;
7012
+ const interacting = mutable && (editing !== void 0 || busy !== void 0);
7013
+ const expanded = !collapsed || interacting;
7014
+ const listVisible = queue.length === 1 || expanded;
7015
+ const apply = async (id, action, failure) => {
7016
+ setBusy(id);
7017
+ try {
7018
+ await updateQueue(id, action);
7019
+ return true;
7020
+ } catch {
7021
+ notify("error", failure);
7022
+ return false;
7023
+ } finally {
7024
+ setBusy((current) => current === id ? void 0 : current);
7025
+ }
7026
+ };
7027
+ const saveEdit = async () => {
7028
+ if (editing === void 0 || editing.text.trim() === "") return;
7029
+ if (await apply(editing.id, {
7030
+ kind: "edit",
7031
+ content: [{
7032
+ type: "text",
7033
+ text: editing.text
7034
+ }]
7035
+ }, t("queueEditFailed"))) setEditing(void 0);
7036
+ };
7037
+ const action = (label, icon, onClick, disabled, hint) => /* @__PURE__ */ jsx(Tooltip, {
7038
+ label: hint ?? label,
7039
+ side: "bottom",
7040
+ delayMs: 400,
7041
+ children: /* @__PURE__ */ jsx("button", {
7042
+ type: "button",
7043
+ className: panelIconButtonClass,
7044
+ "aria-label": label,
7045
+ disabled,
7046
+ onClick,
7047
+ children: icon
7048
+ })
7049
+ });
7050
+ return /* @__PURE__ */ jsxs("div", {
7051
+ style: repositoryBarFrame,
7052
+ "data-claude-queue-dock": "",
7053
+ children: [/* @__PURE__ */ jsx("style", {
7054
+ "data-dsh-claude-queue-styles": true,
7055
+ children: panelIconButtonCss
7056
+ }), /* @__PURE__ */ jsxs("div", {
7057
+ style: queueBar,
7058
+ children: [queue.length > 1 ? /* @__PURE__ */ jsxs("button", {
7059
+ type: "button",
7060
+ style: queueHeader,
7061
+ "aria-controls": listId,
7062
+ "aria-expanded": expanded,
7063
+ disabled: interacting,
7064
+ onClick: () => {
7065
+ setCollapsed((value) => !value);
7066
+ },
7067
+ children: [
7068
+ /* @__PURE__ */ jsx("span", {
7069
+ style: queueLead,
7070
+ "aria-hidden": "true",
7071
+ children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
7072
+ }),
7073
+ /* @__PURE__ */ jsx("span", {
7074
+ style: queueCount,
7075
+ children: t("queueCount", { n: queue.length })
7076
+ }),
7077
+ /* @__PURE__ */ jsx("span", {
7078
+ style: queueLead,
7079
+ "aria-hidden": "true",
7080
+ children: expanded ? /* @__PURE__ */ jsx(IconChevronDownOutline14, {}) : /* @__PURE__ */ jsx(IconChevronUpOutline14, {})
7081
+ })
7082
+ ]
7083
+ }) : null, /* @__PURE__ */ jsx("ul", {
7084
+ id: listId,
7085
+ style: queueList,
7086
+ hidden: !listVisible,
7087
+ children: listVisible ? queue.map((row, index) => /* @__PURE__ */ jsxs("li", {
7088
+ style: {
7089
+ ...queueRow,
7090
+ ...index > 0 ? queueRowDivider : {}
7091
+ },
7092
+ children: [
7093
+ queue.length === 1 ? /* @__PURE__ */ jsx("span", {
7094
+ style: queueLead,
7095
+ "aria-hidden": "true",
7096
+ children: /* @__PURE__ */ jsx(IconQueueOutline14, {})
7097
+ }) : null,
7098
+ editing?.id === row.id ? /* @__PURE__ */ jsx("input", {
7099
+ autoFocus: true,
7100
+ style: queueEditor,
7101
+ "aria-label": t("queueEdit"),
7102
+ value: editing.text,
7103
+ onChange: (event) => {
7104
+ setEditing({
7105
+ id: row.id,
7106
+ text: event.currentTarget.value
7107
+ });
7108
+ },
7109
+ onKeyDown: (event) => {
7110
+ if (event.key === "Escape") setEditing(void 0);
7111
+ else if (event.key === "Enter" && !event.nativeEvent.isComposing) {
7112
+ event.preventDefault();
7113
+ saveEdit();
7114
+ }
7115
+ }
7116
+ }) : /* @__PURE__ */ jsx("span", {
7117
+ style: queuePreview,
7118
+ children: row.preview
7119
+ }),
7120
+ mutable ? /* @__PURE__ */ jsx("span", {
7121
+ style: queueActions,
7122
+ children: editing?.id === row.id ? /* @__PURE__ */ jsxs(Fragment$1, { children: [action(t("queueSave"), /* @__PURE__ */ jsx(IconCheckOutline16, { size: 14 }), () => {
7123
+ saveEdit();
7124
+ }, busy !== void 0 || editing.text.trim() === ""), action(t("queueCancelEdit"), /* @__PURE__ */ jsx(IconCloseOutline16, { size: 14 }), () => {
7125
+ setEditing(void 0);
7126
+ }, busy !== void 0)] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
7127
+ action(t("queueEdit"), /* @__PURE__ */ jsx(IconEditOutline16, { size: 14 }), () => {
7128
+ if (row.text !== null) setEditing({
7129
+ id: row.id,
7130
+ text: row.text
7131
+ });
7132
+ }, busy !== void 0 || row.text === null, row.text === null ? t("queueEditUnsupported") : void 0),
7133
+ action(t("queueRemove"), /* @__PURE__ */ jsx(IconTrashOutline16, { size: 14 }), () => {
7134
+ apply(row.id, { kind: "remove" }, t("queueRemoveFailed"));
7135
+ }, busy !== void 0),
7136
+ action(t("queueSteer"), /* @__PURE__ */ jsx(IconSendOutline14, {}), () => {
7137
+ apply(row.id, { kind: "steer" }, t("queueSteerFailed"));
7138
+ }, busy !== void 0 || !running, running ? void 0 : t("queueSteerUnavailable"))
7139
+ ] })
7140
+ }) : null
7141
+ ]
7142
+ }, row.id)) : null
7143
+ })]
7144
+ })]
7145
+ });
7146
+ }
7147
+ //#endregion
7148
+ //#region src/client/ClaudePullRequestsPanel.tsx
7149
+ const OVERVIEW_REFRESH_MS = 3e4;
7150
+ /** Claude sessions worth listing: non-blank, with a checkout; running first. */
7151
+ function claudeSessionRows(byId) {
7152
+ return Object.values(byId).filter((row) => row !== void 0 && row.agentPreset === "claude" && row.blank !== true && typeof row.cwd === "string").sort((left, right) => Number(right.running === true) - Number(left.running === true) || (left.displayTitle ?? left.id).localeCompare(right.displayTitle ?? right.id));
7153
+ }
7154
+ function Badge({ label, tone = "neutral" }) {
7155
+ const toneStyle = tone === "success" ? repositoryItemSuccess : tone === "warning" ? repositoryItemWarning : tone === "error" ? repositoryItemError : tone === "merged" ? { color: "#a78bfa" } : {};
7156
+ return /* @__PURE__ */ jsxs("span", {
7157
+ style: {
7158
+ ...repositoryItem,
7159
+ ...toneStyle
7160
+ },
7161
+ children: [/* @__PURE__ */ jsx("span", {
7162
+ style: repositoryItemDot,
7163
+ "aria-hidden": "true"
7164
+ }), /* @__PURE__ */ jsx("span", {
7165
+ style: repositoryItemLabel,
7166
+ children: label
7167
+ })]
7168
+ });
7169
+ }
7170
+ function repositoryName(remote) {
7171
+ return remote?.split("/").at(-1);
7172
+ }
7173
+ function ClaudePullRequestsPanel({ t, closeDetails, openSession, loadStatus, sessions }) {
7174
+ const snapshot = useSyncExternalStore(sessions.subscribe, sessions.getSnapshot, sessions.getSnapshot);
7175
+ const rows = useMemo(() => claudeSessionRows(snapshot.byId), [snapshot.byId]);
7176
+ const cwdKey = useMemo(() => [...new Set(rows.map((row) => row.cwd ?? ""))].sort().join("\0"), [rows]);
7177
+ const [statuses, setStatuses] = useState({});
7178
+ useEffect(() => {
7179
+ const cwds = cwdKey.length === 0 ? [] : cwdKey.split("\0");
7180
+ if (cwds.length === 0) return;
7181
+ const controller = new AbortController();
7182
+ const refresh = () => {
7183
+ for (const cwd of cwds) loadStatus(cwd, controller.signal).then((status) => {
7184
+ if (!controller.signal.aborted) setStatuses((previous) => ({
7185
+ ...previous,
7186
+ [cwd]: status
7187
+ }));
7188
+ }, () => void 0);
7189
+ };
7190
+ refresh();
7191
+ const timer = setInterval(refresh, OVERVIEW_REFRESH_MS);
7192
+ return () => {
7193
+ controller.abort();
7194
+ clearInterval(timer);
7195
+ };
7196
+ }, [cwdKey, loadStatus]);
7197
+ return /* @__PURE__ */ jsxs("div", {
7198
+ style: tasksPanel,
7199
+ children: [
7200
+ /* @__PURE__ */ jsx("style", {
7201
+ "data-dsh-claude-overview-styles": true,
7202
+ children: panelIconButtonCss
7203
+ }),
7204
+ /* @__PURE__ */ jsxs("header", {
7205
+ style: tasksHeader,
7206
+ children: [/* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("span", {
7207
+ style: tasksHeading,
7208
+ children: t("overviewTitle")
7209
+ }), /* @__PURE__ */ jsx("span", {
7210
+ style: tasksTurnMeta,
7211
+ children: t("overviewBody")
7212
+ })] }), /* @__PURE__ */ jsx("button", {
7213
+ type: "button",
7214
+ className: panelIconButtonClass,
7215
+ "aria-label": t("diffClose"),
7216
+ onClick: closeDetails,
7217
+ children: /* @__PURE__ */ jsx(IconCloseOutline16, {})
7218
+ })]
7219
+ }),
7220
+ /* @__PURE__ */ jsx("div", {
7221
+ style: overviewBody,
7222
+ children: rows.length === 0 ? /* @__PURE__ */ jsx("p", {
7223
+ style: overviewEmpty,
7224
+ children: t("overviewEmpty")
7225
+ }) : rows.map((row) => {
7226
+ const repository = row.cwd === void 0 ? void 0 : statuses[row.cwd];
7227
+ const pullRequest = repository?.pullRequest;
7228
+ const branch = repository?.status === "ready" ? repository.detached === true ? t("repositoryDetached") : repository.branch ?? t("repositoryUnknownBranch") : repository === void 0 ? t("overviewLoading") : t("repositoryUnavailable");
7229
+ return /* @__PURE__ */ jsxs("button", {
7230
+ type: "button",
7231
+ style: overviewRow,
7232
+ onClick: () => {
7233
+ openSession(row.id);
7234
+ },
7235
+ children: [/* @__PURE__ */ jsxs("span", {
7236
+ style: overviewRowTop,
7237
+ children: [
7238
+ row.running === true ? /* @__PURE__ */ jsx("span", {
7239
+ style: overviewRunningDot,
7240
+ "aria-label": t("overviewRunning")
7241
+ }) : null,
7242
+ /* @__PURE__ */ jsx("span", {
7243
+ style: overviewTitle,
7244
+ children: row.displayTitle ?? row.id
7245
+ }),
7246
+ pullRequest === void 0 ? /* @__PURE__ */ jsx(Badge, { label: t("overviewNoPr") }) : /* @__PURE__ */ jsx(Badge, {
7247
+ label: `#${pullRequest.number} · ${t(`repositoryState_${pullRequest.state}`)}`,
7248
+ tone: pullRequest.state === "merged" ? "merged" : pullRequest.state === "open" ? "success" : "neutral"
7249
+ })
7250
+ ]
7251
+ }), /* @__PURE__ */ jsxs("span", {
7252
+ style: overviewMeta,
7253
+ children: [
7254
+ repositoryName(repository?.remote) === void 0 ? null : /* @__PURE__ */ jsx("span", { children: repositoryName(repository?.remote) }),
7255
+ /* @__PURE__ */ jsx("span", {
7256
+ style: overviewBranch,
7257
+ children: branch
7258
+ }),
7259
+ pullRequest?.state === "open" && pullRequest.checks !== "none" ? /* @__PURE__ */ jsx(Badge, {
7260
+ label: t(`repositoryChecks_${pullRequest.checks}`),
7261
+ tone: pullRequest.checks === "passing" ? "success" : pullRequest.checks === "failing" ? "error" : "warning"
7262
+ }) : null,
7263
+ pullRequest?.state === "open" && pullRequest.review !== "none" ? /* @__PURE__ */ jsx(Badge, {
7264
+ label: t(`repositoryReview_${pullRequest.review}`),
7265
+ tone: pullRequest.review === "approved" ? "success" : pullRequest.review === "changes-requested" ? "error" : "neutral"
7266
+ }) : null,
7267
+ autoFixEnabled(row.id) ? /* @__PURE__ */ jsx(Badge, {
7268
+ label: t("overviewAutoFix"),
7269
+ tone: "success"
7270
+ }) : null
7271
+ ]
7272
+ })]
7273
+ }, row.id);
7274
+ })
7275
+ })
7276
+ ]
6305
7277
  });
6306
7278
  }
6307
7279
  //#endregion
@@ -6350,110 +7322,6 @@ window.__ModuleLoader__.load({
6350
7322
  for (const portal of root.querySelectorAll(`[${PORTAL_ATTRIBUTE}]`)) portal.remove();
6351
7323
  }
6352
7324
  //#endregion
6353
- //#region src/client/repository-setup-api.ts
6354
- const HOST_STAGES = /* @__PURE__ */ new Set([
6355
- "inspecting",
6356
- "fetching",
6357
- "creating-worktree",
6358
- "saving-worktree",
6359
- "switching-branch"
6360
- ]);
6361
- function record(value) {
6362
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6363
- }
6364
- function setupResult(value) {
6365
- const item = record(value);
6366
- 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;
6367
- return item;
6368
- }
6369
- function parseRepositorySetupEvent(line, onProgress) {
6370
- const event = record(JSON.parse(line));
6371
- if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
6372
- onProgress(event.stage);
6373
- return;
6374
- }
6375
- if (event?.type === "complete") {
6376
- const result = setupResult(event.result);
6377
- if (result !== void 0) return result;
6378
- }
6379
- if (event?.type === "error" && typeof event.message === "string") throw new Error(event.message);
6380
- throw new Error("Invalid repository setup progress response.");
6381
- }
6382
- async function response(pending) {
6383
- const result = await pending;
6384
- const body = await result.json();
6385
- if (!result.ok) {
6386
- const error = body;
6387
- throw new Error(error.message ?? error.error ?? "Repository setup failed.");
6388
- }
6389
- return body;
6390
- }
6391
- function loadRepositoryBranches(cwd, signal) {
6392
- return response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches?cwd=${encodeURIComponent(cwd)}`, {
6393
- method: "GET",
6394
- credentials: "same-origin",
6395
- headers: { accept: "application/json" },
6396
- ...signal === void 0 ? {} : { signal }
6397
- }));
6398
- }
6399
- async function prepareRepository(cwd, branch, worktree, branchName, onProgress = () => {}) {
6400
- const result = await fetch(CLAUDE_REPOSITORY_SETUP_PATH, {
6401
- method: "POST",
6402
- credentials: "same-origin",
6403
- headers: {
6404
- accept: "application/x-ndjson",
6405
- "content-type": "application/json"
6406
- },
6407
- body: JSON.stringify({
6408
- cwd,
6409
- branch,
6410
- worktree,
6411
- ...branchName === void 0 ? {} : { branchName }
6412
- })
6413
- });
6414
- if (!result.ok) {
6415
- const body = await result.json();
6416
- throw new Error(body.message ?? body.error ?? "Repository setup failed.");
6417
- }
6418
- if (result.body === null) throw new Error("Repository setup progress stream is unavailable.");
6419
- const reader = result.body.getReader();
6420
- const decoder = new TextDecoder();
6421
- let buffer = "";
6422
- let completed;
6423
- while (true) {
6424
- const chunk = await reader.read();
6425
- buffer += decoder.decode(chunk.value, { stream: !chunk.done });
6426
- const lines = buffer.split("\n");
6427
- buffer = lines.pop() ?? "";
6428
- for (const line of lines) {
6429
- if (line.trim().length === 0) continue;
6430
- const value = parseRepositorySetupEvent(line, onProgress);
6431
- if (value !== void 0) completed = value;
6432
- }
6433
- if (chunk.done) break;
6434
- }
6435
- if (buffer.trim().length > 0) {
6436
- const value = parseRepositorySetupEvent(buffer, onProgress);
6437
- if (value !== void 0) completed = value;
6438
- }
6439
- if (completed === void 0) throw new Error("Repository setup progress ended before completion.");
6440
- return completed;
6441
- }
6442
- async function bindRepositoryLease(leaseId, sessionId) {
6443
- await response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/bind`, {
6444
- method: "POST",
6445
- credentials: "same-origin",
6446
- headers: {
6447
- accept: "application/json",
6448
- "content-type": "application/json"
6449
- },
6450
- body: JSON.stringify({
6451
- leaseId,
6452
- sessionId
6453
- })
6454
- }));
6455
- }
6456
- //#endregion
6457
7325
  //#region src/client/ClaudeHeroRepositoryControls.tsx
6458
7326
  function shouldInterceptKey(event) {
6459
7327
  if (event.key !== "Enter" || event.shiftKey || event.repeat || event.isComposing) return false;
@@ -6585,6 +7453,23 @@ window.__ModuleLoader__.load({
6585
7453
  })]
6586
7454
  });
6587
7455
  }
7456
+ function TicketIcon() {
7457
+ return /* @__PURE__ */ jsxs("svg", {
7458
+ width: "16",
7459
+ height: "16",
7460
+ viewBox: "0 0 16 16",
7461
+ fill: "none",
7462
+ stroke: "currentColor",
7463
+ strokeWidth: "1.5",
7464
+ strokeLinecap: "round",
7465
+ strokeLinejoin: "round",
7466
+ "aria-hidden": "true",
7467
+ children: [/* @__PURE__ */ jsx("path", { d: "M2.5 5.5a1 1 0 0 1 1-1h9a1 1 0 0 1 1 1v1.2a1.3 1.3 0 0 0 0 2.6v1.2a1 1 0 0 1-1 1h-9a1 1 0 0 1-1-1V9.3a1.3 1.3 0 0 0 0-2.6V5.5Z" }), /* @__PURE__ */ jsx("path", {
7468
+ d: "M6.5 4.5v7",
7469
+ strokeDasharray: "1.5 1.5"
7470
+ })]
7471
+ });
7472
+ }
6588
7473
  function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, onMenuOpenChange, onSelect, onWorktreeChange }) {
6589
7474
  const pickerRef = useRef(null);
6590
7475
  const searchRef = useRef(null);
@@ -6771,6 +7656,14 @@ window.__ModuleLoader__.load({
6771
7656
  const [progressStage, setProgressStage] = useState();
6772
7657
  const [progressError, setProgressError] = useState();
6773
7658
  const [error, setError] = useState();
7659
+ const [jiraConnected, setJiraConnected] = useState(false);
7660
+ const [ticket, setTicket] = useState();
7661
+ const [ticketMenuOpen, setTicketMenuOpen] = useState(false);
7662
+ const [ticketQuery, setTicketQuery] = useState("");
7663
+ const [ticketResults, setTicketResults] = useState();
7664
+ const [ticketError, setTicketError] = useState();
7665
+ const ticketPickerRef = useRef(null);
7666
+ const ticketSearchRef = useRef(null);
6774
7667
  const pendingRef = useRef(false);
6775
7668
  const path = workspacePath ?? cwd;
6776
7669
  useEffect(() => {
@@ -6813,6 +7706,11 @@ window.__ModuleLoader__.load({
6813
7706
  setProgressStage(void 0);
6814
7707
  setProgressError(void 0);
6815
7708
  setError(void 0);
7709
+ setTicket(void 0);
7710
+ setTicketMenuOpen(false);
7711
+ setTicketQuery("");
7712
+ setTicketResults(void 0);
7713
+ setTicketError(void 0);
6816
7714
  if (portal === void 0 || path === void 0) return;
6817
7715
  const controller = new AbortController();
6818
7716
  loadRepositoryBranches(path, controller.signal).then((value) => {
@@ -6830,11 +7728,11 @@ window.__ModuleLoader__.load({
6830
7728
  sessionId
6831
7729
  ]);
6832
7730
  const availableBranches = branches === void 0 ? [] : repositoryBranchOptions(branches);
6833
- const changed = branches !== void 0 && selected.length > 0 && (worktree || selected !== branches.current);
7731
+ const changed = branches !== void 0 && selected.length > 0 && (worktree || ticket !== void 0 || selected !== branches.current);
6834
7732
  useEffect(() => {
6835
7733
  if (!changed || portal === void 0 || busy) return;
6836
7734
  const submit = (event) => {
6837
- if (pendingRef.current || input.draft.trim().length === 0) return;
7735
+ if (pendingRef.current || input.draft.trim().length === 0 && ticket === void 0) return;
6838
7736
  if (!(event instanceof KeyboardEvent ? shouldInterceptKey(event) : shouldInterceptClick(event))) return;
6839
7737
  event.preventDefault();
6840
7738
  event.stopPropagation();
@@ -6845,7 +7743,7 @@ window.__ModuleLoader__.load({
6845
7743
  setError(void 0);
6846
7744
  prepare(branches.root, selected, worktree, (stage) => {
6847
7745
  if (worktree) setProgressStage(stage);
6848
- }).catch((reason) => {
7746
+ }, ticket).catch((reason) => {
6849
7747
  const message = reason instanceof Error ? reason.message : String(reason);
6850
7748
  if (worktree) setProgressError(message);
6851
7749
  else setError(message);
@@ -6868,38 +7766,187 @@ window.__ModuleLoader__.load({
6868
7766
  portal,
6869
7767
  prepare,
6870
7768
  selected,
7769
+ ticket,
6871
7770
  worktree
6872
7771
  ]);
7772
+ useEffect(() => {
7773
+ const controller = new AbortController();
7774
+ loadJiraStatus(controller.signal).then((status) => {
7775
+ setJiraConnected(status.connected);
7776
+ }, () => {
7777
+ if (!controller.signal.aborted) setJiraConnected(false);
7778
+ });
7779
+ return () => {
7780
+ controller.abort();
7781
+ };
7782
+ }, [sessionId]);
7783
+ useEffect(() => {
7784
+ if (!ticketMenuOpen) return;
7785
+ const controller = new AbortController();
7786
+ const timer = setTimeout(() => {
7787
+ setTicketError(void 0);
7788
+ searchJiraTickets(ticketQuery, controller.signal).then(setTicketResults, (reason) => {
7789
+ if (controller.signal.aborted) return;
7790
+ setTicketResults([]);
7791
+ setTicketError(reason instanceof JiraClientError && reason.code === "not-connected" ? t("heroTicketNotConnected") : reason instanceof Error ? reason.message : t("heroTicketFailed"));
7792
+ });
7793
+ }, ticketQuery.length === 0 ? 0 : 300);
7794
+ return () => {
7795
+ clearTimeout(timer);
7796
+ controller.abort();
7797
+ };
7798
+ }, [
7799
+ t,
7800
+ ticketMenuOpen,
7801
+ ticketQuery
7802
+ ]);
7803
+ useEffect(() => {
7804
+ if (!ticketMenuOpen) {
7805
+ setTicketQuery("");
7806
+ setTicketResults(void 0);
7807
+ return;
7808
+ }
7809
+ ticketSearchRef.current?.focus();
7810
+ const closeOnOutsidePointer = (event) => {
7811
+ if (event.target instanceof Node && ticketPickerRef.current?.contains(event.target) !== true) setTicketMenuOpen(false);
7812
+ };
7813
+ document.addEventListener("pointerdown", closeOnOutsidePointer);
7814
+ return () => {
7815
+ document.removeEventListener("pointerdown", closeOnOutsidePointer);
7816
+ };
7817
+ }, [ticketMenuOpen]);
7818
+ const chooseTicket = (next) => {
7819
+ setTicket(next);
7820
+ setError(void 0);
7821
+ setTicketMenuOpen(false);
7822
+ };
6873
7823
  if (portal === void 0 || path === void 0) return null;
6874
7824
  return createPortal(/* @__PURE__ */ jsxs("span", {
6875
7825
  style: heroRepositoryControls,
6876
7826
  children: [branches === void 0 ? /* @__PURE__ */ jsx("span", {
6877
7827
  style: heroRepositoryStatus,
6878
7828
  children: error ?? t("repositoryBranchesLoading")
6879
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ClaudeHeroRepositoryCapsule, {
6880
- branches: availableBranches,
6881
- selected,
6882
- worktree,
6883
- busy,
6884
- menuOpen,
6885
- worktreeLabel: t("repositoryWorktree"),
6886
- searchPlaceholder: t("repositoryBranchSearch"),
6887
- emptySearchLabel: t("repositoryBranchSearchEmpty"),
6888
- onMenuOpenChange: setMenuOpen,
6889
- onSelect: (branch) => {
6890
- setSelected(branch);
6891
- setMenuOpen(false);
6892
- setError(void 0);
6893
- },
6894
- onWorktreeChange: (checked) => {
6895
- setWorktree(checked);
6896
- setError(void 0);
6897
- }
6898
- }), error === void 0 ? null : /* @__PURE__ */ jsx("span", {
6899
- role: "alert",
6900
- style: heroRepositoryError,
6901
- children: error
6902
- })] }), progressStage === void 0 || !busy && progressError === void 0 ? null : /* @__PURE__ */ jsx(WorktreeProgressCard, {
7829
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
7830
+ /* @__PURE__ */ jsx(ClaudeHeroRepositoryCapsule, {
7831
+ branches: availableBranches,
7832
+ selected,
7833
+ worktree,
7834
+ busy,
7835
+ menuOpen,
7836
+ worktreeLabel: t("repositoryWorktree"),
7837
+ searchPlaceholder: t("repositoryBranchSearch"),
7838
+ emptySearchLabel: t("repositoryBranchSearchEmpty"),
7839
+ onMenuOpenChange: setMenuOpen,
7840
+ onSelect: (branch) => {
7841
+ setSelected(branch);
7842
+ setMenuOpen(false);
7843
+ setError(void 0);
7844
+ },
7845
+ onWorktreeChange: (checked) => {
7846
+ setWorktree(checked);
7847
+ setError(void 0);
7848
+ }
7849
+ }),
7850
+ !jiraConnected ? null : /* @__PURE__ */ jsx("span", {
7851
+ style: heroRepositoryCapsule,
7852
+ children: /* @__PURE__ */ jsxs("span", {
7853
+ ref: ticketPickerRef,
7854
+ style: heroBranchPicker,
7855
+ children: [/* @__PURE__ */ jsxs("button", {
7856
+ type: "button",
7857
+ style: heroBranchTrigger,
7858
+ "aria-expanded": ticketMenuOpen,
7859
+ "aria-label": t("heroTicketMenu"),
7860
+ title: t("heroTicketMenu"),
7861
+ disabled: busy,
7862
+ onClick: () => {
7863
+ setTicketMenuOpen(!ticketMenuOpen);
7864
+ },
7865
+ children: [
7866
+ /* @__PURE__ */ jsx(TicketIcon, {}),
7867
+ /* @__PURE__ */ jsx("span", {
7868
+ style: heroBranchName,
7869
+ children: ticket === void 0 ? t("heroTicket") : ticket.key
7870
+ }),
7871
+ /* @__PURE__ */ jsx(IconChevronDownOutline14, {})
7872
+ ]
7873
+ }), ticketMenuOpen ? /* @__PURE__ */ jsxs("span", {
7874
+ role: "menu",
7875
+ style: heroBranchMenu,
7876
+ onKeyDown: (event) => {
7877
+ if (event.key === "Escape") {
7878
+ event.preventDefault();
7879
+ setTicketMenuOpen(false);
7880
+ }
7881
+ },
7882
+ children: [/* @__PURE__ */ jsxs("label", {
7883
+ style: heroBranchSearch,
7884
+ children: [/* @__PURE__ */ jsx(IconSearchOutline16, {}), /* @__PURE__ */ jsx("input", {
7885
+ ref: ticketSearchRef,
7886
+ type: "search",
7887
+ value: ticketQuery,
7888
+ placeholder: t("heroTicketSearch"),
7889
+ "aria-label": t("heroTicketSearch"),
7890
+ style: heroBranchSearchInput,
7891
+ onChange: (event) => {
7892
+ setTicketQuery(event.currentTarget.value);
7893
+ }
7894
+ })]
7895
+ }), /* @__PURE__ */ jsx("span", {
7896
+ style: heroBranchList,
7897
+ children: ticketError !== void 0 ? /* @__PURE__ */ jsx("span", {
7898
+ style: heroBranchEmpty,
7899
+ children: ticketError
7900
+ }) : ticketResults === void 0 ? /* @__PURE__ */ jsx("span", {
7901
+ style: heroBranchEmpty,
7902
+ children: t("heroTicketLoading")
7903
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [ticket === void 0 ? null : /* @__PURE__ */ jsx("button", {
7904
+ type: "button",
7905
+ role: "menuitem",
7906
+ style: heroBranchItem,
7907
+ onClick: () => {
7908
+ chooseTicket(void 0);
7909
+ },
7910
+ children: /* @__PURE__ */ jsx("span", {
7911
+ style: heroBranchItemName,
7912
+ children: t("heroTicketNone")
7913
+ })
7914
+ }), ticketResults.length === 0 ? /* @__PURE__ */ jsx("span", {
7915
+ style: heroBranchEmpty,
7916
+ children: t("heroTicketEmpty")
7917
+ }) : ticketResults.map((item) => /* @__PURE__ */ jsxs("button", {
7918
+ type: "button",
7919
+ role: "menuitem",
7920
+ "aria-current": item.key === ticket?.key ? "true" : void 0,
7921
+ style: heroBranchItem,
7922
+ onClick: () => {
7923
+ chooseTicket(item);
7924
+ },
7925
+ children: [
7926
+ /* @__PURE__ */ jsx("span", {
7927
+ style: heroTicketKey,
7928
+ children: item.key
7929
+ }),
7930
+ /* @__PURE__ */ jsx("span", {
7931
+ style: heroBranchItemName,
7932
+ children: item.summary
7933
+ }),
7934
+ item.status === void 0 ? null : /* @__PURE__ */ jsx("span", {
7935
+ style: heroTicketStatus,
7936
+ children: item.status
7937
+ })
7938
+ ]
7939
+ }, item.key))] })
7940
+ })]
7941
+ }) : null]
7942
+ })
7943
+ }),
7944
+ error === void 0 ? null : /* @__PURE__ */ jsx("span", {
7945
+ role: "alert",
7946
+ style: heroRepositoryError,
7947
+ children: error
7948
+ })
7949
+ ] }), progressStage === void 0 || !busy && progressError === void 0 ? null : /* @__PURE__ */ jsx(WorktreeProgressCard, {
6903
7950
  stage: progressStage,
6904
7951
  ...progressError === void 0 ? {} : { error: progressError },
6905
7952
  t,
@@ -7373,6 +8420,50 @@ window.__ModuleLoader__.load({
7373
8420
  prCommentsSend: "让 Claude 处理 GitHub 上的 PR 评论",
7374
8421
  autoFixLabel: "Auto fix",
7375
8422
  autoFixTitle: "自动监听这个 PR 的 review 评论与 CI 失败,交给 Claude 修复并推送,直到全部通过;取消勾选即停止。",
8423
+ queueCount: "{n} 条排队消息",
8424
+ queueEdit: "编辑",
8425
+ queueEditUnsupported: "包含非文本内容,无法编辑",
8426
+ queueSave: "保存",
8427
+ queueCancelEdit: "取消编辑",
8428
+ queueRemove: "删除",
8429
+ queueSteer: "插入当前回合",
8430
+ queueSteerUnavailable: "没有正在运行的回合",
8431
+ queueEditFailed: "排队消息编辑失败。",
8432
+ queueRemoveFailed: "排队消息删除失败。",
8433
+ queueSteerFailed: "排队消息插入失败。",
8434
+ heroTicket: "Ticket",
8435
+ heroTicketMenu: "选择 Jira ticket",
8436
+ heroTicketSearch: "搜索 ticket 或输入 key…",
8437
+ heroTicketLoading: "搜索中…",
8438
+ heroTicketEmpty: "没有匹配的 ticket",
8439
+ heroTicketNone: "不关联 ticket",
8440
+ heroTicketNotConnected: "请先在 Settings 中连接 Jira",
8441
+ heroTicketFailed: "Ticket 搜索失败",
8442
+ jiraTitle: "Jira",
8443
+ jiraBody: "连接 Jira Cloud 后,新会话可以直接从 ticket 开工:分支按 ticket key 命名,输入框预填 ticket 简报,worktree 创建后自动把 ticket 分配给你。",
8444
+ jiraSite: "站点地址",
8445
+ jiraSitePlaceholder: "https://your-team.atlassian.net",
8446
+ jiraEmail: "账号邮箱",
8447
+ jiraToken: "API Token",
8448
+ jiraTokenHelp: "在 Atlassian 创建 API Token",
8449
+ jiraConnect: "连接",
8450
+ jiraConnecting: "正在验证…",
8451
+ jiraDisconnect: "断开连接",
8452
+ jiraConnected: "已连接",
8453
+ jiraLoading: "正在读取 Jira 状态…",
8454
+ jiraError: "Jira 操作失败",
8455
+ cleanupButton: "Clean up",
8456
+ cleanupTitle: "清理已合并分支",
8457
+ cleanupDescription: "删除 worktree 与本地分支并移除对应 workspace;普通 checkout 则切回 base 分支并删除已合并分支。",
8458
+ cleanupCompleted: "已清理 {branch}",
8459
+ overviewOpen: "查看所有 Claude 会话的 Pull Request",
8460
+ overviewTitle: "Pull requests",
8461
+ overviewBody: "所有 Claude 会话的分支与 PR 状态",
8462
+ overviewEmpty: "没有正在进行的 Claude 会话",
8463
+ overviewNoPr: "无 PR",
8464
+ overviewRunning: "运行中",
8465
+ overviewAutoFix: "Auto fix 已开启",
8466
+ overviewLoading: "读取中…",
7376
8467
  reviewCommentAdd: "添加行评论",
7377
8468
  reviewCommentPlaceholder: "请求更改",
7378
8469
  reviewCommentSubmit: "注释",
@@ -7600,6 +8691,50 @@ window.__ModuleLoader__.load({
7600
8691
  prCommentsSend: "Have Claude address the GitHub pull request comments",
7601
8692
  autoFixLabel: "Auto fix",
7602
8693
  autoFixTitle: "Watch this pull request for review comments and failing checks, hand them to Claude to fix and push until everything passes; uncheck to stop.",
8694
+ queueCount: "{n} queued messages",
8695
+ queueEdit: "Edit",
8696
+ queueEditUnsupported: "Contains non-text content and cannot be edited",
8697
+ queueSave: "Save",
8698
+ queueCancelEdit: "Cancel edit",
8699
+ queueRemove: "Remove",
8700
+ queueSteer: "Steer into the running turn",
8701
+ queueSteerUnavailable: "No running turn to steer into",
8702
+ queueEditFailed: "The queued message could not be edited.",
8703
+ queueRemoveFailed: "The queued message could not be removed.",
8704
+ queueSteerFailed: "The queued message could not be steered.",
8705
+ heroTicket: "Ticket",
8706
+ heroTicketMenu: "Pick a Jira ticket",
8707
+ heroTicketSearch: "Search tickets or enter a key…",
8708
+ heroTicketLoading: "Searching…",
8709
+ heroTicketEmpty: "No matching tickets",
8710
+ heroTicketNone: "No ticket",
8711
+ heroTicketNotConnected: "Connect Jira in Settings first",
8712
+ heroTicketFailed: "Ticket search failed",
8713
+ jiraTitle: "Jira",
8714
+ jiraBody: "Connect Jira Cloud to start new sessions from a ticket: the branch is named after the ticket key, the composer is seeded with the ticket brief, and the ticket is assigned to you once the worktree exists.",
8715
+ jiraSite: "Site URL",
8716
+ jiraSitePlaceholder: "https://your-team.atlassian.net",
8717
+ jiraEmail: "Account email",
8718
+ jiraToken: "API token",
8719
+ jiraTokenHelp: "Create an API token at Atlassian",
8720
+ jiraConnect: "Connect",
8721
+ jiraConnecting: "Verifying…",
8722
+ jiraDisconnect: "Disconnect",
8723
+ jiraConnected: "Connected",
8724
+ jiraLoading: "Loading Jira status…",
8725
+ jiraError: "Jira request failed",
8726
+ cleanupButton: "Clean up",
8727
+ cleanupTitle: "Clean up merged branch",
8728
+ cleanupDescription: "Remove the worktree and local branch and delete the workspace; a plain checkout switches back to the base branch and deletes the merged branch.",
8729
+ cleanupCompleted: "Cleaned up {branch}",
8730
+ overviewOpen: "Show pull requests across Claude sessions",
8731
+ overviewTitle: "Pull requests",
8732
+ overviewBody: "Branch and pull request state across Claude sessions",
8733
+ overviewEmpty: "No active Claude sessions",
8734
+ overviewNoPr: "No PR",
8735
+ overviewRunning: "Running",
8736
+ overviewAutoFix: "Auto fix on",
8737
+ overviewLoading: "Loading…",
7603
8738
  reviewCommentAdd: "Add line comment",
7604
8739
  reviewCommentPlaceholder: "Request changes",
7605
8740
  reviewCommentSubmit: "Comment",
@@ -7730,6 +8865,31 @@ window.__ModuleLoader__.load({
7730
8865
  layout?.openDetails();
7731
8866
  disposeExpandedDetailsResize = enableExpandedDetailsResize();
7732
8867
  };
8868
+ const openOverviewPanel = (sessionId) => {
8869
+ if (sessions === void 0) return;
8870
+ closePluginDetails();
8871
+ try {
8872
+ disposePluginDetails = ctx.slots.register({
8873
+ name: "details",
8874
+ priority: -10,
8875
+ locale: namespace,
8876
+ inject: () => ({
8877
+ t,
8878
+ closeDetails: closePluginDetails,
8879
+ openSession: (id) => {
8880
+ sessions.open(id);
8881
+ },
8882
+ loadStatus: loadRepositoryStatusFor,
8883
+ sessions: sessions.list
8884
+ })
8885
+ }, ClaudePullRequestsPanel);
8886
+ } catch {
8887
+ return;
8888
+ }
8889
+ detailsSessionId = sessionId;
8890
+ layout?.openDetails();
8891
+ disposeExpandedDetailsResize = enableExpandedDetailsResize();
8892
+ };
7733
8893
  const openDiffPanel = (sessionId) => {
7734
8894
  closePluginDetails();
7735
8895
  detailsSessionId = sessionId;
@@ -7845,10 +9005,33 @@ window.__ModuleLoader__.load({
7845
9005
  return {
7846
9006
  t,
7847
9007
  openDiff: () => openDiffPanel(sessionId),
7848
- ...submitPrompt === void 0 ? {} : { submitPrompt }
9008
+ ...submitPrompt === void 0 ? {} : { submitPrompt },
9009
+ ...sessions === void 0 ? {} : { openOverview: () => openOverviewPanel(sessionId) },
9010
+ ...workspaces === void 0 ? {} : { deleteWorkspace: async () => {
9011
+ const workspace = workspaces.list.getSnapshot().items.find((item) => item.sessionIds.includes(sessionId));
9012
+ if (workspace !== void 0) await workspaces.delete(workspace.workspaceId);
9013
+ } }
7849
9014
  };
7850
9015
  }
7851
9016
  }, ClaudeRepositoryStatus));
9017
+ if (sessions !== void 0 && conversation !== void 0) ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
9018
+ name: "conversation.input.dock",
9019
+ id: "queue",
9020
+ order: 20,
9021
+ priority: -10,
9022
+ locale: namespace,
9023
+ inject: (sessionId) => {
9024
+ const scope = sessions.scope(sessionId);
9025
+ const target = (scope === void 0 ? void 0 : scope.get("conversation")) ?? conversation;
9026
+ return {
9027
+ t,
9028
+ updateQueue: (itemId, action) => target.updateQueue(itemId, action),
9029
+ notify: (level, text) => {
9030
+ if (scope !== void 0) conversation.input.for(scope).notify(level, text);
9031
+ }
9032
+ };
9033
+ }
9034
+ }, ClaudeQueueDock));
7852
9035
  if (sessions !== void 0 && workspaces !== void 0 && conversation !== void 0 && connection !== void 0) ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
7853
9036
  name: "conversation.input.dock",
7854
9037
  id: "claude-hero-repository-controls",
@@ -7856,14 +9039,19 @@ window.__ModuleLoader__.load({
7856
9039
  locale: namespace,
7857
9040
  inject: (sourceSessionId) => ({
7858
9041
  t,
7859
- prepare: async (cwd, branch, useWorktree, onProgress) => {
9042
+ prepare: async (cwd, branch, useWorktree, onProgress, ticket) => {
7860
9043
  const sourceScope = sessions.scope(sourceSessionId);
7861
9044
  if (sourceScope === void 0) throw new Error(t("repositorySessionUnavailable"));
7862
9045
  const sourceInput = conversation.input.for(sourceScope);
7863
- const draft = sourceInput.state.getSnapshot().draft;
9046
+ const rawDraft = sourceInput.state.getSnapshot().draft;
9047
+ const draft = ticket === void 0 ? rawDraft : rawDraft.trim() === "" ? ticketPrompt(ticket) : `${rawDraft.trimEnd()}\n\n${ticketContext(ticket)}`;
7864
9048
  const imageIds = sourceInput.state.getSnapshot().imageIds;
7865
- const prepared = await prepareRepository(cwd, branch, useWorktree, void 0, onProgress);
9049
+ const prepared = await prepareRepository(cwd, branch, useWorktree, ticket?.key, onProgress);
9050
+ if (ticket !== void 0) assignJiraTicket(ticket.key).catch((reason) => {
9051
+ console.warn(`dsh-claude: could not assign ${ticket.key}: ${reason instanceof Error ? reason.message : String(reason)}`);
9052
+ });
7866
9053
  if (prepared.mode === "checkout") {
9054
+ if (draft !== rawDraft) sourceInput.setDraft(draft);
7867
9055
  sourceInput.submit();
7868
9056
  return;
7869
9057
  }