@norman-else/dsh-claude 0.1.24 → 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
@@ -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 = {
@@ -1497,7 +1501,7 @@ window.__ModuleLoader__.load({
1497
1501
  gap: 2
1498
1502
  };
1499
1503
  const repositoryBarMerged = {
1500
- 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)",
1501
1505
  background: "color-mix(in srgb, #a78bfa 5%, var(--dsw-alias-bg-layer-1))"
1502
1506
  };
1503
1507
  const repositoryPrIcon = {
@@ -1905,6 +1909,105 @@ window.__ModuleLoader__.load({
1905
1909
  fontWeight: 650,
1906
1910
  cursor: "pointer"
1907
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
+ };
1908
2011
  const repositoryMergeTrigger = {
1909
2012
  flex: "none",
1910
2013
  display: "inline-flex",
@@ -2915,7 +3018,7 @@ window.__ModuleLoader__.load({
2915
3018
  const MAX_REVIEW_COMMENTS = 50;
2916
3019
  const MAX_REVIEW_COMMENT_CHARS = 2e3;
2917
3020
  const MAX_TRANSCRIPT_CHARS = 64e3;
2918
- function record$5(value) {
3021
+ function record$6(value) {
2919
3022
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
2920
3023
  }
2921
3024
  function nonNegativeInteger(value) {
@@ -2925,18 +3028,18 @@ window.__ModuleLoader__.load({
2925
3028
  return value === void 0 || typeof value === "string" && value.length <= MAX_REPOSITORY_TEXT_CHARS;
2926
3029
  }
2927
3030
  function validateRepository(value) {
2928
- const repository = record$5(value);
3031
+ const repository = record$6(value);
2929
3032
  if (repository === void 0 || ![
2930
3033
  "ready",
2931
3034
  "not-repository",
2932
3035
  "unavailable"
2933
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;
2934
3037
  if (repository.diff !== void 0) {
2935
- const diff = record$5(repository.diff);
3038
+ const diff = record$6(repository.diff);
2936
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;
2937
3040
  }
2938
3041
  if (repository.pullRequest === void 0) return true;
2939
- const pullRequest = record$5(repository.pullRequest);
3042
+ const pullRequest = record$6(repository.pullRequest);
2940
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 || ![
2941
3044
  "open",
2942
3045
  "closed",
@@ -2961,24 +3064,24 @@ window.__ModuleLoader__.load({
2961
3064
  }
2962
3065
  /** Validate the public route envelope before publishing it to UI components. */
2963
3066
  function parseClaudeClientProjection(value) {
2964
- const input = record$5(value);
3067
+ const input = record$6(value);
2965
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");
2966
3069
  for (const item of input.commands) {
2967
- const command = record$5(item);
3070
+ const command = record$6(item);
2968
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");
2969
3072
  }
2970
3073
  for (const item of input.activities) {
2971
- const activity = record$5(item);
3074
+ const activity = record$6(item);
2972
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");
2973
3076
  }
2974
- if (input.contextUsage !== void 0 && record$5(input.contextUsage) === void 0) throw new Error("invalid Claude context projection");
2975
- 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);
2976
3079
  if (tasks !== void 0 && !Array.isArray(tasks.tasks)) throw new Error("invalid Claude tasks projection");
2977
3080
  if (input.repository !== void 0 && !validateRepository(input.repository)) throw new Error("invalid Claude repository projection");
2978
3081
  if (input.reviewComments !== void 0) {
2979
3082
  if (!Array.isArray(input.reviewComments) || input.reviewComments.length > MAX_REVIEW_COMMENTS) throw new Error("invalid Claude review comment projection");
2980
3083
  for (const item of input.reviewComments) {
2981
- const comment = record$5(item);
3084
+ const comment = record$6(item);
2982
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");
2983
3086
  }
2984
3087
  }
@@ -3176,7 +3279,7 @@ window.__ModuleLoader__.load({
3176
3279
  } catch {
3177
3280
  return;
3178
3281
  }
3179
- const event = record$5(value);
3282
+ const event = record$6(value);
3180
3283
  if (event === void 0 || typeof event.type !== "string") return;
3181
3284
  try {
3182
3285
  switch (event.type) {
@@ -3435,7 +3538,7 @@ window.__ModuleLoader__.load({
3435
3538
  return value;
3436
3539
  }
3437
3540
  }
3438
- function record$4(value) {
3541
+ function record$5(value) {
3439
3542
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
3440
3543
  }
3441
3544
  function text(value) {
@@ -3449,7 +3552,7 @@ window.__ModuleLoader__.load({
3449
3552
  if (typeof value === "string") return value;
3450
3553
  if (typeof value === "number" || typeof value === "boolean") return String(value);
3451
3554
  if (Array.isArray(value)) return value.map(displayValue).join(", ");
3452
- 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");
3453
3556
  }
3454
3557
  function Section({ title, children }) {
3455
3558
  return /* @__PURE__ */ jsxs("section", {
@@ -3519,15 +3622,15 @@ window.__ModuleLoader__.load({
3519
3622
  function ToolPresentation({ tool, t }) {
3520
3623
  const inputValue = parsedValue(tool.input);
3521
3624
  const outputValue = parsedValue(tool.output);
3522
- const input = record$4(inputValue);
3523
- const output = record$4(outputValue);
3625
+ const input = record$5(inputValue);
3626
+ const output = record$5(outputValue);
3524
3627
  const outputTitle = tool.isError === true ? t("toolError") : t("toolOutput");
3525
3628
  if (tool.diffs !== void 0) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(DiffBlock, { diffs: [...tool.diffs] }), /* @__PURE__ */ jsx(TextDetail, {
3526
3629
  title: outputTitle,
3527
3630
  value: typeof outputValue === "string" ? outputValue : void 0
3528
3631
  })] });
3529
3632
  if (tool.toolName === "Read") {
3530
- const file = record$4(output?.file);
3633
+ const file = record$5(output?.file);
3531
3634
  const path = text(file?.filePath) ?? text(input?.file_path);
3532
3635
  const content = text(file?.content) ?? (typeof outputValue === "string" ? outputValue : void 0);
3533
3636
  const offset = numberValue(input?.offset) ?? 1;
@@ -3716,6 +3819,89 @@ window.__ModuleLoader__.load({
3716
3819
  });
3717
3820
  }
3718
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
3719
3905
  //#region src/client/ClaudeCodeSettings.tsx
3720
3906
  function isPluginUpdateStatus(value) {
3721
3907
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
@@ -3910,6 +4096,49 @@ window.__ModuleLoader__.load({
3910
4096
  const [globalSettings, setGlobalSettings] = useState();
3911
4097
  const [globalSettingsError, setGlobalSettingsError] = useState();
3912
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
+ };
3913
4142
  const refresh = useCallback(async () => {
3914
4143
  setBusy(true);
3915
4144
  setError(void 0);
@@ -4119,6 +4348,122 @@ window.__ModuleLoader__.load({
4119
4348
  })
4120
4349
  ]
4121
4350
  }),
4351
+ /* @__PURE__ */ jsxs("section", {
4352
+ style: settingsCard,
4353
+ children: [
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", {
4373
+ style: notice,
4374
+ children: t("jiraLoading")
4375
+ }) : null,
4376
+ jiraStatus?.connected === true ? /* @__PURE__ */ jsxs("div", {
4377
+ style: diagnosticGrid,
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", {
4390
+ style: diagnosticLabel,
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
+ }),
4122
4467
  /* @__PURE__ */ jsxs("section", {
4123
4468
  style: settingsCard,
4124
4469
  children: [
@@ -4302,31 +4647,161 @@ window.__ModuleLoader__.load({
4302
4647
  })).then(result);
4303
4648
  }
4304
4649
  //#endregion
4305
- //#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
+ ]);
4306
4658
  function record$2(value) {
4307
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4659
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4308
4660
  }
4309
- async function loadJson(path, sessionId, pullNumber, signal) {
4310
- const response = await fetch(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}?sessionId=${encodeURIComponent(sessionId)}&number=${pullNumber}`, {
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)}`, {
4311
4690
  method: "GET",
4312
4691
  credentials: "same-origin",
4313
4692
  headers: { accept: "application/json" },
4314
4693
  ...signal === void 0 ? {} : { signal }
4315
- });
4316
- const body = record$2(await response.json());
4317
- if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
4318
- if (body === void 0) throw new Error("Invalid pull request feedback response.");
4319
- return body;
4694
+ }));
4320
4695
  }
4321
- async function loadPullRequestComments(sessionId, pullNumber, signal) {
4322
- const body = await loadJson("/comments", sessionId, pullNumber, signal);
4323
- if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
4324
- const comments = [];
4325
- for (const item of body.comments) {
4326
- const input = record$2(item);
4327
- 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;
4328
- comments.push(input);
4329
- }
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) {
4782
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4783
+ }
4784
+ async function loadJson(path, sessionId, pullNumber, signal) {
4785
+ const response = await fetch(`${CLAUDE_REPOSITORY_FEEDBACK_PATH}${path}?sessionId=${encodeURIComponent(sessionId)}&number=${pullNumber}`, {
4786
+ method: "GET",
4787
+ credentials: "same-origin",
4788
+ headers: { accept: "application/json" },
4789
+ ...signal === void 0 ? {} : { signal }
4790
+ });
4791
+ const body = record$1(await response.json());
4792
+ if (!response.ok) throw new Error(typeof body?.message === "string" ? body.message : "Pull request feedback is unavailable.");
4793
+ if (body === void 0) throw new Error("Invalid pull request feedback response.");
4794
+ return body;
4795
+ }
4796
+ async function loadPullRequestComments(sessionId, pullNumber, signal) {
4797
+ const body = await loadJson("/comments", sessionId, pullNumber, signal);
4798
+ if (!Array.isArray(body.comments)) throw new Error("Invalid pull request comments response.");
4799
+ const comments = [];
4800
+ for (const item of body.comments) {
4801
+ const input = record$1(item);
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;
4803
+ comments.push(input);
4804
+ }
4330
4805
  return comments;
4331
4806
  }
4332
4807
  async function loadFailingChecks(sessionId, pullNumber, signal) {
@@ -4334,7 +4809,7 @@ window.__ModuleLoader__.load({
4334
4809
  if (!Array.isArray(body.checks)) throw new Error("Invalid pull request checks response.");
4335
4810
  const checks = [];
4336
4811
  for (const item of body.checks) {
4337
- const input = record$2(item);
4812
+ const input = record$1(item);
4338
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;
4339
4814
  checks.push(input);
4340
4815
  }
@@ -4495,7 +4970,7 @@ window.__ModuleLoader__.load({
4495
4970
  ]
4496
4971
  });
4497
4972
  }
4498
- function repositoryName(remote) {
4973
+ function repositoryName$1(remote) {
4499
4974
  return remote?.split("/").at(-1);
4500
4975
  }
4501
4976
  function relativeAge(value) {
@@ -4532,7 +5007,7 @@ window.__ModuleLoader__.load({
4532
5007
  /* @__PURE__ */ jsxs("span", {
4533
5008
  style: repositoryPrHoverRepo,
4534
5009
  children: [
4535
- repositoryName(repository.remote),
5010
+ repositoryName$1(repository.remote),
4536
5011
  " #",
4537
5012
  pullRequest.number,
4538
5013
  pullRequest.baseBranch === void 0 ? "" : ` → ${pullRequest.baseBranch}`
@@ -4982,6 +5457,112 @@ window.__ModuleLoader__.load({
4982
5457
  })
4983
5458
  ] });
4984
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
+ }
4985
5566
  const MERGE_METHODS = [
4986
5567
  "merge",
4987
5568
  "squash",
@@ -5151,7 +5732,7 @@ window.__ModuleLoader__.load({
5151
5732
  })
5152
5733
  ] });
5153
5734
  }
5154
- function ClaudeRepositoryStatus({ sessionId, useSessions, useClaudeProjection, t, openDiff, submitPrompt }) {
5735
+ function ClaudeRepositoryStatus({ sessionId, useSessions, useClaudeProjection, t, openDiff, submitPrompt, openOverview, deleteWorkspace }) {
5155
5736
  const blank = useSessions((value) => value.byId[sessionId]?.blank === true);
5156
5737
  const running = useSessions((value) => value.byId[sessionId]?.running === true);
5157
5738
  const projection = useClaudeProjection((value) => value);
@@ -5193,12 +5774,23 @@ window.__ModuleLoader__.load({
5193
5774
  ...merged ? repositoryBarMerged : {}
5194
5775
  },
5195
5776
  children: [
5196
- /* @__PURE__ */ jsx("span", {
5777
+ openOverview === void 0 ? /* @__PURE__ */ jsx("span", {
5197
5778
  style: {
5198
5779
  ...repositoryPrIcon,
5199
5780
  ...merged ? repositoryPrIconMerged : {}
5200
5781
  },
5201
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 })
5202
5794
  }),
5203
5795
  /* @__PURE__ */ jsx(PullRequestLink, {
5204
5796
  repository,
@@ -5206,7 +5798,7 @@ window.__ModuleLoader__.load({
5206
5798
  }),
5207
5799
  repository.remote === void 0 ? null : /* @__PURE__ */ jsx("span", {
5208
5800
  style: repositoryRemote,
5209
- children: repositoryName(repository.remote)
5801
+ children: repositoryName$1(repository.remote)
5210
5802
  }),
5211
5803
  /* @__PURE__ */ jsx("span", {
5212
5804
  style: repositoryBranch,
@@ -5242,7 +5834,7 @@ window.__ModuleLoader__.load({
5242
5834
  children: ["↑", aheadCount > 0 ? aheadCount : ""]
5243
5835
  }) : null]
5244
5836
  }) : null,
5245
- pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs("span", {
5837
+ pullRequest === void 0 ? null : merged ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
5246
5838
  style: repositoryMergedStatus,
5247
5839
  children: [
5248
5840
  /* @__PURE__ */ jsx("span", {
@@ -5255,7 +5847,11 @@ window.__ModuleLoader__.load({
5255
5847
  children: ["· ", t("repositoryMergedAgo", { age: mergedAge })]
5256
5848
  })
5257
5849
  ]
5258
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
5850
+ }), /* @__PURE__ */ jsx(CleanupControl, {
5851
+ repository,
5852
+ t,
5853
+ ...deleteWorkspace === void 0 ? {} : { deleteWorkspace }
5854
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
5259
5855
  pullRequest.checks === "failing" ? /* @__PURE__ */ jsx(FailingChecksControl, {
5260
5856
  sessionId,
5261
5857
  pullNumber: pullRequest.number,
@@ -5296,7 +5892,7 @@ window.__ModuleLoader__.load({
5296
5892
  }
5297
5893
  //#endregion
5298
5894
  //#region src/client/review-comment-api.ts
5299
- function record$1(value) {
5895
+ function record(value) {
5300
5896
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5301
5897
  }
5302
5898
  async function post(path, sessionId, body) {
@@ -5311,13 +5907,13 @@ window.__ModuleLoader__.load({
5311
5907
  });
5312
5908
  const value = await response.json();
5313
5909
  if (!response.ok) {
5314
- const error = record$1(value);
5910
+ const error = record(value);
5315
5911
  throw new Error(typeof error?.message === "string" ? error.message : "Review comment request failed.");
5316
5912
  }
5317
5913
  return value;
5318
5914
  }
5319
5915
  async function addReviewComment(sessionId, comment) {
5320
- const created = record$1(record$1(await post("", sessionId, comment))?.comment);
5916
+ const created = record(record(await post("", sessionId, comment))?.comment);
5321
5917
  if (created === void 0 || typeof created.id !== "string") throw new Error("Invalid review comment response.");
5322
5918
  return created;
5323
5919
  }
@@ -6549,6 +7145,138 @@ window.__ModuleLoader__.load({
6549
7145
  });
6550
7146
  }
6551
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
+ ]
7277
+ });
7278
+ }
7279
+ //#endregion
6552
7280
  //#region src/client/hero-dom-bridge.ts
6553
7281
  const PORTAL_ATTRIBUTE = "data-dsh-claude-hero-controls";
6554
7282
  function normalizedText(element) {
@@ -6594,110 +7322,6 @@ window.__ModuleLoader__.load({
6594
7322
  for (const portal of root.querySelectorAll(`[${PORTAL_ATTRIBUTE}]`)) portal.remove();
6595
7323
  }
6596
7324
  //#endregion
6597
- //#region src/client/repository-setup-api.ts
6598
- const HOST_STAGES = /* @__PURE__ */ new Set([
6599
- "inspecting",
6600
- "fetching",
6601
- "creating-worktree",
6602
- "saving-worktree",
6603
- "switching-branch"
6604
- ]);
6605
- function record(value) {
6606
- return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
6607
- }
6608
- function setupResult(value) {
6609
- const item = record(value);
6610
- 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;
6611
- return item;
6612
- }
6613
- function parseRepositorySetupEvent(line, onProgress) {
6614
- const event = record(JSON.parse(line));
6615
- if (event?.type === "progress" && typeof event.stage === "string" && HOST_STAGES.has(event.stage)) {
6616
- onProgress(event.stage);
6617
- return;
6618
- }
6619
- if (event?.type === "complete") {
6620
- const result = setupResult(event.result);
6621
- if (result !== void 0) return result;
6622
- }
6623
- if (event?.type === "error" && typeof event.message === "string") throw new Error(event.message);
6624
- throw new Error("Invalid repository setup progress response.");
6625
- }
6626
- async function response(pending) {
6627
- const result = await pending;
6628
- const body = await result.json();
6629
- if (!result.ok) {
6630
- const error = body;
6631
- throw new Error(error.message ?? error.error ?? "Repository setup failed.");
6632
- }
6633
- return body;
6634
- }
6635
- function loadRepositoryBranches(cwd, signal) {
6636
- return response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/branches?cwd=${encodeURIComponent(cwd)}`, {
6637
- method: "GET",
6638
- credentials: "same-origin",
6639
- headers: { accept: "application/json" },
6640
- ...signal === void 0 ? {} : { signal }
6641
- }));
6642
- }
6643
- async function prepareRepository(cwd, branch, worktree, branchName, onProgress = () => {}) {
6644
- const result = await fetch(CLAUDE_REPOSITORY_SETUP_PATH, {
6645
- method: "POST",
6646
- credentials: "same-origin",
6647
- headers: {
6648
- accept: "application/x-ndjson",
6649
- "content-type": "application/json"
6650
- },
6651
- body: JSON.stringify({
6652
- cwd,
6653
- branch,
6654
- worktree,
6655
- ...branchName === void 0 ? {} : { branchName }
6656
- })
6657
- });
6658
- if (!result.ok) {
6659
- const body = await result.json();
6660
- throw new Error(body.message ?? body.error ?? "Repository setup failed.");
6661
- }
6662
- if (result.body === null) throw new Error("Repository setup progress stream is unavailable.");
6663
- const reader = result.body.getReader();
6664
- const decoder = new TextDecoder();
6665
- let buffer = "";
6666
- let completed;
6667
- while (true) {
6668
- const chunk = await reader.read();
6669
- buffer += decoder.decode(chunk.value, { stream: !chunk.done });
6670
- const lines = buffer.split("\n");
6671
- buffer = lines.pop() ?? "";
6672
- for (const line of lines) {
6673
- if (line.trim().length === 0) continue;
6674
- const value = parseRepositorySetupEvent(line, onProgress);
6675
- if (value !== void 0) completed = value;
6676
- }
6677
- if (chunk.done) break;
6678
- }
6679
- if (buffer.trim().length > 0) {
6680
- const value = parseRepositorySetupEvent(buffer, onProgress);
6681
- if (value !== void 0) completed = value;
6682
- }
6683
- if (completed === void 0) throw new Error("Repository setup progress ended before completion.");
6684
- return completed;
6685
- }
6686
- async function bindRepositoryLease(leaseId, sessionId) {
6687
- await response(fetch(`${CLAUDE_REPOSITORY_SETUP_PATH}/bind`, {
6688
- method: "POST",
6689
- credentials: "same-origin",
6690
- headers: {
6691
- accept: "application/json",
6692
- "content-type": "application/json"
6693
- },
6694
- body: JSON.stringify({
6695
- leaseId,
6696
- sessionId
6697
- })
6698
- }));
6699
- }
6700
- //#endregion
6701
7325
  //#region src/client/ClaudeHeroRepositoryControls.tsx
6702
7326
  function shouldInterceptKey(event) {
6703
7327
  if (event.key !== "Enter" || event.shiftKey || event.repeat || event.isComposing) return false;
@@ -6829,6 +7453,23 @@ window.__ModuleLoader__.load({
6829
7453
  })]
6830
7454
  });
6831
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
+ }
6832
7473
  function ClaudeHeroRepositoryCapsule({ branches, selected, worktree, busy, menuOpen, worktreeLabel, searchPlaceholder, emptySearchLabel, onMenuOpenChange, onSelect, onWorktreeChange }) {
6833
7474
  const pickerRef = useRef(null);
6834
7475
  const searchRef = useRef(null);
@@ -7015,6 +7656,14 @@ window.__ModuleLoader__.load({
7015
7656
  const [progressStage, setProgressStage] = useState();
7016
7657
  const [progressError, setProgressError] = useState();
7017
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);
7018
7667
  const pendingRef = useRef(false);
7019
7668
  const path = workspacePath ?? cwd;
7020
7669
  useEffect(() => {
@@ -7057,6 +7706,11 @@ window.__ModuleLoader__.load({
7057
7706
  setProgressStage(void 0);
7058
7707
  setProgressError(void 0);
7059
7708
  setError(void 0);
7709
+ setTicket(void 0);
7710
+ setTicketMenuOpen(false);
7711
+ setTicketQuery("");
7712
+ setTicketResults(void 0);
7713
+ setTicketError(void 0);
7060
7714
  if (portal === void 0 || path === void 0) return;
7061
7715
  const controller = new AbortController();
7062
7716
  loadRepositoryBranches(path, controller.signal).then((value) => {
@@ -7074,11 +7728,11 @@ window.__ModuleLoader__.load({
7074
7728
  sessionId
7075
7729
  ]);
7076
7730
  const availableBranches = branches === void 0 ? [] : repositoryBranchOptions(branches);
7077
- 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);
7078
7732
  useEffect(() => {
7079
7733
  if (!changed || portal === void 0 || busy) return;
7080
7734
  const submit = (event) => {
7081
- if (pendingRef.current || input.draft.trim().length === 0) return;
7735
+ if (pendingRef.current || input.draft.trim().length === 0 && ticket === void 0) return;
7082
7736
  if (!(event instanceof KeyboardEvent ? shouldInterceptKey(event) : shouldInterceptClick(event))) return;
7083
7737
  event.preventDefault();
7084
7738
  event.stopPropagation();
@@ -7089,7 +7743,7 @@ window.__ModuleLoader__.load({
7089
7743
  setError(void 0);
7090
7744
  prepare(branches.root, selected, worktree, (stage) => {
7091
7745
  if (worktree) setProgressStage(stage);
7092
- }).catch((reason) => {
7746
+ }, ticket).catch((reason) => {
7093
7747
  const message = reason instanceof Error ? reason.message : String(reason);
7094
7748
  if (worktree) setProgressError(message);
7095
7749
  else setError(message);
@@ -7112,38 +7766,187 @@ window.__ModuleLoader__.load({
7112
7766
  portal,
7113
7767
  prepare,
7114
7768
  selected,
7769
+ ticket,
7115
7770
  worktree
7116
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
+ };
7117
7823
  if (portal === void 0 || path === void 0) return null;
7118
7824
  return createPortal(/* @__PURE__ */ jsxs("span", {
7119
7825
  style: heroRepositoryControls,
7120
7826
  children: [branches === void 0 ? /* @__PURE__ */ jsx("span", {
7121
7827
  style: heroRepositoryStatus,
7122
7828
  children: error ?? t("repositoryBranchesLoading")
7123
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ClaudeHeroRepositoryCapsule, {
7124
- branches: availableBranches,
7125
- selected,
7126
- worktree,
7127
- busy,
7128
- menuOpen,
7129
- worktreeLabel: t("repositoryWorktree"),
7130
- searchPlaceholder: t("repositoryBranchSearch"),
7131
- emptySearchLabel: t("repositoryBranchSearchEmpty"),
7132
- onMenuOpenChange: setMenuOpen,
7133
- onSelect: (branch) => {
7134
- setSelected(branch);
7135
- setMenuOpen(false);
7136
- setError(void 0);
7137
- },
7138
- onWorktreeChange: (checked) => {
7139
- setWorktree(checked);
7140
- setError(void 0);
7141
- }
7142
- }), error === void 0 ? null : /* @__PURE__ */ jsx("span", {
7143
- role: "alert",
7144
- style: heroRepositoryError,
7145
- children: error
7146
- })] }), 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, {
7147
7950
  stage: progressStage,
7148
7951
  ...progressError === void 0 ? {} : { error: progressError },
7149
7952
  t,
@@ -7628,6 +8431,39 @@ window.__ModuleLoader__.load({
7628
8431
  queueEditFailed: "排队消息编辑失败。",
7629
8432
  queueRemoveFailed: "排队消息删除失败。",
7630
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: "读取中…",
7631
8467
  reviewCommentAdd: "添加行评论",
7632
8468
  reviewCommentPlaceholder: "请求更改",
7633
8469
  reviewCommentSubmit: "注释",
@@ -7866,6 +8702,39 @@ window.__ModuleLoader__.load({
7866
8702
  queueEditFailed: "The queued message could not be edited.",
7867
8703
  queueRemoveFailed: "The queued message could not be removed.",
7868
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…",
7869
8738
  reviewCommentAdd: "Add line comment",
7870
8739
  reviewCommentPlaceholder: "Request changes",
7871
8740
  reviewCommentSubmit: "Comment",
@@ -7996,6 +8865,31 @@ window.__ModuleLoader__.load({
7996
8865
  layout?.openDetails();
7997
8866
  disposeExpandedDetailsResize = enableExpandedDetailsResize();
7998
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
+ };
7999
8893
  const openDiffPanel = (sessionId) => {
8000
8894
  closePluginDetails();
8001
8895
  detailsSessionId = sessionId;
@@ -8111,7 +9005,12 @@ window.__ModuleLoader__.load({
8111
9005
  return {
8112
9006
  t,
8113
9007
  openDiff: () => openDiffPanel(sessionId),
8114
- ...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
+ } }
8115
9014
  };
8116
9015
  }
8117
9016
  }, ClaudeRepositoryStatus));
@@ -8140,14 +9039,19 @@ window.__ModuleLoader__.load({
8140
9039
  locale: namespace,
8141
9040
  inject: (sourceSessionId) => ({
8142
9041
  t,
8143
- prepare: async (cwd, branch, useWorktree, onProgress) => {
9042
+ prepare: async (cwd, branch, useWorktree, onProgress, ticket) => {
8144
9043
  const sourceScope = sessions.scope(sourceSessionId);
8145
9044
  if (sourceScope === void 0) throw new Error(t("repositorySessionUnavailable"));
8146
9045
  const sourceInput = conversation.input.for(sourceScope);
8147
- 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)}`;
8148
9048
  const imageIds = sourceInput.state.getSnapshot().imageIds;
8149
- 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
+ });
8150
9053
  if (prepared.mode === "checkout") {
9054
+ if (draft !== rawDraft) sourceInput.setDraft(draft);
8151
9055
  sourceInput.submit();
8152
9056
  return;
8153
9057
  }