@hellcoder/companion 0.105.3 → 0.106.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.
Files changed (30) hide show
  1. package/dist/assets/{AgentsPage-Chj3i8hp.js → AgentsPage-DbvkUXVp.js} +5 -5
  2. package/dist/assets/{CronManager-CnSV7pph.js → CronManager-BLIoyqgi.js} +1 -1
  3. package/dist/assets/{IntegrationsPage-D7He8DJZ.js → IntegrationsPage-DIG0VC9S.js} +1 -1
  4. package/dist/assets/{LinearOAuthSettingsPage-BhbSx1L1.js → LinearOAuthSettingsPage-VDcZL-vG.js} +1 -1
  5. package/dist/assets/{LinearSettingsPage-xYU0CnhC.js → LinearSettingsPage-N9lB7uAD.js} +1 -1
  6. package/dist/assets/{Playground-ZMOfN3kG.js → Playground-egZO3vjk.js} +1 -1
  7. package/dist/assets/{PromptsPage-C-YET0qF.js → PromptsPage-ByCRKIIK.js} +1 -1
  8. package/dist/assets/{RunsPage-D8p_NPsc.js → RunsPage-DRLiiAa4.js} +1 -1
  9. package/dist/assets/{SandboxManager-DNct4A2p.js → SandboxManager-D0UrXCUj.js} +1 -1
  10. package/dist/assets/SettingsPage-BESpvcQy.js +1 -0
  11. package/dist/assets/{TailscalePage-CVXN6STS.js → TailscalePage-hiU_mAeJ.js} +1 -1
  12. package/dist/assets/{index-B_8Ih1pQ.js → index-Ct0BP0Nf.js} +49 -49
  13. package/dist/assets/{sw-register-Gj6h8GCi.js → sw-register-DqHi_6mH.js} +1 -1
  14. package/dist/index.html +1 -1
  15. package/dist/sw.js +1 -1
  16. package/package.json +1 -1
  17. package/server/agent-executor.test.ts +99 -2
  18. package/server/agent-executor.ts +34 -4
  19. package/server/agent-types.ts +10 -0
  20. package/server/auto-namer.test.ts +4 -0
  21. package/server/linear-connections.test.ts +4 -0
  22. package/server/routes/agent-routes.ts +2 -1
  23. package/server/routes/settings-routes.ts +10 -0
  24. package/server/routes.test.ts +52 -0
  25. package/server/session-orchestrator.test.ts +44 -1
  26. package/server/session-orchestrator.ts +14 -0
  27. package/server/settings-manager.test.ts +3 -0
  28. package/server/settings-manager.ts +10 -1
  29. package/server/ws-bridge-codex.test.ts +6 -0
  30. package/dist/assets/SettingsPage-Bc33muUi.js +0 -1
@@ -49,6 +49,7 @@ vi.mock("./settings-manager.js", () => ({
49
49
  claudeCodeOAuthToken: "",
50
50
  openaiApiKey: "",
51
51
  onboardingCompleted: false,
52
+ proactiveKeepaliveEnabled: true,
52
53
  })),
53
54
  }));
54
55
 
