webmux 0.37.0 → 0.38.0

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/bin/webmux.js CHANGED
@@ -972,7 +972,7 @@ var require_src = __commonJS((exports, module) => {
972
972
  module.exports = { cursor, scroll, erase, beep };
973
973
  });
974
974
 
975
- // node_modules/.bun/@clack+core@1.4.1/node_modules/@clack/core/dist/index.mjs
975
+ // node_modules/.bun/@clack+core@1.4.2/node_modules/@clack/core/dist/index.mjs
976
976
  import { styleText } from "util";
977
977
  import { stdout, stdin } from "process";
978
978
  import l__default from "readline";
@@ -1608,8 +1608,8 @@ var init_dist3 = __esm(() => {
1608
1608
  };
1609
1609
  o$1 = /* @__PURE__ */ new Set(["up", "down", "left", "right"]);
1610
1610
  h = class h extends V {
1611
- #s = false;
1612
- #t;
1611
+ #t = false;
1612
+ #s;
1613
1613
  focused = "editor";
1614
1614
  get userInputWithCursor() {
1615
1615
  if (this.state === "submit")
@@ -1617,10 +1617,10 @@ var init_dist3 = __esm(() => {
1617
1617
  const t2 = this.userInput;
1618
1618
  if (this.cursor >= t2.length)
1619
1619
  return `${t2}\u2588`;
1620
- const s = t2.slice(0, this.cursor), r2 = t2[this.cursor], e = t2.slice(this.cursor + 1);
1620
+ const s = t2.slice(0, this.cursor), r2 = t2[this.cursor], i = t2.slice(this.cursor + 1);
1621
1621
  return r2 === `
1622
1622
  ` ? `${s}\u2588
1623
- ${e}` : `${s}${styleText("inverse", r2)}${e}`;
1623
+ ${i}` : `${s}${styleText("inverse", r2)}${i}`;
1624
1624
  }
1625
1625
  get cursor() {
1626
1626
  return this._cursor;
@@ -1650,37 +1650,41 @@ ${e}` : `${s}${styleText("inverse", r2)}${e}`;
1650
1650
  }
1651
1651
  }
1652
1652
  _shouldSubmit(t2, s) {
1653
- if (this.#t)
1653
+ if (this.#s)
1654
1654
  return this.focused === "submit" ? true : (this.#r(`
1655
1655
  `), this._cursor++, false);
1656
- const r2 = this.#s;
1657
- return this.#s = true, r2 ? (this.userInput[this.cursor - 1] === `
1656
+ const r2 = this.#t;
1657
+ return this.#t = true, r2 && this.cursor === this.userInput.length ? (this.userInput[this.cursor - 1] === `
1658
1658
  ` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
1659
1659
  `), this._cursor++, false);
1660
1660
  }
1661
1661
  constructor(t2) {
1662
- super(t2, false), this.#t = t2.showSubmit ?? false, this.on("key", (s, r2) => {
1663
- if (r2?.name && o$1.has(r2.name)) {
1664
- this.#i(r2.name);
1662
+ const s = t2.initialUserInput ?? t2.initialValue;
1663
+ super({
1664
+ ...t2,
1665
+ initialUserInput: s
1666
+ }, false), s !== undefined && (this._cursor = s.length), this.#s = t2.showSubmit ?? false, this.on("key", (r2, i) => {
1667
+ if (i?.name && o$1.has(i.name)) {
1668
+ this.#t = false, this.#i(i.name);
1665
1669
  return;
1666
1670
  }
1667
- if (s === "\t" && this.#t) {
1671
+ if (r2 === "\t" && this.#s) {
1668
1672
  this.focused = this.focused === "editor" ? "submit" : "editor";
1669
1673
  return;
1670
1674
  }
1671
- if (r2?.name !== "return") {
1672
- if (this.#s = false, r2?.name === "backspace" && this.cursor > 0) {
1675
+ if (i?.name !== "return") {
1676
+ if (this.#t = false, i?.name === "backspace" && this.cursor > 0) {
1673
1677
  this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--;
1674
1678
  return;
1675
1679
  }
1676
- if (r2?.name === "delete" && this.cursor < this.userInput.length) {
1680
+ if (i?.name === "delete" && this.cursor < this.userInput.length) {
1677
1681
  this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
1678
1682
  return;
1679
1683
  }
1680
- s && (this.#t && this.focused === "submit" && (this.focused = "editor"), this.#r(s ?? ""), this._cursor++);
1684
+ r2 && (this.#s && this.focused === "submit" && (this.focused = "editor"), this.#r(r2 ?? ""), this._cursor++);
1681
1685
  }
1682
- }), this.on("userInput", (s) => {
1683
- this._setValue(s);
1686
+ }), this.on("userInput", (r2) => {
1687
+ this._setValue(r2);
1684
1688
  }), this.on("finalize", () => {
1685
1689
  this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
1686
1690
  });
@@ -1715,7 +1719,7 @@ ${e}` : `${s}${styleText("inverse", r2)}${e}`;
1715
1719
  };
1716
1720
  });
1717
1721
 
1718
- // node_modules/.bun/@clack+prompts@1.5.1/node_modules/@clack/prompts/dist/index.mjs
1722
+ // node_modules/.bun/@clack+prompts@1.6.0/node_modules/@clack/prompts/dist/index.mjs
1719
1723
  import { styleText as styleText2, stripVTControlCharacters } from "util";
1720
1724
  import process$1 from "process";
1721
1725
  function isUnicodeSupported() {
@@ -1724,8 +1728,12 @@ function isUnicodeSupported() {
1724
1728
  }
1725
1729
  return Boolean(process$1.env.CI) || Boolean(process$1.env.WT_SESSION) || Boolean(process$1.env.TERMINUS_SUBLIME) || process$1.env.ConEmuTask === "{cmd::Cmder}" || process$1.env.TERM_PROGRAM === "Terminus-Sublime" || process$1.env.TERM_PROGRAM === "vscode" || process$1.env.TERM === "xterm-256color" || process$1.env.TERM === "alacritty" || process$1.env.TERMINAL_EMULATOR === "JetBrains-JediTerm";
1726
1730
  }
1727
- var import_sisteransi2, unicode, unicodeOr = (e, o2) => unicode ? e : o2, S_STEP_ACTIVE, S_STEP_CANCEL, S_STEP_ERROR, S_STEP_SUBMIT, S_BAR_START, S_BAR, S_BAR_END, S_BAR_START_RIGHT, S_BAR_END_RIGHT, S_RADIO_ACTIVE, S_RADIO_INACTIVE, S_CHECKBOX_ACTIVE, S_CHECKBOX_SELECTED, S_CHECKBOX_INACTIVE, S_PASSWORD_MASK, S_BAR_H, S_CORNER_TOP_RIGHT, S_CONNECT_LEFT, S_CORNER_BOTTOM_RIGHT, S_CORNER_BOTTOM_LEFT, S_CORNER_TOP_LEFT, S_INFO, S_SUCCESS, S_WARN, S_ERROR, symbol = (e) => {
1728
- switch (e) {
1731
+ function formatInstructionFooter(o2, e) {
1732
+ const r2 = [`${e ? `${styleText2("cyan", S_BAR)} ` : ""}${o2.join(" \u2022 ")}`];
1733
+ return e && r2.push(styleText2("cyan", S_BAR_END)), r2;
1734
+ }
1735
+ var import_sisteransi2, unicode, unicodeOr = (o2, e) => unicode ? o2 : e, S_STEP_ACTIVE, S_STEP_CANCEL, S_STEP_ERROR, S_STEP_SUBMIT, S_BAR_START, S_BAR, S_BAR_END, S_BAR_START_RIGHT, S_BAR_END_RIGHT, S_RADIO_ACTIVE, S_RADIO_INACTIVE, S_CHECKBOX_ACTIVE, S_CHECKBOX_SELECTED, S_CHECKBOX_INACTIVE, S_PASSWORD_MASK, S_BAR_H, S_CORNER_TOP_RIGHT, S_CONNECT_LEFT, S_CORNER_BOTTOM_RIGHT, S_CORNER_BOTTOM_LEFT, S_CORNER_TOP_LEFT, S_INFO, S_SUCCESS, S_WARN, S_ERROR, symbol = (o2) => {
1736
+ switch (o2) {
1729
1737
  case "initial":
1730
1738
  case "active":
1731
1739
  return styleText2("cyan", S_STEP_ACTIVE);
@@ -1736,8 +1744,8 @@ var import_sisteransi2, unicode, unicodeOr = (e, o2) => unicode ? e : o2, S_STEP
1736
1744
  case "submit":
1737
1745
  return styleText2("green", S_STEP_SUBMIT);
1738
1746
  }
1739
- }, symbolBar = (e) => {
1740
- switch (e) {
1747
+ }, symbolBar = (o2) => {
1748
+ switch (o2) {
1741
1749
  case "initial":
1742
1750
  case "active":
1743
1751
  return styleText2("cyan", S_BAR);
@@ -1829,7 +1837,7 @@ ${g2}
1829
1837
  }
1830
1838
  }
1831
1839
  }).prompt();
1832
- }, log, intro = (o2 = "", t2) => {
1840
+ }, MULTISELECT_INSTRUCTIONS, log, intro = (o2 = "", t2) => {
1833
1841
  const i = t2?.output ?? process.stdout, e = t2?.withGuide ?? settings.withGuide ? `${styleText2("gray", S_BAR_START)} ` : "";
1834
1842
  i.write(`${e}${o2}
1835
1843
  `);
@@ -1839,38 +1847,38 @@ ${styleText2("gray", S_BAR_END)} ` : "";
1839
1847
  i.write(`${e}${o2}
1840
1848
 
1841
1849
  `);
1842
- }, W$1 = (o2) => styleText2("dim", o2), C2 = (o2, e, s) => {
1850
+ }, W$1 = (o2) => o2, C2 = (o2, e, s) => {
1843
1851
  const a2 = {
1844
1852
  hard: true,
1845
1853
  trim: false
1846
1854
  }, i = wrapAnsi(o2, e, a2).split(`
1847
- `), c2 = i.reduce((n2, r2) => Math.max(dist_default2(r2), n2), 0), u3 = i.map(s).reduce((n2, r2) => Math.max(dist_default2(r2), n2), 0), g2 = e - (u3 - c2);
1855
+ `), c2 = i.reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), u3 = i.map(s).reduce((n2, t2) => Math.max(dist_default2(t2), n2), 0), g2 = e - (u3 - c2);
1848
1856
  return wrapAnsi(o2, g2, a2);
1849
1857
  }, note = (o2 = "", e = "", s) => {
1850
1858
  const a2 = s?.output ?? process$1.stdout, i = s?.withGuide ?? settings.withGuide, c2 = s?.format ?? W$1, g2 = ["", ...C2(o2, getColumns(a2) - 6, c2).split(`
1851
- `).map(c2), ""], n2 = dist_default2(e), r2 = Math.max(g2.reduce((m2, F) => {
1859
+ `).map(c2), ""], n2 = dist_default2(e), t2 = Math.max(g2.reduce((m2, F) => {
1852
1860
  const O = dist_default2(F);
1853
1861
  return O > m2 ? O : m2;
1854
- }, 0), n2) + 2, h2 = g2.map((m2) => `${styleText2("gray", S_BAR)} ${m2}${" ".repeat(r2 - dist_default2(m2))}${styleText2("gray", S_BAR)}`).join(`
1862
+ }, 0), n2) + 2, h2 = g2.map((m2) => `${styleText2("gray", S_BAR)} ${m2}${" ".repeat(t2 - dist_default2(m2))}${styleText2("gray", S_BAR)}`).join(`
1855
1863
  `), T3 = i ? `${styleText2("gray", S_BAR)}
1856
1864
  ` : "", l$1 = i ? S_CONNECT_LEFT : S_CORNER_BOTTOM_LEFT;
1857
- a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(r2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT)}
1865
+ a2.write(`${T3}${styleText2("green", S_STEP_SUBMIT)} ${styleText2("reset", e)} ${styleText2("gray", S_BAR_H.repeat(Math.max(t2 - n2 - 1, 1)) + S_CORNER_TOP_RIGHT)}
1858
1866
  ${h2}
1859
- ${styleText2("gray", l$1 + S_BAR_H.repeat(r2 + 2) + S_CORNER_BOTTOM_RIGHT)}
1867
+ ${styleText2("gray", l$1 + S_BAR_H.repeat(t2 + 2) + S_CORNER_BOTTOM_RIGHT)}
1860
1868
  `);
1861
- }, u3, c2 = (e, a2) => e.includes(`
1862
- `) ? e.split(`
1863
- `).map((t2) => a2(t2)).join(`
1864
- `) : a2(e), select = (e) => {
1865
- const a2 = (t2, d) => {
1866
- const s = t2.label ?? String(t2.value);
1867
- switch (d) {
1869
+ }, u3, SELECT_INSTRUCTIONS, c2 = (t2, a2) => t2.includes(`
1870
+ `) ? t2.split(`
1871
+ `).map((i) => a2(i)).join(`
1872
+ `) : a2(t2), select = (t2) => {
1873
+ const a2 = (i, m2) => {
1874
+ const s = i.label ?? String(i.value);
1875
+ switch (m2) {
1868
1876
  case "disabled":
1869
- return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${t2.hint ? ` ${styleText2("dim", `(${t2.hint ?? "disabled"})`)}` : ""}`;
1877
+ return `${styleText2("gray", S_RADIO_INACTIVE)} ${c2(s, (n2) => styleText2("gray", n2))}${i.hint ? ` ${styleText2("dim", `(${i.hint ?? "disabled"})`)}` : ""}`;
1870
1878
  case "selected":
1871
1879
  return `${c2(s, (n2) => styleText2("dim", n2))}`;
1872
1880
  case "active":
1873
- return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${t2.hint ? ` ${styleText2("dim", `(${t2.hint})`)}` : ""}`;
1881
+ return `${styleText2("green", S_RADIO_ACTIVE)} ${s}${i.hint ? ` ${styleText2("dim", `(${i.hint})`)}` : ""}`;
1874
1882
  case "cancelled":
1875
1883
  return `${c2(s, (n2) => styleText2(["strikethrough", "dim"], n2))}`;
1876
1884
  default:
@@ -1878,39 +1886,40 @@ ${styleText2("gray", l$1 + S_BAR_H.repeat(r2 + 2) + S_CORNER_BOTTOM_RIGHT)}
1878
1886
  }
1879
1887
  };
1880
1888
  return new a({
1881
- options: e.options,
1882
- signal: e.signal,
1883
- input: e.input,
1884
- output: e.output,
1885
- initialValue: e.initialValue,
1889
+ options: t2.options,
1890
+ signal: t2.signal,
1891
+ input: t2.input,
1892
+ output: t2.output,
1893
+ initialValue: t2.initialValue,
1886
1894
  render() {
1887
- const t2 = e.withGuide ?? settings.withGuide, d = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(e.output, e.message, s, d), u4 = `${t2 ? `${styleText2("gray", S_BAR)}
1895
+ const i = t2.withGuide ?? settings.withGuide, m2 = `${symbol(this.state)} `, s = `${symbolBar(this.state)} `, n2 = wrapTextWithPrefix(t2.output, t2.message, s, m2), u4 = `${i ? `${styleText2("gray", S_BAR)}
1888
1896
  ` : ""}${n2}
1889
1897
  `;
1890
1898
  switch (this.state) {
1891
1899
  case "submit": {
1892
- const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "selected"), r2);
1893
- return `${u4}${l}`;
1900
+ const r2 = i ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(t2.output, a2(this.options[this.cursor], "selected"), r2);
1901
+ return `${u4}${o2}`;
1894
1902
  }
1895
1903
  case "cancel": {
1896
- const r2 = t2 ? `${styleText2("gray", S_BAR)} ` : "", l = wrapTextWithPrefix(e.output, a2(this.options[this.cursor], "cancelled"), r2);
1897
- return `${u4}${l}${t2 ? `
1904
+ const r2 = i ? `${styleText2("gray", S_BAR)} ` : "", o2 = wrapTextWithPrefix(t2.output, a2(this.options[this.cursor], "cancelled"), r2);
1905
+ return `${u4}${o2}${i ? `
1898
1906
  ${styleText2("gray", S_BAR)}` : ""}`;
1899
1907
  }
1900
1908
  default: {
1901
- const r2 = t2 ? `${styleText2("cyan", S_BAR)} ` : "", l = t2 ? styleText2("cyan", S_BAR_END) : "", g2 = u4.split(`
1902
- `).length, h2 = t2 ? 2 : 1;
1909
+ const r2 = i ? `${styleText2("cyan", S_BAR)} ` : "", o2 = u4.split(`
1910
+ `).length, $ = formatInstructionFooter(SELECT_INSTRUCTIONS, i), h2 = $.join(`
1911
+ `), b2 = $.length + 1;
1903
1912
  return `${u4}${r2}${limitOptions({
1904
- output: e.output,
1913
+ output: t2.output,
1905
1914
  cursor: this.cursor,
1906
1915
  options: this.options,
1907
- maxItems: e.maxItems,
1916
+ maxItems: t2.maxItems,
1908
1917
  columnPadding: r2.length,
1909
- rowPadding: g2 + h2,
1910
- style: (p2, b2) => a2(p2, p2.disabled ? "disabled" : b2 ? "active" : "inactive")
1918
+ rowPadding: o2 + b2,
1919
+ style: (p2, x) => a2(p2, p2.disabled ? "disabled" : x ? "active" : "inactive")
1911
1920
  }).join(`
1912
1921
  ${r2}`)}
1913
- ${l}
1922
+ ${h2}
1914
1923
  `;
1915
1924
  }
1916
1925
  }
@@ -1949,6 +1958,11 @@ var init_dist4 = __esm(() => {
1949
1958
  S_SUCCESS = unicodeOr("\u25C6", "*");
1950
1959
  S_WARN = unicodeOr("\u25B2", "!");
1951
1960
  S_ERROR = unicodeOr("\u25A0", "x");
1961
+ MULTISELECT_INSTRUCTIONS = [
1962
+ `${styleText2("dim", "\u2191/\u2193")} to navigate`,
1963
+ `${styleText2("dim", "Space:")} select`,
1964
+ `${styleText2("dim", "Enter:")} confirm`
1965
+ ];
1952
1966
  log = {
1953
1967
  message: (s = [], {
1954
1968
  symbol: e = styleText2("gray", S_BAR),
@@ -1996,6 +2010,10 @@ var init_dist4 = __esm(() => {
1996
2010
  heavy: unicodeOr("\u2501", "="),
1997
2011
  block: unicodeOr("\u2588", "#")
1998
2012
  };
2013
+ SELECT_INSTRUCTIONS = [
2014
+ `${styleText2("dim", "\u2191/\u2193")} to navigate`,
2015
+ `${styleText2("dim", "Enter:")} confirm`
2016
+ ];
1999
2017
  i = `${styleText2("gray", S_BAR)} `;
2000
2018
  });
2001
2019
 
@@ -7825,7 +7843,7 @@ var init_zod = __esm(() => {
7825
7843
  init_external();
7826
7844
  });
7827
7845
 
7828
- // node_modules/.bun/@ts-rest+core@3.52.1+0e383980587f1470/node_modules/@ts-rest/core/index.esm.mjs
7846
+ // node_modules/.bun/@ts-rest+core@3.52.1+f5aac5c7d31a6dcb/node_modules/@ts-rest/core/index.esm.mjs
7829
7847
  var isZodType = (obj) => {
7830
7848
  return typeof (obj === null || obj === undefined ? undefined : obj.safeParse) === "function";
7831
7849
  }, isZodObjectStrict = (obj) => {
@@ -8147,7 +8165,7 @@ function parseLinearTarget(raw) {
8147
8165
  return { kind: "team", teamKey: trimmed };
8148
8166
  return { kind: "invalid", raw: trimmed };
8149
8167
  }
8150
- var BooleanLikeSchema, ErrorResponseSchema, OkResponseSchema, EnabledResponseSchema, BuiltInAgentIdSchema, AgentIdSchema, WorktreeCreateModeSchema, LinearIssueIdSchema, LinearTeamKeySchema, PostWorktreeToLinearTargetSchema, PostWorktreeToLinearRequestSchema, PostWorktreeToLinearResponseSchema, FromLinearInputSchema, OneshotConfigSchema, AgentCapabilitiesSchema, AgentSummarySchema, AgentDetailsSchema, AgentListResponseSchema, UpsertCustomAgentRequestSchema, AgentResponseSchema, ValidateCustomAgentResponseSchema, WorktreeCreationPhaseSchema, AvailableBranchSchema, AvailableBranchesQuerySchema, NumberLikePathParamSchema, BranchListResponseSchema, WorktreeSourceSchema, CreateWorktreeRequestSchema, OpenWorktreeRequestSchema, CreateWorktreeResponseSchema, SetWorktreeArchivedRequestSchema, SetWorktreeArchivedResponseSchema, SetWorktreeLabelRequestSchema, SetWorktreeLabelResponseSchema, ToggleEnabledRequestSchema, SendWorktreePromptRequestSchema, AgentsSendMessageRequestSchema, PullMainRequestSchema, PullMainStatusSchema, PullMainResponseSchema, ServiceStatusSchema, PrCommentSchema, CiCheckSchema, PrEntrySchema, LinearIssueLabelSchema, LinearIssueStateSchema, LinkedLinearIssueSchema, LinearIssueSchema, LinearIssueAvailabilitySchema, LinearIssuesResponseSchema, AutoNameProviderSchema, AutoNameConfigResponseSchema, WorktreeCreationStateSchema, AppNotificationSchema, ProjectWorktreeSnapshotSchema, ProjectSnapshotSchema, WorktreeConversationProviderSchema, CodexWorktreeConversationRefSchema, ClaudeWorktreeConversationRefSchema, WorktreeConversationRefSchema, AgentsUiWorktreeSummarySchema, AgentsUiConversationMessageRoleSchema, AgentsUiConversationMessageStatusSchema, AgentsUiConversationMessageKindSchema, AgentsUiConversationMessageSchema, AgentsUiConversationStateSchema, AgentsUiWorktreeConversationResponseSchema, AgentsUiSendMessageResponseSchema, AgentsUiInterruptResponseSchema, AgentsUiConversationMessageDeltaEventSchema, AgentsUiConversationMessageUpsertEventSchema, AgentsUiConversationStatusEventSchema, AgentsUiConversationErrorEventSchema, AgentsUiConversationEventSchema, WorktreeListResponseSchema, UnpushedCommitSchema, WorktreeDiffResponseSchema, ServiceConfigSchema, ProfileConfigSchema, LinkedRepoInfoSchema, AppConfigSchema, CiLogsResponseSchema, WorktreeNameParamsSchema, NotificationIdParamsSchema, AgentIdParamsSchema, RunIdParamsSchema, InstanceSummarySchema, InstancesResponseSchema;
8168
+ var BooleanLikeSchema, ErrorResponseSchema, OkResponseSchema, EnabledResponseSchema, BuiltInAgentIdSchema, AgentIdSchema, WorktreeCreateModeSchema, LinearIssueIdSchema, LinearTeamKeySchema, PostWorktreeToLinearTargetSchema, PostWorktreeToLinearRequestSchema, PostWorktreeToLinearResponseSchema, FromLinearInputSchema, OneshotConfigSchema, AgentCapabilitiesSchema, AgentSummarySchema, AgentDetailsSchema, AgentListResponseSchema, UpsertCustomAgentRequestSchema, AgentResponseSchema, ValidateCustomAgentResponseSchema, WorktreeCreationPhaseSchema, AvailableBranchSchema, AvailableBranchesQuerySchema, NumberLikePathParamSchema, BranchListResponseSchema, WorktreeSourceSchema, CreateWorktreeRequestSchema, OpenWorktreeRequestSchema, CreateWorktreeResponseSchema, SetWorktreeArchivedRequestSchema, SetWorktreeArchivedResponseSchema, SetWorktreeLabelRequestSchema, SetWorktreeLabelResponseSchema, ToggleEnabledRequestSchema, SendWorktreePromptRequestSchema, AgentsSendMessageRequestSchema, PullMainRequestSchema, PullMainStatusSchema, PullMainResponseSchema, ServiceStatusSchema, PrCommentSchema, CiCheckSchema, PrEntrySchema, LinearIssueLabelSchema, LinearIssueStateSchema, LinkedLinearIssueSchema, LinearIssueSchema, LinearIssueAvailabilitySchema, LinearIssuesResponseSchema, AutoNameProviderSchema, AutoNameConfigResponseSchema, WorktreeCreationStateSchema, AppNotificationSchema, WorktreeTabSchema, ProjectWorktreeSnapshotSchema, ProjectSnapshotSchema, WorktreeConversationProviderSchema, CodexWorktreeConversationRefSchema, ClaudeWorktreeConversationRefSchema, WorktreeConversationRefSchema, AgentsUiWorktreeSummarySchema, AgentsUiConversationMessageRoleSchema, AgentsUiConversationMessageStatusSchema, AgentsUiConversationMessageKindSchema, AgentsUiConversationMessageSchema, AgentsUiConversationStateSchema, AgentsUiWorktreeConversationResponseSchema, AgentsUiSendMessageResponseSchema, AgentsUiInterruptResponseSchema, AgentsUiConversationMessageDeltaEventSchema, AgentsUiConversationMessageUpsertEventSchema, AgentsUiConversationStatusEventSchema, AgentsUiConversationErrorEventSchema, AgentsUiConversationEventSchema, WorktreeListResponseSchema, UnpushedCommitSchema, WorktreeDiffResponseSchema, ServiceConfigSchema, ProfileConfigSchema, LinkedRepoInfoSchema, AppConfigSchema, CiLogsResponseSchema, WorktreeNameParamsSchema, WorktreeTabParamsSchema, CreateTabResponseSchema, NotificationIdParamsSchema, AgentIdParamsSchema, RunIdParamsSchema, InstanceSummarySchema, InstancesResponseSchema;
8151
8169
  var init_schemas = __esm(() => {
8152
8170
  init_zod();
8153
8171
  BooleanLikeSchema = exports_external.union([
@@ -8403,6 +8421,15 @@ var init_schemas = __esm(() => {
8403
8421
  url: exports_external.string().optional(),
8404
8422
  timestamp: exports_external.number()
8405
8423
  });
8424
+ WorktreeTabSchema = exports_external.object({
8425
+ tabId: exports_external.string(),
8426
+ kind: exports_external.enum(["root", "fork"]),
8427
+ label: exports_external.string(),
8428
+ seq: exports_external.number().nullable(),
8429
+ sessionId: exports_external.string().nullable(),
8430
+ paneId: exports_external.string().optional(),
8431
+ createdAt: exports_external.string()
8432
+ });
8406
8433
  ProjectWorktreeSnapshotSchema = exports_external.object({
8407
8434
  branch: exports_external.string(),
8408
8435
  label: exports_external.string().nullable(),
@@ -8425,7 +8452,9 @@ var init_schemas = __esm(() => {
8425
8452
  linearIssue: LinkedLinearIssueSchema.nullable(),
8426
8453
  creation: WorktreeCreationStateSchema.nullable(),
8427
8454
  source: WorktreeSourceSchema,
8428
- oneshot: OneshotConfigSchema.nullable()
8455
+ oneshot: OneshotConfigSchema.nullable(),
8456
+ tabs: exports_external.array(WorktreeTabSchema).default([]),
8457
+ activeTabId: exports_external.string().nullable().default(null)
8429
8458
  });
8430
8459
  ProjectSnapshotSchema = exports_external.object({
8431
8460
  project: exports_external.object({
@@ -8508,12 +8537,14 @@ var init_schemas = __esm(() => {
8508
8537
  AgentsUiSendMessageResponseSchema = exports_external.object({
8509
8538
  conversationId: exports_external.string(),
8510
8539
  turnId: exports_external.string(),
8511
- running: exports_external.literal(true)
8540
+ running: exports_external.literal(true),
8541
+ streaming: exports_external.boolean()
8512
8542
  });
8513
8543
  AgentsUiInterruptResponseSchema = exports_external.object({
8514
8544
  conversationId: exports_external.string(),
8515
8545
  turnId: exports_external.string(),
8516
- interrupted: exports_external.literal(true)
8546
+ interrupted: exports_external.literal(true),
8547
+ streaming: exports_external.boolean()
8517
8548
  });
8518
8549
  AgentsUiConversationMessageDeltaEventSchema = exports_external.object({
8519
8550
  type: exports_external.literal("messageDelta"),
@@ -8594,6 +8625,13 @@ var init_schemas = __esm(() => {
8594
8625
  WorktreeNameParamsSchema = exports_external.object({
8595
8626
  name: exports_external.string()
8596
8627
  });
8628
+ WorktreeTabParamsSchema = exports_external.object({
8629
+ name: exports_external.string(),
8630
+ tabId: exports_external.string()
8631
+ });
8632
+ CreateTabResponseSchema = exports_external.object({
8633
+ tab: WorktreeTabSchema
8634
+ });
8597
8635
  NotificationIdParamsSchema = exports_external.object({
8598
8636
  id: NumberLikePathParamSchema
8599
8637
  });
@@ -8646,6 +8684,9 @@ var init_contract = __esm(() => {
8646
8684
  postWorktreeToLinear: "/api/worktrees/:name/linear/post",
8647
8685
  setWorktreeLabel: "/api/worktrees/:name/label",
8648
8686
  sendWorktreePrompt: "/api/worktrees/:name/send",
8687
+ createWorktreeTab: "/api/worktrees/:name/tabs",
8688
+ selectWorktreeTab: "/api/worktrees/:name/tabs/:tabId/select",
8689
+ deleteWorktreeTab: "/api/worktrees/:name/tabs/:tabId",
8649
8690
  mergeWorktree: "/api/worktrees/:name/merge",
8650
8691
  fetchWorktreeDiff: "/api/worktrees/:name/diff",
8651
8692
  fetchLinearIssues: "/api/linear/issues",
@@ -8900,6 +8941,35 @@ var init_contract = __esm(() => {
8900
8941
  ...commonErrorResponses
8901
8942
  }
8902
8943
  },
8944
+ createWorktreeTab: {
8945
+ method: "POST",
8946
+ path: apiPaths.createWorktreeTab,
8947
+ pathParams: WorktreeNameParamsSchema,
8948
+ body: c3.noBody(),
8949
+ responses: {
8950
+ 201: CreateTabResponseSchema,
8951
+ ...commonErrorResponses
8952
+ }
8953
+ },
8954
+ selectWorktreeTab: {
8955
+ method: "POST",
8956
+ path: apiPaths.selectWorktreeTab,
8957
+ pathParams: WorktreeTabParamsSchema,
8958
+ body: c3.noBody(),
8959
+ responses: {
8960
+ 200: OkResponseSchema,
8961
+ ...commonErrorResponses
8962
+ }
8963
+ },
8964
+ deleteWorktreeTab: {
8965
+ method: "DELETE",
8966
+ path: apiPaths.deleteWorktreeTab,
8967
+ pathParams: WorktreeTabParamsSchema,
8968
+ responses: {
8969
+ 200: OkResponseSchema,
8970
+ ...commonErrorResponses
8971
+ }
8972
+ },
8903
8973
  mergeWorktree: {
8904
8974
  method: "POST",
8905
8975
  path: apiPaths.mergeWorktree,
@@ -10156,6 +10226,18 @@ function summarizeToolInput(toolName, jsonText) {
10156
10226
  }
10157
10227
  if (!input)
10158
10228
  return truncateInline(jsonText, 100);
10229
+ if (toolName.toLowerCase() === "askuserquestion" && Array.isArray(input.questions)) {
10230
+ const headers = [];
10231
+ for (const question of input.questions) {
10232
+ if (question && typeof question === "object" && !Array.isArray(question)) {
10233
+ const header = question.header;
10234
+ if (typeof header === "string")
10235
+ headers.push(header);
10236
+ }
10237
+ }
10238
+ if (headers.length > 0)
10239
+ return truncateInline(headers.join(", "), 120);
10240
+ }
10159
10241
  const keys = TOOL_PRIMARY_KEY[toolName.toLowerCase()];
10160
10242
  if (keys) {
10161
10243
  const values = [];
@@ -10201,14 +10283,30 @@ function flushStreamingLine(state) {
10201
10283
  process.stdout.write(`
10202
10284
  `);
10203
10285
  state.streamingItemId = null;
10286
+ state.streamingText = "";
10287
+ state.streamingTurnId = null;
10204
10288
  state.streamingNeedsHeader = false;
10205
10289
  }
10206
10290
  }
10291
+ function messageKind(message) {
10292
+ return message.kind ?? "text";
10293
+ }
10294
+ function isSameStreamingMessage(state, message) {
10295
+ if (state.streamingItemId === null)
10296
+ return false;
10297
+ if (state.streamingItemId === message.id)
10298
+ return true;
10299
+ if (message.role !== "assistant" || messageKind(message) !== "text")
10300
+ return false;
10301
+ if (state.streamingText.length === 0)
10302
+ return false;
10303
+ return message.turnId === state.streamingTurnId && (state.streamingText.startsWith(message.text) || message.text.startsWith(state.streamingText));
10304
+ }
10207
10305
  function printNewMessages(state, messages) {
10208
10306
  for (const message of messages) {
10209
10307
  if (state.printedMessageIds.has(message.id))
10210
10308
  continue;
10211
- if (state.streamingItemId === message.id) {
10309
+ if (isSameStreamingMessage(state, message)) {
10212
10310
  state.printedMessageIds.add(message.id);
10213
10311
  if (message.status === "completed")
10214
10312
  flushStreamingLine(state);
@@ -10225,13 +10323,6 @@ function printNewMessages(state, messages) {
10225
10323
  }
10226
10324
  }
10227
10325
  function handleConversationEvent(event, state, stderr) {
10228
- if (event.type === "snapshot") {
10229
- if (event.revision <= state.lastStreamRevision)
10230
- return;
10231
- state.lastStreamRevision = event.revision;
10232
- printNewMessages(state, event.data.conversation.messages);
10233
- return;
10234
- }
10235
10326
  if (event.type === "messageDelta") {
10236
10327
  if (event.revision <= state.lastStreamRevision)
10237
10328
  return;
@@ -10239,6 +10330,8 @@ function handleConversationEvent(event, state, stderr) {
10239
10330
  if (state.streamingItemId !== event.itemId) {
10240
10331
  flushStreamingLine(state);
10241
10332
  state.streamingItemId = event.itemId;
10333
+ state.streamingText = "";
10334
+ state.streamingTurnId = event.turnId;
10242
10335
  state.streamingNeedsHeader = true;
10243
10336
  }
10244
10337
  if (state.streamingNeedsHeader) {
@@ -10246,6 +10339,7 @@ function handleConversationEvent(event, state, stderr) {
10246
10339
  state.streamingNeedsHeader = false;
10247
10340
  }
10248
10341
  process.stdout.write(event.delta);
10342
+ state.streamingText += event.delta;
10249
10343
  return;
10250
10344
  }
10251
10345
  if (event.type === "messageUpsert") {
@@ -10255,6 +10349,14 @@ function handleConversationEvent(event, state, stderr) {
10255
10349
  printNewMessages(state, [event.message]);
10256
10350
  return;
10257
10351
  }
10352
+ if (event.type === "conversationStatus") {
10353
+ if (event.revision <= state.lastStreamRevision)
10354
+ return;
10355
+ state.lastStreamRevision = event.revision;
10356
+ if (!event.running)
10357
+ flushStreamingLine(state);
10358
+ return;
10359
+ }
10258
10360
  if (event.type === "error") {
10259
10361
  flushStreamingLine(state);
10260
10362
  stderr(`[${timestamp()}] [error] ${event.message}`);
@@ -10632,6 +10734,8 @@ async function runOneshot(parsed, port) {
10632
10734
  const conversationState = {
10633
10735
  printedMessageIds: new Set,
10634
10736
  streamingItemId: null,
10737
+ streamingText: "",
10738
+ streamingTurnId: null,
10635
10739
  streamingNeedsHeader: false,
10636
10740
  lastStreamRevision: 0
10637
10741
  };
@@ -10888,7 +10992,12 @@ var init_linear_commands = __esm(() => {
10888
10992
  });
10889
10993
 
10890
10994
  // backend/src/domain/model.ts
10891
- var WORKTREE_META_SCHEMA_VERSION = 1, WORKTREE_ARCHIVE_STATE_VERSION = 1;
10995
+ function conversationSessionId(conversation) {
10996
+ if (!conversation)
10997
+ return null;
10998
+ return conversation.provider === "codexAppServer" ? conversation.threadId : conversation.sessionId;
10999
+ }
11000
+ var WORKTREE_META_SCHEMA_VERSION = 1, WORKTREE_ARCHIVE_STATE_VERSION = 1, ROOT_TAB_ID = "root";
10892
11001
 
10893
11002
  // backend/src/adapters/fs.ts
10894
11003
  import { mkdir } from "fs/promises";
@@ -11064,10 +11173,28 @@ function normalizeConversationMeta(raw) {
11064
11173
  function normalizeOptionalString(raw) {
11065
11174
  return typeof raw === "string" && raw.trim() ? raw.trim() : undefined;
11066
11175
  }
11176
+ function backfillTabs(meta) {
11177
+ if (meta.tabs && meta.tabs.length > 0)
11178
+ return null;
11179
+ const rootTab = {
11180
+ tabId: ROOT_TAB_ID,
11181
+ kind: "root",
11182
+ label: "Root",
11183
+ seq: null,
11184
+ sessionId: conversationSessionId(meta.conversation),
11185
+ createdAt: meta.createdAt
11186
+ };
11187
+ return {
11188
+ tabs: [rootTab],
11189
+ activeTabId: ROOT_TAB_ID,
11190
+ forkCounter: meta.forkCounter ?? 0
11191
+ };
11192
+ }
11067
11193
  function normalizeWorktreeMeta(meta) {
11068
11194
  const conversation = normalizeConversationMeta(meta.conversation);
11069
11195
  const normalizedLabel = normalizeOptionalString(meta.label);
11070
- if (conversation === meta.conversation && normalizedLabel === meta.label) {
11196
+ const tabBackfill = backfillTabs(meta);
11197
+ if (conversation === meta.conversation && normalizedLabel === meta.label && tabBackfill === null) {
11071
11198
  return meta;
11072
11199
  }
11073
11200
  const rest = { ...meta };
@@ -11076,7 +11203,8 @@ function normalizeWorktreeMeta(meta) {
11076
11203
  return {
11077
11204
  ...rest,
11078
11205
  ...normalizedLabel ? { label: normalizedLabel } : {},
11079
- ...conversation !== undefined ? { conversation } : {}
11206
+ ...conversation !== undefined ? { conversation } : {},
11207
+ ...tabBackfill ?? {}
11080
11208
  };
11081
11209
  }
11082
11210
  function isPrComment(raw) {
@@ -11147,6 +11275,9 @@ function buildProjectSessionName(projectRoot) {
11147
11275
  function buildWorktreeWindowName(branch) {
11148
11276
  return `wm-${branch}`;
11149
11277
  }
11278
+ function buildWorktreeParkingWindowName(branch) {
11279
+ return `wm-${branch}-tabs`;
11280
+ }
11150
11281
  function parseWindowSummaries(output) {
11151
11282
  return output.split(`
11152
11283
  `).map((line) => line.trim()).filter(Boolean).map((line) => {
@@ -11212,6 +11343,24 @@ class BunTmuxGateway {
11212
11343
  const output = assertTmuxOk(["list-windows", "-a", "-F", "#{session_name}\t#{window_name}\t#{window_panes}"], "list tmux windows");
11213
11344
  return parseWindowSummaries(output);
11214
11345
  }
11346
+ getPaneId(target) {
11347
+ return assertTmuxOk(["display-message", "-p", "-t", target, "#{pane_id}"], `resolve tmux pane id for ${target}`);
11348
+ }
11349
+ createParkedPane(opts) {
11350
+ if (!this.hasWindow(opts.sessionName, opts.parkingWindow)) {
11351
+ return assertTmuxOk(["new-window", "-d", "-P", "-F", "#{pane_id}", "-t", opts.sessionName, "-n", opts.parkingWindow, "-c", opts.cwd, opts.command], `create parking window ${opts.sessionName}:${opts.parkingWindow}`);
11352
+ }
11353
+ return assertTmuxOk(["split-window", "-d", "-P", "-F", "#{pane_id}", "-t", `${opts.sessionName}:${opts.parkingWindow}`, "-c", opts.cwd, opts.command], `create parked pane in ${opts.sessionName}:${opts.parkingWindow}`);
11354
+ }
11355
+ swapPanes(source, destination) {
11356
+ assertTmuxOk(["swap-pane", "-s", source, "-t", destination], `swap tmux panes ${source} <-> ${destination}`);
11357
+ }
11358
+ killPane(target) {
11359
+ const result = runTmux(["kill-pane", "-t", target]);
11360
+ if (result.exitCode !== 0 && !result.stderr.includes("can't find pane") && !isIgnorableKillWindowError(result.stderr)) {
11361
+ throw new Error(`kill tmux pane ${target} failed: ${result.stderr}`);
11362
+ }
11363
+ }
11215
11364
  }
11216
11365
  var init_tmux = () => {};
11217
11366
 
@@ -19093,6 +19242,97 @@ class BunPortProbe {
19093
19242
  }
19094
19243
  }
19095
19244
 
19245
+ // backend/src/lib/type-guards.ts
19246
+ function isRecord5(raw) {
19247
+ return typeof raw === "object" && raw !== null && !Array.isArray(raw);
19248
+ }
19249
+
19250
+ // backend/src/adapters/claude-cli.ts
19251
+ function encodeClaudeProjectDir(cwd) {
19252
+ return cwd.replace(/[^A-Za-z0-9]/g, "-");
19253
+ }
19254
+ var init_claude_cli = __esm(() => {
19255
+ init_log();
19256
+ });
19257
+
19258
+ // backend/src/adapters/session-discovery.ts
19259
+ import { readdir, stat as stat2 } from "fs/promises";
19260
+ import { basename as basename5, join as join12 } from "path";
19261
+ function home() {
19262
+ const value = Bun.env.HOME;
19263
+ if (!value)
19264
+ throw new Error("HOME is required to resolve agent sessions");
19265
+ return value;
19266
+ }
19267
+ function newestFirst(sessions) {
19268
+ return sessions.sort((left, right) => right.mtimeMs - left.mtimeMs).map((entry) => entry.sessionId);
19269
+ }
19270
+ async function listClaudeSessionIds(cwd) {
19271
+ const dir = join12(home(), ".claude", "projects", encodeClaudeProjectDir(cwd));
19272
+ const names = await readdir(dir).catch(() => []);
19273
+ const stamped = await Promise.all(names.filter((name) => name.endsWith(".jsonl")).map(async (name) => {
19274
+ const info = await stat2(join12(dir, name)).catch(() => null);
19275
+ return info ? { sessionId: basename5(name, ".jsonl"), mtimeMs: info.mtimeMs } : null;
19276
+ }));
19277
+ return newestFirst(stamped.filter((entry) => entry !== null));
19278
+ }
19279
+ async function readCodexSessionCwdId(path) {
19280
+ try {
19281
+ const head = await Bun.file(path).slice(0, 16384).text();
19282
+ const firstLine = head.split(`
19283
+ `, 1)[0];
19284
+ if (!firstLine)
19285
+ return null;
19286
+ const parsed = JSON.parse(firstLine);
19287
+ if (!isRecord5(parsed) || parsed.type !== "session_meta" || !isRecord5(parsed.payload))
19288
+ return null;
19289
+ const { id, cwd } = parsed.payload;
19290
+ return typeof id === "string" && typeof cwd === "string" ? { id, cwd } : null;
19291
+ } catch {
19292
+ return null;
19293
+ }
19294
+ }
19295
+ async function listCodexSessionIds(cwd) {
19296
+ const root = join12(home(), ".codex", "sessions");
19297
+ const relPaths = await readdir(root, { recursive: true }).catch(() => []);
19298
+ const rollouts = relPaths.filter((rel) => {
19299
+ const name = basename5(rel);
19300
+ return name.startsWith("rollout-") && name.endsWith(".jsonl");
19301
+ });
19302
+ const stamped = await Promise.all(rollouts.map(async (rel) => {
19303
+ const path = join12(root, rel);
19304
+ const meta = await readCodexSessionCwdId(path);
19305
+ if (!meta || meta.cwd !== cwd)
19306
+ return null;
19307
+ const info = await stat2(path).catch(() => null);
19308
+ return info ? { sessionId: meta.id, mtimeMs: info.mtimeMs } : null;
19309
+ }));
19310
+ return newestFirst(stamped.filter((entry) => entry !== null));
19311
+ }
19312
+
19313
+ class FileSessionDiscovery {
19314
+ async listSessionIds(agent, cwd) {
19315
+ return agent === "claude" ? await listClaudeSessionIds(cwd) : await listCodexSessionIds(cwd);
19316
+ }
19317
+ }
19318
+ async function captureNewSessionId(discovery, agent, cwd, before, options = {}) {
19319
+ const beforeSet = new Set(before);
19320
+ const attempts = options.attempts ?? 20;
19321
+ const delayMs = options.delayMs ?? 150;
19322
+ const sleep = options.sleep ?? ((ms) => Bun.sleep(ms));
19323
+ for (let attempt = 0;attempt < attempts; attempt += 1) {
19324
+ const after = await discovery.listSessionIds(agent, cwd);
19325
+ const fresh = after.filter((id) => !beforeSet.has(id));
19326
+ if (fresh.length > 0)
19327
+ return fresh[0];
19328
+ await sleep(delayMs);
19329
+ }
19330
+ return null;
19331
+ }
19332
+ var init_session_discovery = __esm(() => {
19333
+ init_claude_cli();
19334
+ });
19335
+
19096
19336
  // backend/src/lib/branch-name.ts
19097
19337
  import { randomUUID } from "crypto";
19098
19338
  function generateFallbackBranchName() {
@@ -19250,11 +19490,11 @@ var init_archive_state_service = __esm(() => {
19250
19490
 
19251
19491
  // backend/src/adapters/agent-runtime.ts
19252
19492
  import { chmod as chmod2, mkdir as mkdir3 } from "fs/promises";
19253
- import { dirname as dirname4, join as join12, resolve as resolve6 } from "path";
19493
+ import { dirname as dirname4, join as join13, resolve as resolve6 } from "path";
19254
19494
  function shellQuote(value) {
19255
19495
  return `'${value.replaceAll("'", "'\\''")}'`;
19256
19496
  }
19257
- function isRecord5(value) {
19497
+ function isRecord6(value) {
19258
19498
  return typeof value === "object" && value !== null && !Array.isArray(value);
19259
19499
  }
19260
19500
  function buildAgentCtlScript() {
@@ -19640,9 +19880,9 @@ function commandStartsWithAgentCtl(command, agentCtlPath) {
19640
19880
  return trimmedCommand === agentCtlPath || trimmedCommand.startsWith(`${agentCtlPath} `) || trimmedCommand === quotedAgentCtlPath || trimmedCommand.startsWith(`${quotedAgentCtlPath} `);
19641
19881
  }
19642
19882
  function isWebmuxHookGroup(group, agentCtlPath) {
19643
- if (!isRecord5(group) || !Array.isArray(group.hooks))
19883
+ if (!isRecord6(group) || !Array.isArray(group.hooks))
19644
19884
  return false;
19645
- return group.hooks.some((hook) => isRecord5(hook) && typeof hook.command === "string" && commandStartsWithAgentCtl(hook.command, agentCtlPath));
19885
+ return group.hooks.some((hook) => isRecord6(hook) && typeof hook.command === "string" && commandStartsWithAgentCtl(hook.command, agentCtlPath));
19646
19886
  }
19647
19887
  async function mergeCodexHooksFile(hooksPath, hookSettings, agentCtlPath) {
19648
19888
  let existing = {};
@@ -19657,7 +19897,7 @@ async function mergeCodexHooksFile(hooksPath, hookSettings, agentCtlPath) {
19657
19897
  } catch {
19658
19898
  existing = {};
19659
19899
  }
19660
- const existingHooks = isRecord5(existing.hooks) ? existing.hooks : {};
19900
+ const existingHooks = isRecord6(existing.hooks) ? existing.hooks : {};
19661
19901
  const mergedHooks = { ...existingHooks };
19662
19902
  for (const [eventName, groups] of Object.entries(hookSettings)) {
19663
19903
  const eventGroups = existingHooks[eventName];
@@ -19669,7 +19909,7 @@ async function mergeCodexHooksFile(hooksPath, hookSettings, agentCtlPath) {
19669
19909
  }
19670
19910
  async function resolveGitCommonDir(gitDir) {
19671
19911
  try {
19672
- const commonDir = (await Bun.file(join12(gitDir, "commondir")).text()).trim();
19912
+ const commonDir = (await Bun.file(join13(gitDir, "commondir")).text()).trim();
19673
19913
  if (!commonDir)
19674
19914
  return gitDir;
19675
19915
  return commonDir.startsWith("/") ? commonDir : resolve6(gitDir, commonDir);
@@ -19679,7 +19919,7 @@ async function resolveGitCommonDir(gitDir) {
19679
19919
  }
19680
19920
  async function ensureGeneratedCodexHooksIgnored(gitDir) {
19681
19921
  const commonDir = await resolveGitCommonDir(gitDir);
19682
- const excludePath = join12(commonDir, "info", "exclude");
19922
+ const excludePath = join13(commonDir, "info", "exclude");
19683
19923
  let existing = "";
19684
19924
  try {
19685
19925
  existing = await Bun.file(excludePath).text();
@@ -19699,9 +19939,9 @@ async function ensureGeneratedCodexHooksIgnored(gitDir) {
19699
19939
  async function ensureAgentRuntimeArtifacts(input) {
19700
19940
  const storagePaths = getWorktreeStoragePaths(input.gitDir);
19701
19941
  const artifacts = {
19702
- agentCtlPath: join12(storagePaths.webmuxDir, "webmux-agentctl"),
19703
- claudeSettingsPath: join12(input.worktreePath, ".claude", "settings.local.json"),
19704
- codexHooksPath: join12(input.worktreePath, ".codex", "hooks.json")
19942
+ agentCtlPath: join13(storagePaths.webmuxDir, "webmux-agentctl"),
19943
+ claudeSettingsPath: join13(input.worktreePath, ".claude", "settings.local.json"),
19944
+ codexHooksPath: join13(input.worktreePath, ".codex", "hooks.json")
19705
19945
  };
19706
19946
  await mkdir3(dirname4(artifacts.claudeSettingsPath), { recursive: true });
19707
19947
  await mkdir3(dirname4(artifacts.codexHooksPath), { recursive: true });
@@ -19709,7 +19949,7 @@ async function ensureAgentRuntimeArtifacts(input) {
19709
19949
  await chmod2(artifacts.agentCtlPath, 493);
19710
19950
  const hookSettings = buildClaudeHookSettings(artifacts);
19711
19951
  const hooks = hookSettings.hooks;
19712
- if (!isRecord5(hooks)) {
19952
+ if (!isRecord6(hooks)) {
19713
19953
  throw new Error("Invalid Claude hook settings");
19714
19954
  }
19715
19955
  await mergeClaudeSettings(artifacts.claudeSettingsPath, hooks);
@@ -19722,6 +19962,63 @@ var init_agent_runtime = __esm(() => {
19722
19962
  init_fs();
19723
19963
  });
19724
19964
 
19965
+ // backend/src/services/tab-logic.ts
19966
+ function listTabs(meta) {
19967
+ return meta.tabs ?? [];
19968
+ }
19969
+ function findTab(meta, tabId) {
19970
+ return listTabs(meta).find((tab) => tab.tabId === tabId);
19971
+ }
19972
+ function rootTab(meta) {
19973
+ const tabs = listTabs(meta);
19974
+ return tabs.find((tab) => tab.kind === "root") ?? tabs[0];
19975
+ }
19976
+ function activeTabId(meta) {
19977
+ return meta.activeTabId ?? ROOT_TAB_ID;
19978
+ }
19979
+ function nextForkSeq(meta) {
19980
+ return (meta.forkCounter ?? 0) + 1;
19981
+ }
19982
+ function buildForkTab(input) {
19983
+ return {
19984
+ tabId: `fork-${input.seq}`,
19985
+ kind: "fork",
19986
+ label: `Fork ${input.seq}`,
19987
+ seq: input.seq,
19988
+ sessionId: input.sessionId,
19989
+ ...input.paneId ? { paneId: input.paneId } : {},
19990
+ createdAt: input.createdAt
19991
+ };
19992
+ }
19993
+ function appendTab(meta, tab) {
19994
+ return {
19995
+ ...meta,
19996
+ tabs: [...listTabs(meta), tab],
19997
+ forkCounter: tab.seq ?? meta.forkCounter ?? 0,
19998
+ activeTabId: tab.tabId
19999
+ };
20000
+ }
20001
+ function removeTab(meta, tabId) {
20002
+ return {
20003
+ ...meta,
20004
+ tabs: listTabs(meta).filter((tab) => tab.tabId !== tabId),
20005
+ activeTabId: activeTabId(meta) === tabId ? ROOT_TAB_ID : meta.activeTabId
20006
+ };
20007
+ }
20008
+ function updateTab(meta, tabId, patch) {
20009
+ return {
20010
+ ...meta,
20011
+ tabs: listTabs(meta).map((tab) => tab.tabId === tabId ? { ...tab, ...patch } : tab)
20012
+ };
20013
+ }
20014
+ function setActiveTab(meta, tabId) {
20015
+ return { ...meta, activeTabId: tabId };
20016
+ }
20017
+ function withTabs(meta, tabs) {
20018
+ return { ...meta, tabs };
20019
+ }
20020
+ var init_tab_logic = () => {};
20021
+
19725
20022
  // backend/src/services/agent-service.ts
19726
20023
  function quoteShell(value) {
19727
20024
  return `'${value.replaceAll("'", "'\\''")}'`;
@@ -19737,6 +20034,9 @@ function buildBuiltInAgentInvocation(input) {
19737
20034
  if (input.agent === "codex") {
19738
20035
  const hooksFlag = " --enable hooks";
19739
20036
  const yoloFlag2 = input.yolo ? " --yolo" : "";
20037
+ if (input.launchMode === "fork" && input.forkFromSessionId) {
20038
+ return `codex${hooksFlag}${yoloFlag2} fork ${quoteShell(input.forkFromSessionId)}${promptSuffix}`;
20039
+ }
19740
20040
  if (input.launchMode === "resume") {
19741
20041
  const resumeTarget = input.resumeConversationId ? ` ${quoteShell(input.resumeConversationId)}` : " --last";
19742
20042
  return `codex${hooksFlag}${yoloFlag2} resume${resumeTarget}${promptSuffix}`;
@@ -19747,8 +20047,13 @@ function buildBuiltInAgentInvocation(input) {
19747
20047
  return `codex${hooksFlag}${yoloFlag2}${promptSuffix}`;
19748
20048
  }
19749
20049
  const yoloFlag = input.yolo ? " --dangerously-skip-permissions" : "";
20050
+ if (input.launchMode === "fork" && input.forkFromSessionId) {
20051
+ const pin = input.pinSessionId ? ` --session-id ${quoteShell(input.pinSessionId)}` : "";
20052
+ return `claude${yoloFlag} --resume ${quoteShell(input.forkFromSessionId)} --fork-session${pin}${promptSuffix}`;
20053
+ }
19750
20054
  if (input.launchMode === "resume") {
19751
- return `claude${yoloFlag} --continue${promptSuffix}`;
20055
+ const resumeTarget = input.resumeConversationId ? ` --resume ${quoteShell(input.resumeConversationId)}` : " --continue";
20056
+ return `claude${yoloFlag}${resumeTarget}${promptSuffix}`;
19752
20057
  }
19753
20058
  if (input.systemPrompt) {
19754
20059
  return `claude${yoloFlag} --append-system-prompt ${quoteShell(input.systemPrompt)}${promptSuffix}`;
@@ -19783,7 +20088,9 @@ function buildAgentInvocation(input) {
19783
20088
  systemPrompt: input.systemPrompt,
19784
20089
  prompt: input.prompt,
19785
20090
  launchMode: input.launchMode,
19786
- resumeConversationId: input.resumeConversationId
20091
+ resumeConversationId: input.resumeConversationId,
20092
+ forkFromSessionId: input.forkFromSessionId,
20093
+ pinSessionId: input.pinSessionId
19787
20094
  });
19788
20095
  }
19789
20096
  return buildCustomAgentInvocation({
@@ -20167,6 +20474,7 @@ var init_worktree_service = __esm(() => {
20167
20474
  });
20168
20475
 
20169
20476
  // backend/src/services/lifecycle-service.ts
20477
+ import { randomUUID as randomUUID3 } from "crypto";
20170
20478
  import { mkdir as mkdir4 } from "fs/promises";
20171
20479
  import { dirname as dirname5, resolve as resolve8 } from "path";
20172
20480
  function toErrorMessage2(error) {
@@ -20310,6 +20618,15 @@ class LifecycleService {
20310
20618
  agentTerminalStale: false
20311
20619
  });
20312
20620
  }
20621
+ await this.restoreWorktreeTabs({
20622
+ branch,
20623
+ gitDir: resolved.gitDir,
20624
+ worktreePath: resolved.entry.path,
20625
+ profile,
20626
+ profileName,
20627
+ agent,
20628
+ runtimeEnvPath: initialized.paths.runtimeEnvPath
20629
+ });
20313
20630
  await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20314
20631
  return {
20315
20632
  branch,
@@ -20328,12 +20645,24 @@ class LifecycleService {
20328
20645
  const initialized = await this.refreshManagedArtifacts(resolved);
20329
20646
  const { profileName, profile } = this.resolveProfile(initialized.meta.profile);
20330
20647
  const agent = this.resolveAgentDefinition(initialized.meta.agent);
20331
- if (agent.kind !== "builtin" || agent.implementation.agent !== "codex") {
20332
- throw new LifecycleError("Refreshing the agent terminal is only available for Codex worktrees", 409);
20648
+ if (agent.kind !== "builtin" || agent.implementation.agent !== "codex" && agent.implementation.agent !== "claude") {
20649
+ throw new LifecycleError("Refreshing the agent terminal is only available for built-in agent worktrees", 409);
20333
20650
  }
20334
20651
  const conversation = initialized.meta.conversation;
20335
- if (conversation?.provider !== "codexAppServer") {
20336
- throw new LifecycleError("No Codex conversation is available to refresh", 409);
20652
+ if (!conversation) {
20653
+ throw new LifecycleError(`No ${agent.label} conversation is available to refresh`, 409);
20654
+ }
20655
+ let resumeConversationId;
20656
+ if (agent.implementation.agent === "codex") {
20657
+ if (conversation.provider !== "codexAppServer") {
20658
+ throw new LifecycleError(`No ${agent.label} conversation is available to refresh`, 409);
20659
+ }
20660
+ resumeConversationId = conversation.threadId;
20661
+ } else {
20662
+ if (conversation.provider !== "claudeCode") {
20663
+ throw new LifecycleError(`No ${agent.label} conversation is available to refresh`, 409);
20664
+ }
20665
+ resumeConversationId = conversation.sessionId;
20337
20666
  }
20338
20667
  await ensureAgentRuntimeArtifacts({
20339
20668
  gitDir: initialized.paths.gitDir,
@@ -20347,12 +20676,21 @@ class LifecycleService {
20347
20676
  initialized,
20348
20677
  worktreePath: resolved.entry.path,
20349
20678
  launchMode: "resume",
20350
- resumeConversationId: conversation.threadId
20679
+ resumeConversationId
20351
20680
  });
20352
20681
  await writeWorktreeMeta(resolved.gitDir, {
20353
20682
  ...initialized.meta,
20354
20683
  agentTerminalStale: false
20355
20684
  });
20685
+ await this.restoreWorktreeTabs({
20686
+ branch,
20687
+ gitDir: resolved.gitDir,
20688
+ worktreePath: resolved.entry.path,
20689
+ profile,
20690
+ profileName,
20691
+ agent,
20692
+ runtimeEnvPath: initialized.paths.runtimeEnvPath
20693
+ });
20356
20694
  await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20357
20695
  return {
20358
20696
  branch,
@@ -20362,6 +20700,193 @@ class LifecycleService {
20362
20700
  throw this.wrapOperationError(error);
20363
20701
  }
20364
20702
  }
20703
+ async createWorktreeTab(branch) {
20704
+ try {
20705
+ const ctx = await this.prepareTabContext(branch);
20706
+ const rootSessionId = await this.ensureRootSessionId(ctx);
20707
+ if (!rootSessionId) {
20708
+ throw new LifecycleError("The root session hasn't started yet \u2014 interact with it once before forking a tab", 409);
20709
+ }
20710
+ const meta = await this.readMetaOrThrow(ctx.resolved.gitDir);
20711
+ const seq = nextForkSeq(meta);
20712
+ const pinSessionId = ctx.agentKind === "claude" ? randomUUID3() : undefined;
20713
+ const agentCommand = buildAgentPaneCommand({
20714
+ agent: ctx.agent,
20715
+ runtimeEnvPath: ctx.initialized.paths.runtimeEnvPath,
20716
+ repoRoot: this.deps.projectRoot,
20717
+ worktreePath: ctx.worktreePath,
20718
+ branch,
20719
+ profileName: ctx.profileName,
20720
+ yolo: ctx.profile.yolo === true,
20721
+ launchMode: "fork",
20722
+ forkFromSessionId: rootSessionId,
20723
+ pinSessionId
20724
+ });
20725
+ const visibleSlot = `${ctx.sessionName}:${ctx.windowName}.0`;
20726
+ const outgoingActiveId = activeTabId(meta);
20727
+ const outgoingPaneId = this.deps.tmux.getPaneId(visibleSlot);
20728
+ const before = await this.deps.sessionDiscovery.listSessionIds(ctx.agentKind, ctx.worktreePath);
20729
+ const paneId = this.deps.tmux.createParkedPane({
20730
+ sessionName: ctx.sessionName,
20731
+ parkingWindow: ctx.parkingWindow,
20732
+ cwd: ctx.worktreePath,
20733
+ command: buildManagedShellCommand(ctx.initialized.paths.runtimeEnvPath)
20734
+ });
20735
+ this.deps.tmux.runCommand(paneId, agentCommand);
20736
+ const sessionId = pinSessionId ?? await captureNewSessionId(this.deps.sessionDiscovery, ctx.agentKind, ctx.worktreePath, before);
20737
+ const tab = buildForkTab({ seq, sessionId, paneId, createdAt: new Date().toISOString() });
20738
+ let nextMeta = appendTab(meta, tab);
20739
+ nextMeta = updateTab(nextMeta, outgoingActiveId, { paneId: outgoingPaneId });
20740
+ await writeWorktreeMeta(ctx.resolved.gitDir, nextMeta);
20741
+ this.deps.tmux.swapPanes(paneId, visibleSlot);
20742
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20743
+ return { tab };
20744
+ } catch (error) {
20745
+ throw this.wrapOperationError(error);
20746
+ }
20747
+ }
20748
+ async selectWorktreeTab(branch, tabId) {
20749
+ try {
20750
+ const ctx = await this.prepareTabContext(branch);
20751
+ const target = findTab(ctx.meta, tabId);
20752
+ if (!target)
20753
+ throw new LifecycleError(`Tab not found: ${tabId}`, 404);
20754
+ const outgoingActiveId = activeTabId(ctx.meta);
20755
+ if (outgoingActiveId === tabId)
20756
+ return;
20757
+ if (!target.paneId)
20758
+ throw new LifecycleError(`Tab ${tabId} has no live pane to show`, 409);
20759
+ const visibleSlot = `${ctx.sessionName}:${ctx.windowName}.0`;
20760
+ const outgoingPaneId = this.deps.tmux.getPaneId(visibleSlot);
20761
+ this.deps.tmux.swapPanes(target.paneId, visibleSlot);
20762
+ let nextMeta = updateTab(ctx.meta, outgoingActiveId, { paneId: outgoingPaneId });
20763
+ nextMeta = setActiveTab(nextMeta, tabId);
20764
+ await writeWorktreeMeta(ctx.resolved.gitDir, nextMeta);
20765
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20766
+ } catch (error) {
20767
+ throw this.wrapOperationError(error);
20768
+ }
20769
+ }
20770
+ async deleteWorktreeTab(branch, tabId) {
20771
+ try {
20772
+ const ctx = await this.prepareTabContext(branch);
20773
+ const target = findTab(ctx.meta, tabId);
20774
+ if (!target)
20775
+ throw new LifecycleError(`Tab not found: ${tabId}`, 404);
20776
+ if (target.kind === "root")
20777
+ throw new LifecycleError("The root tab cannot be deleted", 400);
20778
+ const root = rootTab(ctx.meta);
20779
+ if (activeTabId(ctx.meta) === tabId && root?.paneId) {
20780
+ this.deps.tmux.swapPanes(root.paneId, `${ctx.sessionName}:${ctx.windowName}.0`);
20781
+ }
20782
+ if (target.paneId)
20783
+ this.deps.tmux.killPane(target.paneId);
20784
+ await writeWorktreeMeta(ctx.resolved.gitDir, removeTab(ctx.meta, tabId));
20785
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20786
+ } catch (error) {
20787
+ throw this.wrapOperationError(error);
20788
+ }
20789
+ }
20790
+ async readMetaOrThrow(gitDir) {
20791
+ const meta = await readWorktreeMeta(gitDir);
20792
+ if (!meta)
20793
+ throw new LifecycleError("Worktree metadata is missing", 409);
20794
+ return meta;
20795
+ }
20796
+ async prepareTabContext(branch) {
20797
+ const resolved = await this.resolveExistingWorktree(branch);
20798
+ if (!resolved.meta)
20799
+ throw new LifecycleError(`Worktree ${branch} has no managed metadata`, 409);
20800
+ const sessionName = buildProjectSessionName(this.deps.projectRoot);
20801
+ const windowName = buildWorktreeWindowName(branch);
20802
+ if (!this.deps.tmux.hasWindow(sessionName, windowName)) {
20803
+ throw new LifecycleError(`Worktree ${branch} is not open`, 409);
20804
+ }
20805
+ const { profileName, profile } = this.resolveProfile(resolved.meta.profile);
20806
+ if (profile.runtime === "docker") {
20807
+ throw new LifecycleError("Tabs are not supported for Docker worktrees", 409);
20808
+ }
20809
+ const agent = this.resolveAgentDefinition(resolved.meta.agent);
20810
+ if (agent.kind !== "builtin") {
20811
+ throw new LifecycleError("Tabs are only available for the built-in Claude and Codex agents", 409);
20812
+ }
20813
+ const initialized = await this.refreshManagedArtifacts(resolved);
20814
+ return {
20815
+ resolved,
20816
+ initialized,
20817
+ meta: initialized.meta,
20818
+ worktreePath: resolved.entry.path,
20819
+ agent,
20820
+ agentKind: agent.implementation.agent,
20821
+ profile,
20822
+ profileName,
20823
+ sessionName,
20824
+ windowName,
20825
+ parkingWindow: buildWorktreeParkingWindowName(branch)
20826
+ };
20827
+ }
20828
+ async ensureRootSessionId(ctx) {
20829
+ const root = rootTab(ctx.meta);
20830
+ if (root?.sessionId)
20831
+ return root.sessionId;
20832
+ const discovered = (await this.deps.sessionDiscovery.listSessionIds(ctx.agentKind, ctx.worktreePath))[0] ?? null;
20833
+ if (discovered && root) {
20834
+ await writeWorktreeMeta(ctx.resolved.gitDir, updateTab(ctx.meta, root.tabId, { sessionId: discovered }));
20835
+ }
20836
+ return discovered;
20837
+ }
20838
+ async restoreWorktreeTabs(input) {
20839
+ if (input.profile.runtime === "docker")
20840
+ return;
20841
+ if (input.agent.kind !== "builtin")
20842
+ return;
20843
+ const meta = await readWorktreeMeta(input.gitDir);
20844
+ const root = meta ? rootTab(meta) : undefined;
20845
+ if (!meta || !root)
20846
+ return;
20847
+ if (!listTabs(meta).some((tab) => tab.kind === "fork"))
20848
+ return;
20849
+ const sessionName = buildProjectSessionName(this.deps.projectRoot);
20850
+ const windowName = buildWorktreeWindowName(input.branch);
20851
+ const parkingWindow = buildWorktreeParkingWindowName(input.branch);
20852
+ this.deps.tmux.killWindow(sessionName, parkingWindow);
20853
+ const visibleSlot = `${sessionName}:${windowName}.0`;
20854
+ const visibleSlotPaneId = this.deps.tmux.getPaneId(visibleSlot);
20855
+ const restored = [{ ...root, paneId: visibleSlotPaneId }];
20856
+ for (const fork of listTabs(meta).filter((tab) => tab.kind === "fork")) {
20857
+ if (!fork.sessionId)
20858
+ continue;
20859
+ const command = buildAgentPaneCommand({
20860
+ agent: input.agent,
20861
+ runtimeEnvPath: input.runtimeEnvPath,
20862
+ repoRoot: this.deps.projectRoot,
20863
+ worktreePath: input.worktreePath,
20864
+ branch: input.branch,
20865
+ profileName: input.profileName,
20866
+ yolo: input.profile.yolo === true,
20867
+ launchMode: "resume",
20868
+ resumeConversationId: fork.sessionId
20869
+ });
20870
+ const paneId = this.deps.tmux.createParkedPane({
20871
+ sessionName,
20872
+ parkingWindow,
20873
+ cwd: input.worktreePath,
20874
+ command: buildManagedShellCommand(input.runtimeEnvPath)
20875
+ });
20876
+ this.deps.tmux.runCommand(paneId, command);
20877
+ restored.push({ ...fork, paneId });
20878
+ }
20879
+ let nextMeta = withTabs(meta, restored);
20880
+ const wantActive = activeTabId(meta);
20881
+ const activeTab = restored.find((tab) => tab.tabId === wantActive && tab.kind === "fork" && tab.paneId);
20882
+ if (activeTab?.paneId) {
20883
+ this.deps.tmux.swapPanes(activeTab.paneId, visibleSlotPaneId);
20884
+ nextMeta = setActiveTab(nextMeta, activeTab.tabId);
20885
+ } else {
20886
+ nextMeta = setActiveTab(nextMeta, ROOT_TAB_ID);
20887
+ }
20888
+ await writeWorktreeMeta(input.gitDir, nextMeta);
20889
+ }
20365
20890
  async disarmOneshot(branch) {
20366
20891
  let resolved;
20367
20892
  try {
@@ -20640,8 +21165,13 @@ class LifecycleService {
20640
21165
  }
20641
21166
  return nextMeta;
20642
21167
  }
21168
+ killWorktreeWindows(branch) {
21169
+ const sessionName = buildProjectSessionName(this.deps.projectRoot);
21170
+ this.deps.tmux.killWindow(sessionName, buildWorktreeWindowName(branch));
21171
+ this.deps.tmux.killWindow(sessionName, buildWorktreeParkingWindowName(branch));
21172
+ }
20643
21173
  async closeBranchWindow(branch) {
20644
- this.deps.tmux.killWindow(buildProjectSessionName(this.deps.projectRoot), buildWorktreeWindowName(branch));
21174
+ this.killWorktreeWindows(branch);
20645
21175
  await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
20646
21176
  }
20647
21177
  async materializeRuntimeSession(input) {
@@ -20745,7 +21275,7 @@ ${oneshotPrompt}` : oneshotPrompt ?? baseSystemPrompt;
20745
21275
  }
20746
21276
  }
20747
21277
  try {
20748
- this.deps.tmux.killWindow(buildProjectSessionName(this.deps.projectRoot), buildWorktreeWindowName(branch));
21278
+ this.killWorktreeWindows(branch);
20749
21279
  } catch (error) {
20750
21280
  cleanupErrors.push(`tmux cleanup failed: ${toErrorMessage2(error)}`);
20751
21281
  }
@@ -20783,7 +21313,7 @@ ${oneshotPrompt}` : oneshotPrompt ?? baseSystemPrompt;
20783
21313
  if (resolved.meta?.runtime === "docker") {
20784
21314
  await this.deps.docker.removeContainer(branch);
20785
21315
  }
20786
- this.deps.tmux.killWindow(buildProjectSessionName(this.deps.projectRoot), buildWorktreeWindowName(branch));
21316
+ this.killWorktreeWindows(branch);
20787
21317
  removeManagedWorktree({
20788
21318
  repoRoot: this.deps.projectRoot,
20789
21319
  worktreePath: resolved.entry.path,
@@ -20956,6 +21486,8 @@ var init_lifecycle_service = __esm(() => {
20956
21486
  init_fs();
20957
21487
  init_config();
20958
21488
  init_tmux();
21489
+ init_session_discovery();
21490
+ init_tab_logic();
20959
21491
  init_policies();
20960
21492
  init_agent_service();
20961
21493
  init_agent_registry();
@@ -21084,6 +21616,8 @@ function makeDefaultState(input) {
21084
21616
  agentName: input.agentName ?? null,
21085
21617
  source: input.source ?? "ui",
21086
21618
  oneshot: input.oneshot ?? null,
21619
+ tabs: input.tabs ?? [],
21620
+ activeTabId: input.activeTabId ?? null,
21087
21621
  agentTerminalStale: input.agentTerminalStale === true,
21088
21622
  git: {
21089
21623
  exists: true,
@@ -21140,6 +21674,10 @@ class ProjectRuntime {
21140
21674
  existing.source = input.source;
21141
21675
  if (input.oneshot !== undefined)
21142
21676
  existing.oneshot = input.oneshot;
21677
+ if (input.tabs !== undefined)
21678
+ existing.tabs = input.tabs;
21679
+ if (input.activeTabId !== undefined)
21680
+ existing.activeTabId = input.activeTabId;
21143
21681
  existing.git.exists = true;
21144
21682
  existing.git.branch = input.branch;
21145
21683
  existing.session.windowName = buildWorktreeWindowName(input.branch);
@@ -21276,7 +21814,7 @@ async function mapWithConcurrency(items, limit, fn) {
21276
21814
  }
21277
21815
 
21278
21816
  // backend/src/services/reconciliation-service.ts
21279
- import { basename as basename5, resolve as resolve9 } from "path";
21817
+ import { basename as basename6, resolve as resolve9 } from "path";
21280
21818
  function makeUnmanagedWorktreeId(path) {
21281
21819
  return `unmanaged:${resolve9(path)}`;
21282
21820
  }
@@ -21311,7 +21849,7 @@ function findWindow(windows, sessionName, branch) {
21311
21849
  return windows.find((window) => window.sessionName === sessionName && window.windowName === windowName) ?? null;
21312
21850
  }
21313
21851
  function resolveBranch(entry, metaBranch) {
21314
- const fallback = basename5(entry.path);
21852
+ const fallback = basename6(entry.path);
21315
21853
  return entry.branch ?? metaBranch ?? (fallback.length > 0 ? fallback : "unknown");
21316
21854
  }
21317
21855
 
@@ -21374,6 +21912,8 @@ class ReconciliationService {
21374
21912
  runtime: meta?.runtime ?? "host",
21375
21913
  source: meta?.source ?? "ui",
21376
21914
  oneshot: meta?.oneshot ?? null,
21915
+ tabs: meta?.tabs ?? [],
21916
+ activeTabId: meta?.activeTabId ?? null,
21377
21917
  git: {
21378
21918
  dirty: gitStatus.dirty,
21379
21919
  aheadCount: gitStatus.aheadCount,
@@ -21409,7 +21949,9 @@ class ReconciliationService {
21409
21949
  agentTerminalStale: state.agentTerminalStale,
21410
21950
  runtime: state.runtime,
21411
21951
  source: state.source,
21412
- oneshot: state.oneshot
21952
+ oneshot: state.oneshot,
21953
+ tabs: state.tabs,
21954
+ activeTabId: state.activeTabId
21413
21955
  });
21414
21956
  this.deps.runtime.setGitState(state.worktreeId, {
21415
21957
  exists: true,
@@ -21495,6 +22037,7 @@ function createWebmuxRuntime(options = {}) {
21495
22037
  archiveState: archiveStateService,
21496
22038
  git,
21497
22039
  tmux,
22040
+ sessionDiscovery: new FileSessionDiscovery,
21498
22041
  docker,
21499
22042
  reconciliation: reconciliationService,
21500
22043
  hooks,
@@ -21532,6 +22075,7 @@ var init_runtime = __esm(() => {
21532
22075
  init_git();
21533
22076
  init_hooks();
21534
22077
  init_tmux();
22078
+ init_session_discovery();
21535
22079
  init_auto_name_service();
21536
22080
  init_archive_state_service();
21537
22081
  init_lifecycle_service();
@@ -21543,6 +22087,7 @@ var init_runtime = __esm(() => {
21543
22087
  var exports_worktree_commands = {};
21544
22088
  __export(exports_worktree_commands, {
21545
22089
  runWorktreeCommand: () => runWorktreeCommand,
22090
+ parseTabCommandArgs: () => parseTabCommandArgs,
21546
22091
  parseSendCommandArgs: () => parseSendCommandArgs,
21547
22092
  parseListCommandArgs: () => parseListCommandArgs,
21548
22093
  parseLabelCommandArgs: () => parseLabelCommandArgs,
@@ -21550,7 +22095,7 @@ __export(exports_worktree_commands, {
21550
22095
  parseAddCommandArgs: () => parseAddCommandArgs,
21551
22096
  getWorktreeCommandUsage: () => getWorktreeCommandUsage
21552
22097
  });
21553
- import { basename as basename6, resolve as resolve10 } from "path";
22098
+ import { basename as basename7, resolve as resolve10 } from "path";
21554
22099
  function getWorktreeCommandUsage(command) {
21555
22100
  switch (command) {
21556
22101
  case "add":
@@ -21630,6 +22175,18 @@ function getWorktreeCommandUsage(command) {
21630
22175
  case "prune":
21631
22176
  return `Usage:
21632
22177
  webmux prune`;
22178
+ case "tab":
22179
+ return [
22180
+ "Usage:",
22181
+ " webmux tab <branch> List the agent tabs (\u2605 marks the active one)",
22182
+ " webmux tab <branch> new Create a new forked tab",
22183
+ " webmux tab <branch> switch <tabId> Switch the visible agent pane to a tab",
22184
+ " webmux tab <branch> close <tabId> Delete a forked tab",
22185
+ "",
22186
+ "Options:",
22187
+ " --help Show this help message"
22188
+ ].join(`
22189
+ `);
21633
22190
  }
21634
22191
  }
21635
22192
  function readOptionValue3(args, index, flag) {
@@ -21753,6 +22310,30 @@ function parseAddCommandArgs(args) {
21753
22310
  }
21754
22311
  return { input, detach, fromLinearIssueId, branchExplicit };
21755
22312
  }
22313
+ function parseTabCommandArgs(args) {
22314
+ const positional = [];
22315
+ for (const arg of args) {
22316
+ if (arg === "--help" || arg === "-h")
22317
+ return null;
22318
+ if (arg.startsWith("-"))
22319
+ throw new CommandUsageError(`Unknown option: ${arg}`);
22320
+ positional.push(arg);
22321
+ }
22322
+ const [branch, rawAction = "list", tabId, ...rest] = positional;
22323
+ if (!branch)
22324
+ throw new CommandUsageError("Missing required argument: <branch>");
22325
+ if (!isValidWorktreeName(branch))
22326
+ throw new CommandUsageError("Invalid worktree name");
22327
+ if (rawAction !== "list" && rawAction !== "new" && rawAction !== "switch" && rawAction !== "close") {
22328
+ throw new CommandUsageError(`Unknown tab action: ${rawAction}`);
22329
+ }
22330
+ if ((rawAction === "switch" || rawAction === "close") && !tabId) {
22331
+ throw new CommandUsageError(`The "${rawAction}" action requires a <tabId>`);
22332
+ }
22333
+ if (rest.length > 0)
22334
+ throw new CommandUsageError(`Unexpected argument: ${rest[0]}`);
22335
+ return { branch, action: rawAction, ...tabId ? { tabId } : {} };
22336
+ }
21756
22337
  function parseBranchCommandArgs(args) {
21757
22338
  let branch = null;
21758
22339
  for (const arg of args) {
@@ -21989,7 +22570,7 @@ async function listWorktrees(runtime, stdout2, options) {
21989
22570
  const projectGitDir = runtime.git.resolveWorktreeGitDir(projectDir);
21990
22571
  const archivedPaths = buildArchivedWorktreePathSet(await readWorktreeArchiveState(projectGitDir));
21991
22572
  const rows = await Promise.all(entries.map(async (entry) => {
21992
- const branch = entry.branch ?? basename6(entry.path);
22573
+ const branch = entry.branch ?? basename7(entry.path);
21993
22574
  const isOpen = openWindows.has(buildWorktreeWindowName(branch));
21994
22575
  const gitDir = runtime.git.resolveWorktreeGitDir(entry.path);
21995
22576
  const meta = await readWorktreeMeta(gitDir);
@@ -22156,6 +22737,45 @@ ${parsed.input.prompt}` : seed.data.conversationMarkdown;
22156
22737
  stdout2(`Sent prompt to ${parsed.branch}`);
22157
22738
  return 0;
22158
22739
  }
22740
+ if (context.command === "tab") {
22741
+ const parsed = parseTabCommandArgs(context.args);
22742
+ if (!parsed) {
22743
+ stdout2(getWorktreeCommandUsage("tab"));
22744
+ return 0;
22745
+ }
22746
+ const api = createApi(`http://localhost:${context.port}`);
22747
+ await withServerConnection(context.port, async () => {
22748
+ if (parsed.action === "new") {
22749
+ const { tab } = await api.createWorktreeTab({ params: { name: parsed.branch } });
22750
+ stdout2(`Created ${tab.label} (${tab.tabId}) in ${parsed.branch}`);
22751
+ return;
22752
+ }
22753
+ if (parsed.action === "switch" || parsed.action === "close") {
22754
+ const tabId = parsed.tabId;
22755
+ if (!tabId)
22756
+ throw new CommandUsageError(`The "${parsed.action}" action requires a <tabId>`);
22757
+ if (parsed.action === "switch") {
22758
+ await api.selectWorktreeTab({ params: { name: parsed.branch, tabId } });
22759
+ stdout2(`Switched ${parsed.branch} to tab ${tabId}`);
22760
+ } else {
22761
+ await api.deleteWorktreeTab({ params: { name: parsed.branch, tabId } });
22762
+ stdout2(`Closed tab ${tabId} in ${parsed.branch}`);
22763
+ }
22764
+ return;
22765
+ }
22766
+ const { worktrees } = await api.fetchWorktrees();
22767
+ const worktree = worktrees.find((candidate) => candidate.branch === parsed.branch);
22768
+ if (!worktree) {
22769
+ stdout2(`Worktree not found: ${parsed.branch}`);
22770
+ return;
22771
+ }
22772
+ for (const tab of worktree.tabs) {
22773
+ const marker = tab.tabId === worktree.activeTabId ? "\u2605" : " ";
22774
+ stdout2(`${marker} ${tab.label.padEnd(10)} ${tab.tabId}`);
22775
+ }
22776
+ });
22777
+ return 0;
22778
+ }
22159
22779
  if (context.command === "label") {
22160
22780
  const parsed = parseLabelCommandArgs(context.args);
22161
22781
  if (!parsed) {
@@ -22237,13 +22857,13 @@ var init_worktree_commands = __esm(() => {
22237
22857
  });
22238
22858
 
22239
22859
  // bin/src/webmux.ts
22240
- import { resolve as resolve11, dirname as dirname6, join as join13 } from "path";
22860
+ import { resolve as resolve11, dirname as dirname6, join as join14 } from "path";
22241
22861
  import { existsSync as existsSync7 } from "fs";
22242
22862
  import { fileURLToPath } from "url";
22243
22863
  // package.json
22244
22864
  var package_default = {
22245
22865
  name: "webmux",
22246
- version: "0.37.0",
22866
+ version: "0.38.0",
22247
22867
  description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
22248
22868
  type: "module",
22249
22869
  repository: {
@@ -22322,6 +22942,7 @@ Usage:
22322
22942
  webmux remove Remove a worktree
22323
22943
  webmux merge Merge a worktree into the main branch and remove it
22324
22944
  webmux send Send a prompt to a running worktree agent
22945
+ webmux tab List, create, switch, or close agent tabs in a worktree
22325
22946
  webmux prune Remove all worktrees in the current project
22326
22947
  webmux linear Post a worktree conversation to a Linear issue/team
22327
22948
  webmux completion Generate shell completion script (bash, zsh)
@@ -22342,7 +22963,7 @@ Environment:
22342
22963
  `);
22343
22964
  }
22344
22965
  function isRootCommand(value) {
22345
- return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "oneshot" || value === "list" || value === "open" || value === "close" || value === "refresh" || value === "archive" || value === "unarchive" || value === "label" || value === "remove" || value === "merge" || value === "send" || value === "prune" || value === "linear" || value === "completion";
22966
+ return value === "serve" || value === "init" || value === "service" || value === "update" || value === "add" || value === "oneshot" || value === "list" || value === "open" || value === "close" || value === "refresh" || value === "archive" || value === "unarchive" || value === "label" || value === "remove" || value === "merge" || value === "send" || value === "tab" || value === "prune" || value === "linear" || value === "completion";
22346
22967
  }
22347
22968
  function isServeRootOption(value) {
22348
22969
  return value === "--port" || value === "--prefix" || value === "--app" || value === "--debug" || value === "--help" || value === "-h" || value === "--version" || value === "-V";
@@ -22420,7 +23041,7 @@ Run webmux --help for usage.`);
22420
23041
  };
22421
23042
  }
22422
23043
  function isWorktreeCommand(command) {
22423
- return command === "add" || command === "list" || command === "open" || command === "close" || command === "refresh" || command === "archive" || command === "unarchive" || command === "label" || command === "remove" || command === "merge" || command === "send" || command === "prune";
23044
+ return command === "add" || command === "list" || command === "open" || command === "close" || command === "refresh" || command === "archive" || command === "unarchive" || command === "label" || command === "remove" || command === "merge" || command === "send" || command === "tab" || command === "prune";
22424
23045
  }
22425
23046
  async function loadEnvFile(path) {
22426
23047
  if (!existsSync7(path))
@@ -22630,8 +23251,8 @@ Refreshing ${services.length} installed webmux service(s) to pick up the new ver
22630
23251
  }
22631
23252
  process.on("SIGINT", cleanup);
22632
23253
  process.on("SIGTERM", cleanup);
22633
- const backendEntry = join13(PKG_ROOT, "backend", "dist", "server.js");
22634
- const staticDir = join13(PKG_ROOT, "frontend", "dist");
23254
+ const backendEntry = join14(PKG_ROOT, "backend", "dist", "server.js");
23255
+ const staticDir = join14(PKG_ROOT, "frontend", "dist");
22635
23256
  if (!existsSync7(staticDir)) {
22636
23257
  console.error(`Error: frontend/dist/ not found. Run 'bun run build' first.`);
22637
23258
  process.exit(1);