@@ -201,7 +202,7 @@ function createDeps(overrides?: Partial<SessionOrchestratorDeps>) {
201
202
  const sessionStore = createMockStore();
202
203
  const worktreeTracker = createMockTracker();
203
204
  const prPoller = { watch: vi.fn(), unwatch: vi.fn() };
204
- const agentExecutor = { handleSessionExited: vi.fn() } as any;
205
+ const agentExecutor = { handleSessionExited: vi.fn(), setArchivePreviousSession: vi.fn() } as any;
205
206
  return {
206
207
  launcher,
207
208
  wsBridge,
@@ -229,6 +230,24 @@ describe("SessionOrchestrator", () => {
229
230
  // previous tests (clearAllMocks resets calls/results but NOT implementations).
230
231
  vi.mocked(hasContainerClaudeAuth).mockReturnValue(true);
231
232
  vi.mocked(hasContainerCodexAuth).mockReturnValue(true);
233
+ // Re-establish getSettings default — some tests override it with a minimal
234
+ // object via mockReturnValue, which persists across clearAllMocks and would
235
+ // otherwise drop proactiveKeepaliveEnabled (breaking the keepalive gate).
236
+ vi.mocked(settingsManager.getSettings).mockReturnValue({
237
+ anthropicApiKey: "",
238
+ anthropicModel: "claude-sonnet-4-6",
239
+ linearApiKey: "",
240
+ linearAutoTransition: false,
241
+ linearAutoTransitionStateId: "",
242
+ linearAutoTransitionStateName: "",
243
+ linearArchiveTransition: false,
244
+ linearArchiveTransitionStateId: "",
245
+ linearArchiveTransitionStateName: "",
246
+ claudeCodeOAuthToken: "",
247
+ openaiApiKey: "",
248
+ onboardingCompleted: false,
249
+ proactiveKeepaliveEnabled: true,
250
+ } as any);
232
251
  vi.mocked(containerManager.createContainer).mockReturnValue({
233
252
  containerId: "cid-1",
234
253
  name: "companion-1",
@@ -1806,6 +1825,30 @@ describe("SessionOrchestrator", () => {
1806
1825
  expect(deps.launcher.relaunch).toHaveBeenCalledWith("s1");
1807
1826
  });
1808
1827
 
1828
+ it("does NOT proactively relaunch when proactiveKeepaliveEnabled is off", async () => {
1829
+ // The global kill-switch lets operators disable auto-respawn so dead
1830
+ // sessions stay dead.
1831
+ vi.mocked(settingsManager.getSettings).mockReturnValue({
1832
+ ...settingsManager.getSettings(),
1833
+ proactiveKeepaliveEnabled: false,
1834
+ });
1835
+ deps.launcher.getSession.mockReturnValue({
1836
+ archived: false,
1837
+ state: "exited",
1838
+ pid: undefined,
1839
+ } as any);
1840
+ deps.wsBridge.isCliConnected.mockReturnValue(false);
1841
+ orchestrator.initialize();
1842
+
1843
+ companionBus.emit("session:exited", { sessionId: "s1", exitCode: 1 });
1844
+
1845
+ await vi.advanceTimersByTimeAsync(3_000);
1846
+ await vi.advanceTimersByTimeAsync(15_000);
1847
+ await vi.advanceTimersByTimeAsync(0);
1848
+
1849
+ expect(deps.launcher.relaunch).not.toHaveBeenCalled();
1850
+ });
1851
+
1809
1852
  it("does NOT proactively relaunch after idle-kill (intentional kill)", async () => {
1810
1853
  // Idle-kill is intentional — the proactive keepalive should NOT trigger.
1811
1854
  // The debounce timer in ws-bridge is also cancelled by the idle-kill
@@ -187,6 +187,13 @@ export class SessionOrchestrator {
187
187
  this.worktreeTracker = deps.worktreeTracker;
188
188
  this.prPoller = deps.prPoller;
189
189
  this.agentExecutor = deps.agentExecutor;
190
+
191
+ // Let the agent executor terminate a leftover session before a new run
192
+ // (restartMode "terminate"). Routing through archiveSession registers the
193
+ // kill as intentional, so the proactive keepalive does not respawn it.
194
+ this.agentExecutor.setArchivePreviousSession(async (sessionId) => {
195
+ await this.archiveSession(sessionId);
196
+ });
190
197
  }
191
198
 
192
199
  // ── Initialization (event wiring) ──────────────────────────────────────────
@@ -985,6 +992,13 @@ export class SessionOrchestrator {
985
992
  * - Sessions that have exhausted their relaunch budget
986
993
  */
987
994
  private scheduleProactiveRelaunch(sessionId: string): void {
995
+ // Respect the global kill-switch — lets operators experiment with letting
996
+ // dead sessions stay dead instead of being auto-respawned.
997
+ if (!getSettings().proactiveKeepaliveEnabled) {
998
+ log.info("orchestrator", "Proactive keepalive disabled by settings; not relaunching", { sessionId });
999
+ return;
1000
+ }
1001
+
988
1002
  // Skip if this was an intentional kill. Use has() instead of delete() so
989
1003
  // the guard is preserved for handleAutoRelaunch (debounce path fires later).
990
1004
  if (this.intentionalKills.has(sessionId)) return;
@@ -48,6 +48,7 @@ describe("settings-manager", () => {
48
48
  publicUrl: "",
49
49
  updateChannel: "stable",
50
50
  dockerAutoUpdate: false,
51
+ proactiveKeepaliveEnabled: true,
51
52
  cliBridgeMode: "loopback",
52
53
  claudeBridgeMode: "none",
53
54
  claudeBridgeIngressUrl: "",
@@ -107,6 +108,7 @@ describe("settings-manager", () => {
107
108
  publicUrl: "",
108
109
  updateChannel: "stable",
109
110
  dockerAutoUpdate: false,
111
+ proactiveKeepaliveEnabled: true,
110
112
  cliBridgeMode: "loopback",
111
113
  claudeBridgeMode: "none",
112
114
  claudeBridgeIngressUrl: "",
@@ -190,6 +192,7 @@ describe("settings-manager", () => {
190
192
  publicUrl: "",
191
193
  updateChannel: "stable",
192
194
  dockerAutoUpdate: false,
195
+ proactiveKeepaliveEnabled: true,
193
196
  cliBridgeMode: "loopback",
194
197
  claudeBridgeMode: "none",
195
198
  claudeBridgeIngressUrl: "",
@@ -65,6 +65,12 @@ export interface CompanionSettings {
65
65
  publicUrl: string;
66
66
  updateChannel: UpdateChannel;
67
67
  dockerAutoUpdate: boolean;
68
+ /**
69
+ * When true (default), a CLI process that exits unexpectedly with no browser
70
+ * attached is proactively relaunched to keep long-running sessions (agents,
71
+ * cron) alive. Disable to experiment with letting dead sessions stay dead.
72
+ */
73
+ proactiveKeepaliveEnabled: boolean;
68
74
  /** See CliBridgeMode. Defaults to "loopback". Optional in fixtures; normalize() applies the default. */
69
75
  cliBridgeMode?: CliBridgeMode;
70
76
  /** See ClaudeBridgeMode. Defaults to "none". Persists across companion restarts. */
@@ -104,6 +110,7 @@ let settings: CompanionSettings = {
104
110
  publicUrl: "",
105
111
  updateChannel: "stable",
106
112
  dockerAutoUpdate: false,
113
+ proactiveKeepaliveEnabled: true,
107
114
  cliBridgeMode: "loopback",
108
115
  claudeBridgeMode: "none",
109
116
  claudeBridgeIngressUrl: "",
@@ -139,6 +146,7 @@ function normalize(raw: Partial<CompanionSettings> | null | undefined): Companio
139
146
  publicUrl: typeof raw?.publicUrl === "string" ? raw.publicUrl.trim().replace(/\/+$/, "") : "",
140
147
  updateChannel: raw?.updateChannel === "prerelease" ? "prerelease" : "stable",
141
148
  dockerAutoUpdate: typeof raw?.dockerAutoUpdate === "boolean" ? raw.dockerAutoUpdate : false,
149
+ proactiveKeepaliveEnabled: typeof raw?.proactiveKeepaliveEnabled === "boolean" ? raw.proactiveKeepaliveEnabled : true,
142
150
  cliBridgeMode: raw?.cliBridgeMode === "jsonHandoff" ? "jsonHandoff" : "loopback",
143
151
  claudeBridgeMode: raw?.claudeBridgeMode === "patched" ? "patched" : "none",
144
152
  claudeBridgeIngressUrl: typeof raw?.claudeBridgeIngressUrl === "string" ? raw.claudeBridgeIngressUrl : "",
@@ -172,7 +180,7 @@ export function getSettings(): CompanionSettings {
172
180
  }
173
181
 
174
182
  export function updateSettings(
175
- patch: Partial<Pick<CompanionSettings, "anthropicApiKey" | "anthropicModel" | "claudeCodeOAuthToken" | "openaiApiKey" | "onboardingCompleted" | "linearApiKey" | "linearAutoTransition" | "linearAutoTransitionStateId" | "linearAutoTransitionStateName" | "linearArchiveTransition" | "linearArchiveTransitionStateId" | "linearArchiveTransitionStateName" | "linearOAuthClientId" | "linearOAuthClientSecret" | "linearOAuthWebhookSecret" | "linearOAuthAccessToken" | "linearOAuthRefreshToken" | "aiValidationEnabled" | "aiValidationAutoApprove" | "aiValidationAutoDeny" | "publicUrl" | "updateChannel" | "dockerAutoUpdate" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
183
+ patch: Partial<Pick<CompanionSettings, "anthropicApiKey" | "anthropicModel" | "claudeCodeOAuthToken" | "openaiApiKey" | "onboardingCompleted" | "linearApiKey" | "linearAutoTransition" | "linearAutoTransitionStateId" | "linearAutoTransitionStateName" | "linearArchiveTransition" | "linearArchiveTransitionStateId" | "linearArchiveTransitionStateName" | "linearOAuthClientId" | "linearOAuthClientSecret" | "linearOAuthWebhookSecret" | "linearOAuthAccessToken" | "linearOAuthRefreshToken" | "aiValidationEnabled" | "aiValidationAutoApprove" | "aiValidationAutoDeny" | "publicUrl" | "updateChannel" | "dockerAutoUpdate" | "proactiveKeepaliveEnabled" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
176
184
  ): CompanionSettings {
177
185
  ensureLoaded();
178
186
  settings = normalize({
@@ -199,6 +207,7 @@ export function updateSettings(
199
207
  publicUrl: patch.publicUrl ?? settings.publicUrl,
200
208
  updateChannel: patch.updateChannel ?? settings.updateChannel,
201
209
  dockerAutoUpdate: patch.dockerAutoUpdate ?? settings.dockerAutoUpdate,
210
+ proactiveKeepaliveEnabled: patch.proactiveKeepaliveEnabled ?? settings.proactiveKeepaliveEnabled,
202
211
  cliBridgeMode: patch.cliBridgeMode ?? settings.cliBridgeMode,
203
212
  claudeBridgeMode: patch.claudeBridgeMode ?? settings.claudeBridgeMode,
204
213
  claudeBridgeIngressUrl: patch.claudeBridgeIngressUrl ?? settings.claudeBridgeIngressUrl,
@@ -135,6 +135,7 @@ describe("attachCodexAdapterHandlers", () => {
135
135
  publicUrl: "",
136
136
  updateChannel: "stable",
137
137
  dockerAutoUpdate: false,
138
+ proactiveKeepaliveEnabled: true,
138
139
  updatedAt: 0,
139
140
  });
140
141
  });
@@ -1121,6 +1122,7 @@ describe("attachCodexAdapterHandlers", () => {
1121
1122
  publicUrl: "",
1122
1123
  updateChannel: "stable",
1123
1124
  dockerAutoUpdate: false,
1125
+ proactiveKeepaliveEnabled: true,
1124
1126
  updatedAt: 0,
1125
1127
  });
1126
1128
  }
@@ -1297,6 +1299,7 @@ describe("attachCodexAdapterHandlers", () => {
1297
1299
  publicUrl: "",
1298
1300
  updateChannel: "stable",
1299
1301
  dockerAutoUpdate: false,
1302
+ proactiveKeepaliveEnabled: true,
1300
1303
  updatedAt: 0,
1301
1304
  });
1302
1305
 
@@ -1343,6 +1346,7 @@ describe("attachCodexAdapterHandlers", () => {
1343
1346
  publicUrl: "",
1344
1347
  updateChannel: "stable",
1345
1348
  dockerAutoUpdate: false,
1349
+ proactiveKeepaliveEnabled: true,
1346
1350
  updatedAt: 0,
1347
1351
  });
1348
1352
 
@@ -1454,6 +1458,7 @@ describe("attachCodexAdapterHandlers", () => {
1454
1458
  publicUrl: "",
1455
1459
  updateChannel: "stable",
1456
1460
  dockerAutoUpdate: false,
1461
+ proactiveKeepaliveEnabled: true,
1457
1462
  updatedAt: 0,
1458
1463
  });
1459
1464
 
@@ -1584,6 +1589,7 @@ describe("attachCodexAdapterHandlers", () => {
1584
1589
  publicUrl: "",
1585
1590
  updateChannel: "stable",
1586
1591
  dockerAutoUpdate: false,
1592
+ proactiveKeepaliveEnabled: true,
1587
1593
  updatedAt: 0,
1588
1594
  });
1589
1595
 
@@ -1 +0,0 @@
1
- import{r as s,u as o,g as st,a as n,j as e,n as ct,b as nt,s as ot}from"./index-B_8Ih1pQ.js";const Z=[{id:"general",label:"General"},{id:"webhooks",label:"Webhooks"},{id:"authentication",label:"Authentication"},{id:"notifications",label:"Notifications"},{id:"providers",label:"Providers"},{id:"anthropic",label:"Anthropic"},{id:"ai-validation",label:"AI Validation"},{id:"updates",label:"Updates"},{id:"telemetry",label:"Telemetry"},{id:"environments",label:"Environments"}];function it({embedded:ee=!1}){const[p,te]=s.useState(""),[se,ce]=s.useState("claude-sonnet-4-6"),[d,ne]=s.useState(!1),[w,Le]=s.useState(!0),[g,C]=s.useState(!1),[oe,b]=s.useState(""),[ae,y]=s.useState(!1),Fe=o(t=>t.darkMode),Ke=o(t=>t.toggleDarkMode),ie=o(t=>t.diffBase),Me=o(t=>t.setDiffBase),Be=o(t=>t.notificationSound),He=o(t=>t.toggleNotificationSound),re=o(t=>t.notificationDesktop),le=o(t=>t.setNotificationDesktop),a=o(t=>t.updateInfo),I=o(t=>t.setUpdateInfo),Qe=o(t=>t.setUpdateOverlayActive),_e=typeof Notification<"u",[x,v]=s.useState("stable"),[S,V]=s.useState(!1),[de,R]=s.useState("loopback"),[P,ue]=s.useState(!1),[O,xe]=s.useState(!1),[pe,j]=s.useState(""),[me,A]=s.useState(""),[he,Ge]=s.useState(st()),[U,D]=s.useState(!1),[$,L]=s.useState(!0),[F,K]=s.useState(!1),[T,M]=s.useState(""),[fe,ge]=s.useState("general"),[qe,be]=s.useState(!1),[B,ye]=s.useState(!1),[k,m]=s.useState(null),[h,ve]=s.useState(""),[H,je]=s.useState(!1),[f,ke]=s.useState(""),[Q,Ne]=s.useState(!1),[_,we]=s.useState(!1),[Ye,G]=s.useState(!1),[Ce,Se]=s.useState(""),[We,Ae]=s.useState(!1),[Je,Ue]=s.useState(!1),[N,Te]=s.useState(null),[q,Ee]=s.useState(!1),[u,Ie]=s.useState(null),[E,ze]=s.useState(0),[Y,Ve]=s.useState(!1),[W,Re]=s.useState(!1),[Xe,Pe]=s.useState(!1),Oe=s.useRef(null),J=s.useRef({});s.useEffect(()=>{const t=Oe.current;if(!t)return;const c=new IntersectionObserver(i=>{var $e;let r=null;for(const X of i)X.isIntersecting&&(!r||X.boundingClientRect.top<r.boundingClientRect.top)&&(r=X);($e=r==null?void 0:r.target)!=null&&$e.id&&ge(r.target.id)},{root:t,rootMargin:"-10% 0px -70% 0px",threshold:0});for(const i of Z){const r=J.current[i.id];r&&c.observe(r)}return()=>c.disconnect()},[w]);const De=s.useCallback(t=>{ge(t);const c=J.current[t];c&&c.scrollIntoView({behavior:"smooth",block:"start"})},[]);s.useEffect(()=>{n.getSettings().then(t=>{ne(t.anthropicApiKeyConfigured),je(t.claudeCodeOAuthTokenConfigured),Ne(t.openaiApiKeyConfigured),ce(t.anthropicModel||"claude-sonnet-4-6"),typeof t.aiValidationEnabled=="boolean"&&D(t.aiValidationEnabled),typeof t.aiValidationAutoApprove=="boolean"&&L(t.aiValidationAutoApprove),typeof t.aiValidationAutoDeny=="boolean"&&K(t.aiValidationAutoDeny),(t.updateChannel==="stable"||t.updateChannel==="prerelease")&&v(t.updateChannel),typeof t.dockerAutoUpdate=="boolean"&&V(t.dockerAutoUpdate),(t.cliBridgeMode==="loopback"||t.cliBridgeMode==="jsonHandoff")&&R(t.cliBridgeMode),typeof t.publicUrl=="string"&&(M(t.publicUrl),o.getState().setPublicUrl(t.publicUrl))}).catch(t=>b(t instanceof Error?t.message:String(t))).finally(()=>Le(!1)),n.getAuthToken().then(t=>Te(t.token)).catch(()=>{})},[]);async function Ze(t){t.preventDefault(),C(!0),b(""),y(!1);try{const c=p.trim(),i={anthropicModel:se.trim()||"claude-sonnet-4-6"};c&&(i.anthropicApiKey=c);const r=await n.updateSettings(i);ne(r.anthropicApiKeyConfigured),te(""),y(!0),setTimeout(()=>y(!1),1800)}catch(c){b(c instanceof Error?c.message:String(c))}finally{C(!1)}}async function z(t){const c=t==="aiValidationEnabled"?U:t==="aiValidationAutoApprove"?$:F,i=!c;t==="aiValidationEnabled"?D(i):t==="aiValidationAutoApprove"?L(i):K(i);try{await n.updateSettings({[t]:i})}catch{t==="aiValidationEnabled"?D(c):t==="aiValidationAutoApprove"?L(c):K(c)}}async function et(){ue(!0),j(""),A("");try{const t=await n.forceCheckForUpdate();I(t),t.updateAvailable&&t.latestVersion?j(`Update v${t.latestVersion} is available.`):j("You are up to date.")}catch(t){A(t instanceof Error?t.message:String(t))}finally{ue(!1)}}async function tt(){xe(!0),j(""),A("");try{localStorage.setItem("companion_docker_prompt_pending","1");const t=await n.triggerUpdate();j(t.message),Qe(!0)}catch(t){localStorage.removeItem("companion_docker_prompt_pending"),A(t instanceof Error?t.message:String(t)),xe(!1)}}const l=s.useCallback(t=>c=>{J.current[t]=c},[]);return e.jsxs("div",{className:`${ee?"h-full":"h-[100dvh]"} bg-cc-bg text-cc-fg font-sans-ui antialiased flex flex-col`,children:[e.jsx("div",{className:"shrink-0 max-w-5xl w-full mx-auto px-4 sm:px-8 pt-6 sm:pt-10",children:e.jsxs("div",{className:"flex items-start justify-between gap-3 mb-6",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-xl font-semibold text-cc-fg",children:"Settings"}),e.jsx("p",{className:"mt-1 text-sm text-cc-muted",children:"Configure API access, notifications, appearance, and workspace defaults."})]}),!ee&&e.jsx("button",{onClick:()=>{const t=o.getState().currentSessionId;t?ct(t):nt()},className:"px-3 py-2.5 min-h-[44px] rounded-lg text-sm text-cc-muted hover:text-cc-fg hover:bg-cc-hover transition-colors cursor-pointer",children:"Back"})]})}),e.jsx("div",{className:"sm:hidden shrink-0 border-b border-cc-border",children:e.jsx("nav",{className:"flex gap-1 px-4 py-2 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden","aria-label":"Settings categories",children:Z.map(t=>e.jsx("button",{type:"button",onClick:()=>De(t.id),className:`shrink-0 px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${fe===t.id?"text-cc-primary bg-cc-primary/8":"text-cc-muted hover:text-cc-fg hover:bg-cc-hover"}`,children:t.label},t.id))})}),e.jsxs("div",{className:"flex-1 min-h-0 flex max-w-5xl w-full mx-auto",children:[e.jsx("nav",{className:"hidden sm:flex flex-col gap-0.5 w-44 shrink-0 pt-2 pr-6 pl-8 sticky top-0 self-start","aria-label":"Settings categories",children:Z.map(t=>e.jsx("button",{type:"button",onClick:()=>De(t.id),className:`text-left px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${fe===t.id?"text-cc-primary bg-cc-primary/8":"text-cc-muted hover:text-cc-fg hover:bg-cc-hover"}`,children:t.label},t.id))}),e.jsx("div",{ref:Oe,className:"flex-1 min-w-0 overflow-y-auto px-4 sm:px-8 sm:pl-0 pb-safe",children:e.jsxs("div",{className:"space-y-10 py-4 sm:py-2",children:[e.jsxs("section",{id:"general",ref:l("general"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"General"}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("button",{type:"button",onClick:Ke,className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg text-sm bg-cc-hover text-cc-fg hover:bg-cc-active transition-colors cursor-pointer",children:[e.jsx("span",{children:"Theme"}),e.jsx("span",{className:"text-xs text-cc-muted",children:Fe?"Dark":"Light"})]}),e.jsxs("button",{type:"button",onClick:()=>Me(ie==="last-commit"?"default-branch":"last-commit"),className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg text-sm bg-cc-hover text-cc-fg hover:bg-cc-active transition-colors cursor-pointer",children:[e.jsx("span",{children:"Diff compare against"}),e.jsx("span",{className:"text-xs text-cc-muted",children:ie==="last-commit"?"Last commit (HEAD)":"Default branch"})]}),e.jsx("p",{className:"text-xs text-cc-muted px-1",children:"Last commit shows only uncommitted changes. Default branch shows all changes since diverging from main."}),e.jsx("div",{className:"pt-3 border-t border-cc-border",children:e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("span",{className:"block text-sm font-medium",children:"Claude Code bridge mode"}),e.jsx("p",{className:"mt-0.5 text-xs text-cc-muted",children:'How the Companion hands the bridge URL to the spawned Claude Code CLI. Loopback is the default fix for Claude Code v1.2.1+ which rejects the literal "localhost". JSON handoff is the more robust just-every/code-style approach: writes a temp descriptor with a one-shot token and passes its path via CLAUDE_BRIDGE_CONFIG.'})]}),e.jsxs("select",{"aria-label":"CLI bridge mode",value:de,onChange:async t=>{const c=t.target.value==="jsonHandoff"?"jsonHandoff":"loopback",i=de;R(c);try{await n.updateSettings({cliBridgeMode:c})}catch{R(i)}},className:"ml-3 px-2 py-1.5 text-xs bg-cc-bg rounded-lg border border-cc-border text-cc-fg focus:outline-none focus:ring-1 focus:ring-cc-primary",children:[e.jsx("option",{value:"loopback",children:"Loopback (default)"}),e.jsx("option",{value:"jsonHandoff",children:"JSON handoff (experimental)"})]})]})})]})]}),e.jsxs("section",{id:"webhooks",ref:l("webhooks"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Webhooks"}),e.jsxs("div",{className:"space-y-4",children:[e.jsx("p",{className:"text-xs text-cc-muted",children:"The public URL is used for webhook URLs that external services (Linear, GitHub) send events to. Set this to the externally-reachable address of your Companion instance."}),e.jsxs("p",{className:"text-xs text-cc-muted",children:["Tip:"," ",e.jsx("a",{href:"#/integrations/tailscale",className:"text-cc-primary hover:underline",children:"Use the Tailscale integration"})," ","to get an HTTPS URL automatically."]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-xs font-medium text-cc-fg mb-1.5",htmlFor:"public-url",children:"Public URL"}),e.jsx("input",{id:"public-url",type:"url","aria-label":"Public URL",value:T,onChange:t=>M(t.target.value),placeholder:"https://your-domain.example.com",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg border border-cc-border text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary font-mono-code"}),e.jsx("p",{className:"mt-1.5 text-[10px] text-cc-muted",children:T?`Using: ${T}`:`Fallback: ${typeof window<"u"?window.location.origin:"http://localhost:3456"}`})]}),e.jsx("button",{type:"button",onClick:async()=>{C(!0),b("");try{const t=await n.updateSettings({publicUrl:T.trim()});M(t.publicUrl),o.getState().setPublicUrl(t.publicUrl),y(!0),setTimeout(()=>y(!1),1800)}catch(t){b(t instanceof Error?t.message:String(t))}finally{C(!1)}},disabled:g,className:"px-4 py-2 min-h-[44px] rounded-lg text-sm font-medium bg-cc-primary text-white hover:opacity-90 transition-opacity disabled:opacity-50 cursor-pointer",children:g?"Saving...":ae?"Saved!":"Save Public URL"})]})]}),e.jsxs("section",{id:"authentication",ref:l("authentication"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Authentication"}),e.jsxs("div",{className:"space-y-4",children:[e.jsx("p",{className:"text-xs text-cc-muted",children:"Use the auth token or QR code to connect additional devices (e.g. mobile over Tailscale)."}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",children:"Auth Token"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("div",{className:"flex-1 px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg font-mono-code select-all break-all flex items-center",children:N?q?N:"••••••••••••••••":e.jsx("span",{className:"text-cc-muted",children:"Loading..."})}),e.jsx("button",{type:"button",onClick:()=>Ee(t=>!t),className:"px-3 py-2.5 min-h-[44px] rounded-lg text-sm bg-cc-hover hover:bg-cc-active text-cc-fg transition-colors cursor-pointer",title:q?"Hide token":"Show token",children:q?"Hide":"Show"}),e.jsx("button",{type:"button",onClick:()=>{N&&navigator.clipboard.writeText(N).then(()=>{Pe(!0),setTimeout(()=>Pe(!1),1500)})},disabled:!N,className:"px-3 py-2.5 min-h-[44px] rounded-lg text-sm bg-cc-hover hover:bg-cc-active text-cc-fg transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",title:"Copy token to clipboard",children:Xe?"Copied":"Copy"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",children:"Mobile Login QR"}),u&&u.length>0?e.jsxs("div",{className:"space-y-3",children:[u.length>1&&e.jsx("div",{className:"flex gap-1",children:u.map((t,c)=>e.jsx("button",{type:"button",onClick:()=>ze(c),className:`px-3 py-1.5 rounded-md text-xs font-medium transition-colors cursor-pointer ${c===E?"bg-cc-primary text-white":"bg-cc-hover text-cc-muted hover:text-cc-fg"}`,children:t.label},t.label))}),e.jsx("div",{className:"inline-block rounded-lg bg-white p-2",children:e.jsx("img",{src:u[E].qrDataUrl,alt:`QR code for ${u[E].label} login`,className:"w-48 h-48"})}),e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-bg text-sm font-mono-code text-cc-fg break-all select-all",children:u[E].url}),e.jsx("p",{className:"text-xs text-cc-muted",children:"Scan with your phone's camera app — it will open the URL and auto-authenticate."})]}):u&&u.length===0?e.jsx("p",{className:"text-xs text-cc-muted",children:"No remote addresses detected (LAN or Tailscale). Connect to a network to generate a QR code."}):e.jsx("button",{type:"button",onClick:async()=>{Ve(!0);try{const t=await n.getAuthQr();Ie(t.qrCodes)}catch{}finally{Ve(!1)}},disabled:Y,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${Y?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:Y?"Generating...":"Show QR Code"})]}),e.jsxs("div",{className:"pt-2",children:[e.jsx("button",{type:"button",onClick:async()=>{if(confirm("Regenerate auth token? All existing sessions on other devices will be signed out.")){Re(!0);try{const t=await n.regenerateAuthToken();Te(t.token),Ee(!0),Ie(null)}catch{}finally{Re(!1)}}},disabled:W,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${W?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-error/10 hover:bg-cc-error/20 text-cc-error cursor-pointer"}`,children:W?"Regenerating...":"Regenerate Token"}),e.jsx("p",{className:"mt-1.5 text-xs text-cc-muted",children:"Creates a new token. All other signed-in devices will need to re-authenticate."})]})]})]}),e.jsxs("section",{id:"notifications",ref:l("notifications"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Notifications"}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("button",{type:"button",onClick:He,className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg text-sm bg-cc-hover text-cc-fg hover:bg-cc-active transition-colors cursor-pointer",children:[e.jsx("span",{children:"Sound"}),e.jsx("span",{className:"text-xs text-cc-muted",children:Be?"On":"Off"})]}),_e&&e.jsxs("button",{type:"button",onClick:async()=>{if(re)le(!1);else{if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return;le(!0)}},className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg text-sm bg-cc-hover text-cc-fg hover:bg-cc-active transition-colors cursor-pointer",children:[e.jsx("span",{children:"Desktop Alerts"}),e.jsx("span",{className:"text-xs text-cc-muted",children:re?"On":"Off"})]})]})]}),e.jsxs("section",{id:"providers",ref:l("providers"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Providers"}),e.jsxs("div",{className:"space-y-6",children:[e.jsx("p",{className:"text-xs text-cc-muted",children:"Configure authentication tokens for Claude Code and Codex. These are injected into sessions automatically."}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("label",{className:"block text-sm font-medium",htmlFor:"claude-code-token",children:"Claude Code OAuth Token"}),e.jsxs("p",{className:"text-xs text-cc-muted",children:["Run ",e.jsx("code",{className:"font-mono-code bg-cc-code-bg px-1 py-0.5 rounded text-cc-code-fg",children:"claude setup-token"})," in your terminal, then paste the token here."]}),e.jsx("input",{id:"claude-code-token",type:"password",value:H&&!We&&!h?"••••••••••••••••":h,onChange:t=>ve(t.target.value),onFocus:()=>Ae(!0),onBlur:()=>Ae(!1),placeholder:H?"Enter a new token to replace":"Paste token from claude setup-token",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow"}),e.jsx("p",{className:"text-xs text-cc-muted",children:H?"Claude Code token configured":"Claude Code token not configured"})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("label",{className:"block text-sm font-medium",htmlFor:"openai-api-key",children:"OpenAI API Key (Codex)"}),e.jsxs("p",{className:"text-xs text-cc-muted",children:["Used to authenticate Codex sessions. You can also use ",e.jsx("code",{className:"font-mono-code bg-cc-code-bg px-1 py-0.5 rounded text-cc-code-fg",children:"codex --login"})," for device-based auth."]}),e.jsx("input",{id:"openai-api-key",type:"password",value:Q&&!Je&&!f?"••••••••••••••••":f,onChange:t=>ke(t.target.value),onFocus:()=>Ue(!0),onBlur:()=>Ue(!1),placeholder:Q?"Enter a new key to replace":"sk-...",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow"}),e.jsx("p",{className:"text-xs text-cc-muted",children:Q?"OpenAI key configured":"OpenAI key not configured"})]}),Ce&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-error/10 border border-cc-error/20 text-xs text-cc-error",children:Ce}),Ye&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-success/10 border border-cc-success/20 text-xs text-cc-success",children:"Provider settings saved."}),e.jsx("button",{type:"button",disabled:_||!h.trim()&&!f.trim(),onClick:async()=>{we(!0),Se(""),G(!1);try{const t={};h.trim()&&(t.claudeCodeOAuthToken=h.trim()),f.trim()&&(t.openaiApiKey=f.trim());const c=await n.updateSettings(t);je(c.claudeCodeOAuthTokenConfigured),Ne(c.openaiApiKeyConfigured),ve(""),ke(""),G(!0),setTimeout(()=>G(!1),1800)}catch(t){Se(t instanceof Error?t.message:String(t))}finally{we(!1)}},className:`px-4 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${_||!h.trim()&&!f.trim()?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:_?"Saving...":"Save Provider Settings"})]})]}),e.jsxs("section",{id:"anthropic",ref:l("anthropic"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Anthropic"}),e.jsxs("form",{onSubmit:Ze,className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",htmlFor:"anthropic-key",children:"Anthropic API Key"}),e.jsx("input",{id:"anthropic-key",type:"password",value:d&&!qe&&!p?"••••••••••••••••":p,onChange:t=>{te(t.target.value),m(null)},onFocus:()=>be(!0),onBlur:()=>be(!1),placeholder:d?"Enter a new key to replace":"sk-ant-api03-...",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow"}),e.jsx("p",{className:"mt-1.5 text-xs text-cc-muted",children:"Auto-renaming is disabled until this key is configured."})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",htmlFor:"anthropic-model",children:"Anthropic Model"}),e.jsx("input",{id:"anthropic-model",type:"text",value:se,onChange:t=>ce(t.target.value),placeholder:"claude-sonnet-4-6",className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg placeholder:text-cc-muted focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow"})]}),oe&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-error/10 border border-cc-error/20 text-xs text-cc-error",children:oe}),ae&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-success/10 border border-cc-success/20 text-xs text-cc-success",children:"Settings saved."}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-xs text-cc-muted",children:w?"Loading...":d?"Anthropic key configured":"Anthropic key not configured"}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{type:"button",disabled:B||!p.trim(),onClick:async()=>{ye(!0),m(null);try{const t=await n.verifyAnthropicKey(p.trim());m(t),setTimeout(()=>m(null),5e3)}catch(t){m({valid:!1,error:t instanceof Error?t.message:String(t)}),setTimeout(()=>m(null),5e3)}finally{ye(!1)}},className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${B||!p.trim()?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:B?"Verifying...":"Verify"}),e.jsx("button",{type:"submit",disabled:g||w,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${g||w?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:g?"Saving...":"Save"})]})]}),k&&e.jsx("div",{className:`px-3 py-2 rounded-lg text-xs ${k.valid?"bg-cc-success/10 border border-cc-success/20 text-cc-success":"bg-cc-error/10 border border-cc-error/20 text-cc-error"}`,children:k.valid?"API key is valid.":`Invalid API key${k.error?`: ${k.error}`:"."}`})]})]}),e.jsxs("section",{id:"ai-validation",ref:l("ai-validation"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"AI Validation"}),e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-cc-muted leading-relaxed",children:"When enabled, an AI model evaluates tool calls before they execute. Safe operations are auto-approved, dangerous ones are blocked, and uncertain cases are shown to you with a recommendation. Requires an Anthropic API key. These settings serve as defaults for new sessions. Each session can override AI validation independently via the shield icon in the session header."}),e.jsxs("button",{type:"button",onClick:()=>z("aiValidationEnabled"),disabled:!d,className:`w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg transition-colors ${d?"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer":"bg-cc-hover text-cc-muted cursor-not-allowed opacity-60"}`,children:[e.jsx("span",{className:"text-sm",children:"AI Validation Mode"}),e.jsx("span",{className:`text-xs font-medium ${U&&d?"text-cc-success":"text-cc-muted"}`,children:U&&d?"On":"Off"})]}),!d&&e.jsx("p",{className:"text-[11px] text-cc-warning",children:"Configure an Anthropic API key above to enable AI validation."}),U&&d&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",onClick:()=>z("aiValidationAutoApprove"),className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg bg-cc-hover hover:bg-cc-active text-cc-fg transition-colors cursor-pointer",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm",children:"Auto-approve safe tools"}),e.jsx("p",{className:"text-[11px] text-cc-muted mt-0.5",children:"Automatically allow read-only tools and benign commands"})]}),e.jsx("span",{className:`text-xs font-medium ${$?"text-cc-success":"text-cc-muted"}`,children:$?"On":"Off"})]}),e.jsxs("button",{type:"button",onClick:()=>z("aiValidationAutoDeny"),className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg bg-cc-hover hover:bg-cc-active text-cc-fg transition-colors cursor-pointer",children:[e.jsxs("div",{children:[e.jsx("span",{className:"text-sm",children:"Auto-deny dangerous tools"}),e.jsx("p",{className:"text-[11px] text-cc-muted mt-0.5",children:"Automatically block destructive commands like rm -rf"})]}),e.jsx("span",{className:`text-xs font-medium ${F?"text-cc-success":"text-cc-muted"}`,children:F?"On":"Off"})]})]})]})]}),e.jsxs("section",{id:"updates",ref:l("updates"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Updates"}),e.jsxs("div",{className:"space-y-3",children:[a?e.jsxs("p",{className:"text-xs text-cc-muted",children:["Current version: v",a.currentVersion,a.latestVersion?` • Latest: v${a.latestVersion}`:"",a.channel==="prerelease"?" (prerelease)":""]}):e.jsx("p",{className:"text-xs text-cc-muted",children:"Version information not loaded yet."}),e.jsxs("div",{children:[e.jsx("span",{id:"update-channel-label",className:"block text-sm font-medium mb-1.5",children:"Update Channel"}),e.jsxs("div",{className:"flex gap-1",role:"radiogroup","aria-labelledby":"update-channel-label",children:[e.jsx("button",{type:"button",role:"radio","aria-checked":x==="stable",onClick:async()=>{if(x!=="stable"){v("stable");try{await n.updateSettings({updateChannel:"stable"})}catch{v("prerelease");return}try{const t=await n.forceCheckForUpdate();I(t)}catch{}}},className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${x==="stable"?"bg-cc-primary text-white":"bg-cc-hover text-cc-muted hover:text-cc-fg hover:bg-cc-active"}`,children:"Stable"}),e.jsx("button",{type:"button",role:"radio","aria-checked":x==="prerelease",onClick:async()=>{if(x!=="prerelease"){v("prerelease");try{await n.updateSettings({updateChannel:"prerelease"})}catch{v("stable");return}try{const t=await n.forceCheckForUpdate();I(t)}catch{}}},className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${x==="prerelease"?"bg-cc-primary text-white":"bg-cc-hover text-cc-muted hover:text-cc-fg hover:bg-cc-active"}`,children:"Prerelease"})]}),e.jsx("p",{className:"mt-1.5 text-xs text-cc-muted",children:x==="prerelease"?"Tracking prerelease channel. You will receive preview builds from the latest main branch.":"Tracking stable channel. You will only receive versioned releases."})]}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("span",{className:"block text-sm font-medium",children:"Auto-update Docker image"}),e.jsx("p",{className:"mt-0.5 text-xs text-cc-muted",children:"Automatically re-pull the sandbox Docker image when updating The Companion"})]}),e.jsx("button",{type:"button",role:"switch","aria-checked":S,onClick:async()=>{const t=!S;V(t);try{await n.updateSettings({dockerAutoUpdate:t})}catch{V(!t)}},className:`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors ${S?"bg-cc-primary":"bg-cc-hover"}`,children:e.jsx("span",{className:`pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform transition-transform ${S?"translate-x-5":"translate-x-0"}`})})]}),me&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-error/10 border border-cc-error/20 text-xs text-cc-error",children:me}),pe&&e.jsx("div",{className:"px-3 py-2 rounded-lg bg-cc-success/10 border border-cc-success/20 text-xs text-cc-success",children:pe}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx("button",{type:"button",onClick:et,disabled:P,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${P?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:P?"Checking...":"Check for updates"}),a!=null&&a.isServiceMode?e.jsx("button",{type:"button",onClick:tt,disabled:O||a.updateInProgress||!a.updateAvailable,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${O||a.updateInProgress||!a.updateAvailable?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:O||a.updateInProgress?"Updating...":"Update & Restart"}):e.jsxs("p",{className:"text-xs text-cc-muted self-center",children:["Install service mode with ",e.jsx("code",{className:"font-mono-code bg-cc-code-bg px-1 py-0.5 rounded text-cc-code-fg",children:"the-companion install"})," to enable one-click updates."]})]})]})]}),e.jsxs("section",{id:"telemetry",ref:l("telemetry"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Telemetry"}),e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-cc-muted",children:"Anonymous product analytics and crash reports via PostHog to improve reliability."}),e.jsxs("button",{type:"button",onClick:()=>{const t=!he;ot(t),Ge(t)},className:"w-full flex items-center justify-between px-3 py-3 min-h-[44px] rounded-lg text-sm bg-cc-hover text-cc-fg hover:bg-cc-active transition-colors cursor-pointer",children:[e.jsx("span",{children:"Usage analytics and errors"}),e.jsx("span",{className:"text-xs text-cc-muted",children:he?"On":"Off"})]}),e.jsx("p",{className:"text-xs text-cc-muted",children:"Browser Do Not Track is respected automatically."})]})]}),e.jsxs("section",{id:"environments",ref:l("environments"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Environments"}),e.jsxs("div",{className:"space-y-3",children:[e.jsx("p",{className:"text-xs text-cc-muted",children:"Manage reusable environment profiles used when creating sessions."}),e.jsx("button",{type:"button",onClick:()=>{window.location.hash="#/environments"},className:"px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium bg-cc-primary hover:bg-cc-primary-hover text-white transition-colors cursor-pointer",children:"Open Environments Page"})]})]})]})})]})]})}export{it as SettingsPage};