@hellcoder/companion 0.113.4 → 0.113.5-preview.20260730100656.ecb60c9

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 (26) hide show
  1. package/dist/assets/{AgentsPage-B2NtxJig.js → AgentsPage-Ct9Hvk1J.js} +1 -1
  2. package/dist/assets/{CronManager-CES2-E0_.js → CronManager-B4XnCjHZ.js} +1 -1
  3. package/dist/assets/{DashboardPage-aFf4uQfU.js → DashboardPage-CfgavOrA.js} +1 -1
  4. package/dist/assets/{IntegrationsPage-C_tZ2P0K.js → IntegrationsPage-CmRcLZ2Y.js} +1 -1
  5. package/dist/assets/{LinearOAuthSettingsPage-DUQik5kh.js → LinearOAuthSettingsPage-D5fdWchh.js} +1 -1
  6. package/dist/assets/{LinearSettingsPage-DFh8jfhh.js → LinearSettingsPage-BHrdSu6B.js} +1 -1
  7. package/dist/assets/{Playground-C4t0-Zia.js → Playground-BDjcdHjZ.js} +1 -1
  8. package/dist/assets/{PromptsPage-Ma4gA0R7.js → PromptsPage-C4XeLDqH.js} +1 -1
  9. package/dist/assets/{RunsPage-jk2yW392.js → RunsPage-9_OGNofF.js} +1 -1
  10. package/dist/assets/{SandboxManager-BA6-RlkR.js → SandboxManager-C0UI27DS.js} +1 -1
  11. package/dist/assets/SettingsPage-1ggRq3DY.js +1 -0
  12. package/dist/assets/{TailscalePage-CZ6V3kc7.js → TailscalePage-9EDSWy6L.js} +1 -1
  13. package/dist/assets/{index-B1Oejky6.js → index-DRO5Cupx.js} +3 -3
  14. package/dist/assets/{sw-register-B9GK06Mo.js → sw-register-UeoQRzHq.js} +1 -1
  15. package/dist/index.html +1 -1
  16. package/dist/sw.js +1 -1
  17. package/package.json +2 -1
  18. package/server/claude-adapter.ts +9 -7
  19. package/server/claude-sdk-adapter.test.ts +298 -0
  20. package/server/claude-sdk-adapter.ts +394 -0
  21. package/server/cli-launcher.ts +110 -0
  22. package/server/routes/settings-routes.ts +10 -0
  23. package/server/routes.test.ts +27 -0
  24. package/server/settings-manager.test.ts +21 -0
  25. package/server/settings-manager.ts +16 -1
  26. package/dist/assets/SettingsPage-BehGv-KV.js +0 -1
@@ -1161,6 +1161,7 @@ describe("GET /api/sessions/:id/archive-info", () => {
1161
1161
  keepaliveDetachedSessions: false,
1162
1162
  wedgeKillEnabled: true,
1163
1163
  silenceProbeEnabled: true,
1164
+ claudeTransport: "stdio",
1164
1165
  updatedAt: 0,
1165
1166
  });
1166
1167
  const res = await app.request("/api/sessions/s1/archive-info", { method: "GET" });
@@ -1539,6 +1540,7 @@ describe("GET /api/settings", () => {
1539
1540
  keepaliveDetachedSessions: false,
1540
1541
  wedgeKillEnabled: true,
1541
1542
  silenceProbeEnabled: true,
1543
+ claudeTransport: "stdio",
1542
1544
  updatedAt: 123,
1543
1545
  });
1544
1546
 
@@ -1575,6 +1577,7 @@ describe("GET /api/settings", () => {
1575
1577
  keepaliveDetachedSessions: false,
1576
1578
  wedgeKillEnabled: true,
1577
1579
  silenceProbeEnabled: true,
1580
+ claudeTransport: "stdio",
1578
1581
  });
1579
1582
  });
1580
1583
 
@@ -1611,6 +1614,7 @@ describe("GET /api/settings", () => {
1611
1614
  keepaliveDetachedSessions: false,
1612
1615
  wedgeKillEnabled: true,
1613
1616
  silenceProbeEnabled: true,
1617
+ claudeTransport: "stdio",
1614
1618
  updatedAt: 123,
1615
1619
  });
1616
1620
 
@@ -1647,6 +1651,7 @@ describe("GET /api/settings", () => {
1647
1651
  keepaliveDetachedSessions: false,
1648
1652
  wedgeKillEnabled: true,
1649
1653
  silenceProbeEnabled: true,
1654
+ claudeTransport: "stdio",
1650
1655
  });
1651
1656
  });
1652
1657
 
@@ -1684,6 +1689,7 @@ describe("GET /api/settings", () => {
1684
1689
  keepaliveDetachedSessions: false,
1685
1690
  wedgeKillEnabled: true,
1686
1691
  silenceProbeEnabled: true,
1692
+ claudeTransport: "stdio",
1687
1693
  updatedAt: 100,
1688
1694
  });
1689
1695
 
@@ -1729,6 +1735,7 @@ describe("PUT /api/settings", () => {
1729
1735
  keepaliveDetachedSessions: false,
1730
1736
  wedgeKillEnabled: true,
1731
1737
  silenceProbeEnabled: true,
1738
+ claudeTransport: "stdio",
1732
1739
  updatedAt: 456,
1733
1740
  });
1734
1741
 
@@ -1787,6 +1794,7 @@ describe("PUT /api/settings", () => {
1787
1794
  keepaliveDetachedSessions: false,
1788
1795
  wedgeKillEnabled: true,
1789
1796
  silenceProbeEnabled: true,
1797
+ claudeTransport: "stdio",
1790
1798
  });
1791
1799
  });
1792
1800
 
@@ -1823,6 +1831,7 @@ describe("PUT /api/settings", () => {
1823
1831
  keepaliveDetachedSessions: false,
1824
1832
  wedgeKillEnabled: true,
1825
1833
  silenceProbeEnabled: true,
1834
+ claudeTransport: "stdio",
1826
1835
  updatedAt: 789,
1827
1836
  });
1828
1837
 
@@ -1876,6 +1885,7 @@ describe("PUT /api/settings", () => {
1876
1885
  keepaliveDetachedSessions: false,
1877
1886
  wedgeKillEnabled: true,
1878
1887
  silenceProbeEnabled: true,
1888
+ claudeTransport: "stdio",
1879
1889
  updatedAt: 999,
1880
1890
  });
1881
1891
 
@@ -2029,6 +2039,7 @@ describe("PUT /api/settings", () => {
2029
2039
  keepaliveDetachedSessions: false,
2030
2040
  wedgeKillEnabled: true,
2031
2041
  silenceProbeEnabled: true,
2042
+ claudeTransport: "stdio",
2032
2043
  updatedAt: 500,
2033
2044
  });
2034
2045
 
@@ -2288,6 +2299,7 @@ describe("GET /api/linear/issues", () => {
2288
2299
  keepaliveDetachedSessions: false,
2289
2300
  wedgeKillEnabled: true,
2290
2301
  silenceProbeEnabled: true,
2302
+ claudeTransport: "stdio",
2291
2303
  updatedAt: 0,
2292
2304
  });
2293
2305
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2331,6 +2343,7 @@ describe("GET /api/linear/issues", () => {
2331
2343
  keepaliveDetachedSessions: false,
2332
2344
  wedgeKillEnabled: true,
2333
2345
  silenceProbeEnabled: true,
2346
+ claudeTransport: "stdio",
2334
2347
  updatedAt: 0,
2335
2348
  });
2336
2349
 
@@ -2427,6 +2440,7 @@ describe("GET /api/linear/issues", () => {
2427
2440
  keepaliveDetachedSessions: false,
2428
2441
  wedgeKillEnabled: true,
2429
2442
  silenceProbeEnabled: true,
2443
+ claudeTransport: "stdio",
2430
2444
  updatedAt: 0,
2431
2445
  });
2432
2446
 
@@ -2530,6 +2544,7 @@ describe("GET /api/linear/issues", () => {
2530
2544
  keepaliveDetachedSessions: false,
2531
2545
  wedgeKillEnabled: true,
2532
2546
  silenceProbeEnabled: true,
2547
+ claudeTransport: "stdio",
2533
2548
  updatedAt: 0,
2534
2549
  });
2535
2550
 
@@ -2598,6 +2613,7 @@ describe("GET /api/linear/connection", () => {
2598
2613
  keepaliveDetachedSessions: false,
2599
2614
  wedgeKillEnabled: true,
2600
2615
  silenceProbeEnabled: true,
2616
+ claudeTransport: "stdio",
2601
2617
  updatedAt: 0,
2602
2618
  });
2603
2619
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2641,6 +2657,7 @@ describe("GET /api/linear/connection", () => {
2641
2657
  keepaliveDetachedSessions: false,
2642
2658
  wedgeKillEnabled: true,
2643
2659
  silenceProbeEnabled: true,
2660
+ claudeTransport: "stdio",
2644
2661
  updatedAt: 0,
2645
2662
  });
2646
2663
 
@@ -2706,6 +2723,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2706
2723
  keepaliveDetachedSessions: false,
2707
2724
  wedgeKillEnabled: true,
2708
2725
  silenceProbeEnabled: true,
2726
+ claudeTransport: "stdio",
2709
2727
  updatedAt: 0,
2710
2728
  });
2711
2729
 
@@ -2753,6 +2771,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2753
2771
  keepaliveDetachedSessions: false,
2754
2772
  wedgeKillEnabled: true,
2755
2773
  silenceProbeEnabled: true,
2774
+ claudeTransport: "stdio",
2756
2775
  updatedAt: 0,
2757
2776
  });
2758
2777
 
@@ -2799,6 +2818,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2799
2818
  keepaliveDetachedSessions: false,
2800
2819
  wedgeKillEnabled: true,
2801
2820
  silenceProbeEnabled: true,
2821
+ claudeTransport: "stdio",
2802
2822
  updatedAt: 0,
2803
2823
  });
2804
2824
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2847,6 +2867,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2847
2867
  keepaliveDetachedSessions: false,
2848
2868
  wedgeKillEnabled: true,
2849
2869
  silenceProbeEnabled: true,
2870
+ claudeTransport: "stdio",
2850
2871
  updatedAt: 0,
2851
2872
  });
2852
2873
 
@@ -2929,6 +2950,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2929
2950
  keepaliveDetachedSessions: false,
2930
2951
  wedgeKillEnabled: true,
2931
2952
  silenceProbeEnabled: true,
2953
+ claudeTransport: "stdio",
2932
2954
  updatedAt: 0,
2933
2955
  });
2934
2956
 
@@ -2990,6 +3012,7 @@ describe("GET /api/linear/projects", () => {
2990
3012
  keepaliveDetachedSessions: false,
2991
3013
  wedgeKillEnabled: true,
2992
3014
  silenceProbeEnabled: true,
3015
+ claudeTransport: "stdio",
2993
3016
  updatedAt: 0,
2994
3017
  });
2995
3018
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -3033,6 +3056,7 @@ describe("GET /api/linear/projects", () => {
3033
3056
  keepaliveDetachedSessions: false,
3034
3057
  wedgeKillEnabled: true,
3035
3058
  silenceProbeEnabled: true,
3059
+ claudeTransport: "stdio",
3036
3060
  updatedAt: 0,
3037
3061
  });
3038
3062
 
@@ -3106,6 +3130,7 @@ describe("GET /api/linear/project-issues", () => {
3106
3130
  keepaliveDetachedSessions: false,
3107
3131
  wedgeKillEnabled: true,
3108
3132
  silenceProbeEnabled: true,
3133
+ claudeTransport: "stdio",
3109
3134
  updatedAt: 0,
3110
3135
  });
3111
3136
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -3149,6 +3174,7 @@ describe("GET /api/linear/project-issues", () => {
3149
3174
  keepaliveDetachedSessions: false,
3150
3175
  wedgeKillEnabled: true,
3151
3176
  silenceProbeEnabled: true,
3177
+ claudeTransport: "stdio",
3152
3178
  updatedAt: 0,
3153
3179
  });
3154
3180
 
@@ -3237,6 +3263,7 @@ describe("GET /api/linear/project-issues", () => {
3237
3263
  keepaliveDetachedSessions: false,
3238
3264
  wedgeKillEnabled: true,
3239
3265
  silenceProbeEnabled: true,
3266
+ claudeTransport: "stdio",
3240
3267
  updatedAt: 0,
3241
3268
  });
3242
3269
 
@@ -56,6 +56,7 @@ describe("settings-manager", () => {
56
56
  keepaliveDetachedSessions: false,
57
57
  wedgeKillEnabled: true,
58
58
  silenceProbeEnabled: true,
59
+ claudeTransport: "stdio",
59
60
  cliBridgeMode: "loopback",
60
61
  claudeBridgeMode: "none",
61
62
  claudeBridgeIngressUrl: "",
@@ -123,6 +124,7 @@ describe("settings-manager", () => {
123
124
  keepaliveDetachedSessions: false,
124
125
  wedgeKillEnabled: true,
125
126
  silenceProbeEnabled: true,
127
+ claudeTransport: "stdio",
126
128
  cliBridgeMode: "loopback",
127
129
  claudeBridgeMode: "none",
128
130
  claudeBridgeIngressUrl: "",
@@ -216,6 +218,7 @@ describe("settings-manager", () => {
216
218
  keepaliveDetachedSessions: false,
217
219
  wedgeKillEnabled: true,
218
220
  silenceProbeEnabled: true,
221
+ claudeTransport: "stdio",
219
222
  cliBridgeMode: "loopback",
220
223
  claudeBridgeMode: "none",
221
224
  claudeBridgeIngressUrl: "",
@@ -310,4 +313,22 @@ describe("settings-manager", () => {
310
313
  const updated = updateSettings({ anthropicModel: "claude-haiku-3" });
311
314
  expect(updated.publicUrl).toBe("https://example.com");
312
315
  });
316
+
317
+ // claudeTransport selects which layer drives Claude sessions (stdio bridge
318
+ // vs Agent SDK). It must default to "stdio", persist a valid "sdk" value,
319
+ // and normalize anything invalid back to "stdio" so a corrupt settings file
320
+ // can never strand sessions on a nonexistent transport.
321
+ it("defaults claudeTransport to stdio and persists sdk", () => {
322
+ expect(getSettings().claudeTransport).toBe("stdio");
323
+ const updated = updateSettings({ claudeTransport: "sdk" });
324
+ expect(updated.claudeTransport).toBe("sdk");
325
+ // Survives an unrelated update.
326
+ expect(updateSettings({ anthropicModel: "claude-haiku-3" }).claudeTransport).toBe("sdk");
327
+ });
328
+
329
+ it("normalizes an invalid claudeTransport value from disk to stdio", () => {
330
+ writeFileSync(settingsPath, JSON.stringify({ claudeTransport: "warp-drive" }));
331
+ _resetForTest(settingsPath);
332
+ expect(getSettings().claudeTransport).toBe("stdio");
333
+ });
313
334
  });
@@ -124,6 +124,18 @@ export interface CompanionSettings {
124
124
  * with no error surfaced. Disable to leave silent sessions alone.
125
125
  */
126
126
  silenceProbeEnabled: boolean;
127
+ /**
128
+ * Which transport drives Claude Code sessions.
129
+ *
130
+ * "stdio" (default): companion spawns `claude --print --input-format
131
+ * stream-json` and bridges its pipes itself (ClaudeAdapter.attachStdio).
132
+ * "sdk": sessions run through the official @anthropic-ai/claude-agent-sdk
133
+ * (SdkClaudeAdapter) — same binary, same subscription auth, but Anthropic's
134
+ * maintained protocol layer instead of ours. Applied at spawn/relaunch time,
135
+ * so flipping this and hitting Reconnect migrates a live session between
136
+ * transports via --resume. Claude sessions only; Codex is unaffected.
137
+ */
138
+ claudeTransport?: "stdio" | "sdk";
127
139
  /** See CliBridgeMode. Defaults to "loopback". Optional in fixtures; normalize() applies the default. */
128
140
  cliBridgeMode?: CliBridgeMode;
129
141
  /** See ClaudeBridgeMode. Defaults to "none". Persists across companion restarts. */
@@ -171,6 +183,7 @@ let settings: CompanionSettings = {
171
183
  keepaliveDetachedSessions: false,
172
184
  wedgeKillEnabled: true,
173
185
  silenceProbeEnabled: true,
186
+ claudeTransport: "stdio",
174
187
  cliBridgeMode: "loopback",
175
188
  claudeBridgeMode: "none",
176
189
  claudeBridgeIngressUrl: "",
@@ -225,6 +238,7 @@ function normalize(raw: Partial<CompanionSettings> | null | undefined): Companio
225
238
  keepaliveDetachedSessions: typeof raw?.keepaliveDetachedSessions === "boolean" ? raw.keepaliveDetachedSessions : false,
226
239
  wedgeKillEnabled: typeof raw?.wedgeKillEnabled === "boolean" ? raw.wedgeKillEnabled : true,
227
240
  silenceProbeEnabled: typeof raw?.silenceProbeEnabled === "boolean" ? raw.silenceProbeEnabled : true,
241
+ claudeTransport: raw?.claudeTransport === "sdk" ? "sdk" : "stdio",
228
242
  cliBridgeMode: raw?.cliBridgeMode === "jsonHandoff" ? "jsonHandoff" : "loopback",
229
243
  claudeBridgeMode: raw?.claudeBridgeMode === "patched" ? "patched" : "none",
230
244
  claudeBridgeIngressUrl: typeof raw?.claudeBridgeIngressUrl === "string" ? raw.claudeBridgeIngressUrl : "",
@@ -258,7 +272,7 @@ export function getSettings(): CompanionSettings {
258
272
  }
259
273
 
260
274
  export function updateSettings(
261
- patch: Partial<Pick<CompanionSettings, "anthropicApiKey" | "anthropicModel" | "claudeCodeOAuthToken" | "openaiApiKey" | "onboardingCompleted" | "linearApiKey" | "linearAutoTransition" | "linearAutoTransitionStateId" | "linearAutoTransitionStateName" | "linearArchiveTransition" | "linearArchiveTransitionStateId" | "linearArchiveTransitionStateName" | "linearOAuthClientId" | "linearOAuthClientSecret" | "linearOAuthWebhookSecret" | "linearOAuthAccessToken" | "linearOAuthRefreshToken" | "aiValidationEnabled" | "aiValidationAutoApprove" | "aiValidationAutoDeny" | "dashboardEnabled" | "dashboardModel" | "dashboardRunHour" | "dashboardMaxSessionsPerRun" | "publicUrl" | "updateChannel" | "dockerAutoUpdate" | "proactiveKeepaliveEnabled" | "keepaliveDetachedSessions" | "wedgeKillEnabled" | "silenceProbeEnabled" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
275
+ patch: Partial<Pick<CompanionSettings, "anthropicApiKey" | "anthropicModel" | "claudeCodeOAuthToken" | "openaiApiKey" | "onboardingCompleted" | "linearApiKey" | "linearAutoTransition" | "linearAutoTransitionStateId" | "linearAutoTransitionStateName" | "linearArchiveTransition" | "linearArchiveTransitionStateId" | "linearArchiveTransitionStateName" | "linearOAuthClientId" | "linearOAuthClientSecret" | "linearOAuthWebhookSecret" | "linearOAuthAccessToken" | "linearOAuthRefreshToken" | "aiValidationEnabled" | "aiValidationAutoApprove" | "aiValidationAutoDeny" | "dashboardEnabled" | "dashboardModel" | "dashboardRunHour" | "dashboardMaxSessionsPerRun" | "publicUrl" | "updateChannel" | "dockerAutoUpdate" | "proactiveKeepaliveEnabled" | "keepaliveDetachedSessions" | "wedgeKillEnabled" | "silenceProbeEnabled" | "claudeTransport" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
262
276
  ): CompanionSettings {
263
277
  ensureLoaded();
264
278
  settings = normalize({
@@ -293,6 +307,7 @@ export function updateSettings(
293
307
  keepaliveDetachedSessions: patch.keepaliveDetachedSessions ?? settings.keepaliveDetachedSessions,
294
308
  wedgeKillEnabled: patch.wedgeKillEnabled ?? settings.wedgeKillEnabled,
295
309
  silenceProbeEnabled: patch.silenceProbeEnabled ?? settings.silenceProbeEnabled,
310
+ claudeTransport: patch.claudeTransport ?? settings.claudeTransport,
296
311
  cliBridgeMode: patch.cliBridgeMode ?? settings.cliBridgeMode,
297
312
  claudeBridgeMode: patch.claudeBridgeMode ?? settings.claudeBridgeMode,
298
313
  claudeBridgeIngressUrl: patch.claudeBridgeIngressUrl ?? settings.claudeBridgeIngressUrl,
@@ -1 +0,0 @@
1
- import{r as s,u as o,g as gt,a as n,j as e,n as vt,b as yt,s as jt}from"./index-B1Oejky6.js";const ue=[{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:"dashboard",label:"Dashboard"},{id:"updates",label:"Updates"},{id:"telemetry",label:"Telemetry"},{id:"environments",label:"Environments"}];function wt({embedded:xe=!1}){const[p,pe]=s.useState(""),[me,he]=s.useState("claude-sonnet-4-6"),[d,be]=s.useState(!1),[k,Ze]=s.useState(!0),[f,S]=s.useState(!1),[fe,g]=s.useState(""),[ge,v]=s.useState(!1),et=o(t=>t.darkMode),tt=o(t=>t.toggleDarkMode),ve=o(t=>t.diffBase),st=o(t=>t.setDiffBase),at=o(t=>t.notificationSound),nt=o(t=>t.toggleNotificationSound),ye=o(t=>t.notificationDesktop),je=o(t=>t.setNotificationDesktop),r=o(t=>t.updateInfo),O=o(t=>t.setUpdateInfo),ct=o(t=>t.setUpdateOverlayActive),ot=typeof Notification<"u",[x,y]=s.useState("stable"),[C,V]=s.useState(!1),[A,D]=s.useState(!0),[E,L]=s.useState(!0),[U,K]=s.useState(!0),[Ne,M]=s.useState("loopback"),[$,we]=s.useState(!1),[F,ke]=s.useState(!1),[Se,j]=s.useState(""),[Ce,T]=s.useState(""),[Ae,rt]=s.useState(gt()),[I,H]=s.useState(!1),[B,Q]=s.useState(!0),[_,W]=s.useState(!1),[G,q]=s.useState(!1),[Ee,Y]=s.useState("claude-haiku-4-5"),[Ue,z]=s.useState(3),[Te,J]=s.useState(30),[P,X]=s.useState(""),[Ie,Pe]=s.useState("general"),[it,Re]=s.useState(!1),[Z,Oe]=s.useState(!1),[N,m]=s.useState(null),[h,Ve]=s.useState(""),[ee,De]=s.useState(!1),[b,Le]=s.useState(""),[te,Ke]=s.useState(!1),[se,Me]=s.useState(!1),[lt,ae]=s.useState(!1),[$e,Fe]=s.useState(""),[dt,He]=s.useState(!1),[ut,Be]=s.useState(!1),[w,Qe]=s.useState(null),[ne,_e]=s.useState(!1),[u,We]=s.useState(null),[R,xt]=s.useState(0),[ce,Ge]=s.useState(!1),[oe,qe]=s.useState(!1),[pt,Ye]=s.useState(!1),ze=s.useRef(null),re=s.useRef({});s.useEffect(()=>{const t=ze.current;if(!t)return;const a=new IntersectionObserver(c=>{var Xe;let i=null;for(const de of c)de.isIntersecting&&(!i||de.boundingClientRect.top<i.boundingClientRect.top)&&(i=de);(Xe=i==null?void 0:i.target)!=null&&Xe.id&&Pe(i.target.id)},{root:t,rootMargin:"-10% 0px -70% 0px",threshold:0});for(const c of ue){const i=re.current[c.id];i&&a.observe(i)}return()=>a.disconnect()},[k]);const Je=s.useCallback(t=>{Pe(t);const a=re.current[t];a&&a.scrollIntoView({behavior:"smooth",block:"start"})},[]);s.useEffect(()=>{n.getSettings().then(t=>{be(t.anthropicApiKeyConfigured),De(t.claudeCodeOAuthTokenConfigured),Ke(t.openaiApiKeyConfigured),he(t.anthropicModel||"claude-sonnet-4-6"),typeof t.aiValidationEnabled=="boolean"&&H(t.aiValidationEnabled),typeof t.aiValidationAutoApprove=="boolean"&&Q(t.aiValidationAutoApprove),typeof t.aiValidationAutoDeny=="boolean"&&W(t.aiValidationAutoDeny),typeof t.dashboardEnabled=="boolean"&&q(t.dashboardEnabled),typeof t.dashboardModel=="string"&&t.dashboardModel&&Y(t.dashboardModel),typeof t.dashboardRunHour=="number"&&z(t.dashboardRunHour),typeof t.dashboardMaxSessionsPerRun=="number"&&J(t.dashboardMaxSessionsPerRun),(t.updateChannel==="stable"||t.updateChannel==="prerelease")&&y(t.updateChannel),typeof t.dockerAutoUpdate=="boolean"&&V(t.dockerAutoUpdate),typeof t.proactiveKeepaliveEnabled=="boolean"&&D(t.proactiveKeepaliveEnabled),typeof t.wedgeKillEnabled=="boolean"&&L(t.wedgeKillEnabled),typeof t.silenceProbeEnabled=="boolean"&&K(t.silenceProbeEnabled),(t.cliBridgeMode==="loopback"||t.cliBridgeMode==="jsonHandoff")&&M(t.cliBridgeMode),typeof t.publicUrl=="string"&&(X(t.publicUrl),o.getState().setPublicUrl(t.publicUrl))}).catch(t=>g(t instanceof Error?t.message:String(t))).finally(()=>Ze(!1)),n.getAuthToken().then(t=>Qe(t.token)).catch(()=>{})},[]);async function mt(t){t.preventDefault(),S(!0),g(""),v(!1);try{const a=p.trim(),c={anthropicModel:me.trim()||"claude-sonnet-4-6"};a&&(c.anthropicApiKey=a);const i=await n.updateSettings(c);be(i.anthropicApiKeyConfigured),pe(""),v(!0),setTimeout(()=>v(!1),1800)}catch(a){g(a instanceof Error?a.message:String(a))}finally{S(!1)}}async function ie(t){const a=t==="aiValidationEnabled"?I:t==="aiValidationAutoApprove"?B:_,c=!a;t==="aiValidationEnabled"?H(c):t==="aiValidationAutoApprove"?Q(c):W(c);try{await n.updateSettings({[t]:c})}catch{t==="aiValidationEnabled"?H(a):t==="aiValidationAutoApprove"?Q(a):W(a)}}async function ht(){const t=G;q(!t);try{await n.updateSettings({dashboardEnabled:!t})}catch{q(t)}}async function le(t,a){try{await n.updateSettings(t)}catch{a()}}async function bt(){we(!0),j(""),T("");try{const t=await n.forceCheckForUpdate();O(t),t.updateAvailable&&t.latestVersion?j(`Update v${t.latestVersion} is available.`):j("You are up to date.")}catch(t){T(t instanceof Error?t.message:String(t))}finally{we(!1)}}async function ft(){ke(!0),j(""),T("");try{localStorage.setItem("companion_docker_prompt_pending","1");const t=await n.triggerUpdate();j(t.message),ct(!0)}catch(t){localStorage.removeItem("companion_docker_prompt_pending"),T(t instanceof Error?t.message:String(t)),ke(!1)}}const l=s.useCallback(t=>a=>{re.current[t]=a},[]);return e.jsxs("div",{className:`${xe?"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."})]}),!xe&&e.jsx("button",{onClick:()=>{const t=o.getState().currentSessionId;t?vt(t):yt()},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:ue.map(t=>e.jsx("button",{type:"button",onClick:()=>Je(t.id),className:`shrink-0 px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${Ie===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:ue.map(t=>e.jsx("button",{type:"button",onClick:()=>Je(t.id),className:`text-left px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${Ie===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:ze,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:tt,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:et?"Dark":"Light"})]}),e.jsxs("button",{type:"button",onClick:()=>st(ve==="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:ve==="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:Ne,onChange:async t=>{const a=t.target.value==="jsonHandoff"?"jsonHandoff":"loopback",c=Ne;M(a);try{await n.updateSettings({cliBridgeMode:a})}catch{M(c)}},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("div",{className:"pt-3 border-t border-cc-border",children:[e.jsxs("button",{type:"button",role:"switch","aria-checked":A,"aria-label":"Proactive keepalive relaunch",onClick:async()=>{const t=!A,a=A;D(t);try{await n.updateSettings({proactiveKeepaliveEnabled:t})}catch{D(a)}},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:"Proactive keepalive relaunch"}),e.jsx("span",{className:"text-xs text-cc-muted",children:A?"On":"Off"})]}),e.jsx("p",{className:"mt-1 text-xs text-cc-muted px-1",children:"When on (default), a CLI process that exits unexpectedly with no browser attached is automatically relaunched to keep long-running sessions (agents, cron) alive. Turn off to experiment with letting dead sessions stay dead."})]}),e.jsxs("div",{className:"pt-3 border-t border-cc-border",children:[e.jsxs("button",{type:"button",role:"switch","aria-checked":E,"aria-label":"Kill wedged CLI processes",onClick:async()=>{const t=!E,a=E;L(t);try{await n.updateSettings({wedgeKillEnabled:t})}catch{L(a)}},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:"Kill wedged CLI processes"}),e.jsx("span",{className:"text-xs text-cc-muted",children:E?"On":"Off"})]}),e.jsx("p",{className:"mt-1 text-xs text-cc-muted px-1",children:"When on (default), a CLI whose output stream closes but which does not exit is treated as wedged and killed so the session can recover. This was built for older CLI behaviour, and every process sampled since has turned out to be healthy — usually one supervising a background command. Turn off to test whether the wedge still happens at all: sessions still relaunch, but processes are left to exit on their own instead of being terminated."})]}),e.jsxs("div",{className:"pt-3 border-t border-cc-border",children:[e.jsxs("button",{type:"button",role:"switch","aria-checked":U,"aria-label":"Probe silent CLI processes",onClick:async()=>{const t=!U,a=U;K(t);try{await n.updateSettings({silenceProbeEnabled:t})}catch{K(a)}},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:"Probe silent CLI processes"}),e.jsx("span",{className:"text-xs text-cc-muted",children:U?"On":"Off"})]}),e.jsx("p",{className:"mt-1 text-xs text-cc-muted px-1",children:"When on (default), a CLI that stops responding while its output stream stays open is sent a liveness probe after ~2 minutes of silence. If it does not answer within a further minute the session is relaunched and the in-flight turn replayed. Without this such a session hangs indefinitely showing “Still working…” with no error, because nothing ever closes the stream for companion to notice."})]})]})]}),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:P,onChange:t=>X(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:P?`Using: ${P}`:`Fallback: ${typeof window<"u"?window.location.origin:"http://localhost:3456"}`})]}),e.jsx("button",{type:"button",onClick:async()=>{S(!0),g("");try{const t=await n.updateSettings({publicUrl:P.trim()});X(t.publicUrl),o.getState().setPublicUrl(t.publicUrl),v(!0),setTimeout(()=>v(!1),1800)}catch(t){g(t instanceof Error?t.message:String(t))}finally{S(!1)}},disabled:f,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:f?"Saving...":ge?"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:w?ne?w:"••••••••••••••••":e.jsx("span",{className:"text-cc-muted",children:"Loading..."})}),e.jsx("button",{type:"button",onClick:()=>_e(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:ne?"Hide token":"Show token",children:ne?"Hide":"Show"}),e.jsx("button",{type:"button",onClick:()=>{w&&navigator.clipboard.writeText(w).then(()=>{Ye(!0),setTimeout(()=>Ye(!1),1500)})},disabled:!w,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:pt?"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,a)=>e.jsx("button",{type:"button",onClick:()=>xt(a),className:`px-3 py-1.5 rounded-md text-xs font-medium transition-colors cursor-pointer ${a===R?"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[R].qrDataUrl,alt:`QR code for ${u[R].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[R].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()=>{Ge(!0);try{const t=await n.getAuthQr();We(t.qrCodes)}catch{}finally{Ge(!1)}},disabled:ce,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${ce?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:ce?"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.")){qe(!0);try{const t=await n.regenerateAuthToken();Qe(t.token),_e(!0),We(null)}catch{}finally{qe(!1)}}},disabled:oe,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${oe?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-error/10 hover:bg-cc-error/20 text-cc-error cursor-pointer"}`,children:oe?"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:nt,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:at?"On":"Off"})]}),ot&&e.jsxs("button",{type:"button",onClick:async()=>{if(ye)je(!1);else{if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return;je(!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:ye?"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:ee&&!dt&&!h?"••••••••••••••••":h,onChange:t=>Ve(t.target.value),onFocus:()=>He(!0),onBlur:()=>He(!1),placeholder:ee?"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:ee?"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:te&&!ut&&!b?"••••••••••••••••":b,onChange:t=>Le(t.target.value),onFocus:()=>Be(!0),onBlur:()=>Be(!1),placeholder:te?"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:te?"OpenAI key configured":"OpenAI key not configured"})]}),$e&&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:$e}),lt&&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:se||!h.trim()&&!b.trim(),onClick:async()=>{Me(!0),Fe(""),ae(!1);try{const t={};h.trim()&&(t.claudeCodeOAuthToken=h.trim()),b.trim()&&(t.openaiApiKey=b.trim());const a=await n.updateSettings(t);De(a.claudeCodeOAuthTokenConfigured),Ke(a.openaiApiKeyConfigured),Ve(""),Le(""),ae(!0),setTimeout(()=>ae(!1),1800)}catch(t){Fe(t instanceof Error?t.message:String(t))}finally{Me(!1)}},className:`px-4 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${se||!h.trim()&&!b.trim()?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:se?"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:mt,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&&!it&&!p?"••••••••••••••••":p,onChange:t=>{pe(t.target.value),m(null)},onFocus:()=>Re(!0),onBlur:()=>Re(!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:me,onChange:t=>he(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"})]}),fe&&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:fe}),ge&&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:k?"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:Z||!p.trim(),onClick:async()=>{Oe(!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{Oe(!1)}},className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${Z||!p.trim()?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:Z?"Verifying...":"Verify"}),e.jsx("button",{type:"submit",disabled:f||k,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${f||k?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:f?"Saving...":"Save"})]})]}),N&&e.jsx("div",{className:`px-3 py-2 rounded-lg text-xs ${N.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:N.valid?"API key is valid.":`Invalid API key${N.error?`: ${N.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:()=>ie("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 ${I&&d?"text-cc-success":"text-cc-muted"}`,children:I&&d?"On":"Off"})]}),!d&&e.jsx("p",{className:"text-[11px] text-cc-warning",children:"Configure an Anthropic API key above to enable AI validation."}),I&&d&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",onClick:()=>ie("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 ${B?"text-cc-success":"text-cc-muted"}`,children:B?"On":"Off"})]}),e.jsxs("button",{type:"button",onClick:()=>ie("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 ${_?"text-cc-success":"text-cc-muted"}`,children:_?"On":"Off"})]})]})]})]}),e.jsxs("section",{id:"dashboard",ref:l("dashboard"),children:[e.jsx("h2",{className:"text-sm font-semibold text-cc-fg mb-4",children:"Project Dashboard"}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("p",{className:"text-xs text-cc-muted leading-relaxed",children:["A nightly job summarizes every session with recent activity so the"," ",e.jsx("a",{href:"#/dashboard",className:"underline hover:no-underline",children:"dashboard"})," ",'can show where each project stands. It runs through the Claude Code CLI with the same login your normal sessions use — no separate API key needed. This is opt-in because it spends tokens every night. You can always refresh manually with "Update now" on the dashboard, even when the nightly run is off.']}),e.jsxs("button",{type:"button",onClick:ht,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.jsx("span",{className:"text-sm",children:"Nightly dashboard updates"}),e.jsx("span",{className:`text-xs font-medium ${G?"text-cc-success":"text-cc-muted"}`,children:G?"On":"Off"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",htmlFor:"dashboard-model",children:"Summarization model"}),e.jsxs("select",{id:"dashboard-model",value:Ee,onChange:t=>{const a=Ee;Y(t.target.value),le({dashboardModel:t.target.value},()=>Y(a))},className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow",children:[e.jsx("option",{value:"claude-haiku-4-5",children:"Claude Haiku 4.5 — cheapest, recommended"}),e.jsx("option",{value:"claude-sonnet-4-6",children:"Claude Sonnet 4.6"}),e.jsx("option",{value:"claude-sonnet-5",children:"Claude Sonnet 5"})]}),e.jsx("p",{className:"mt-1.5 text-xs text-cc-muted",children:"Summarize-and-classify doesn't need an expensive model — Haiku keeps the nightly cost low."})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",htmlFor:"dashboard-run-hour",children:"Run at"}),e.jsx("select",{id:"dashboard-run-hour",value:Ue,onChange:t=>{const a=Ue,c=Number(t.target.value);z(c),le({dashboardRunHour:c},()=>z(a))},className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow",children:Array.from({length:24},(t,a)=>e.jsxs("option",{value:a,children:[String(a).padStart(2,"0"),":00"]},a))})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1.5",htmlFor:"dashboard-max-sessions",children:"Max sessions per run"}),e.jsx("select",{id:"dashboard-max-sessions",value:Te,onChange:t=>{const a=Te,c=Number(t.target.value);J(c),le({dashboardMaxSessionsPerRun:c},()=>J(a))},className:"w-full px-3 py-2.5 min-h-[44px] text-sm bg-cc-bg rounded-lg text-cc-fg focus:outline-none focus:ring-1 focus:ring-cc-primary/40 transition-shadow",children:[10,20,30,50,100,200].map(t=>e.jsx("option",{value:t,children:t},t))})]})]})]})]}),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:[r?e.jsxs("p",{className:"text-xs text-cc-muted",children:["Current version: v",r.currentVersion,r.latestVersion?` • Latest: v${r.latestVersion}`:"",r.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"){y("stable");try{await n.updateSettings({updateChannel:"stable"})}catch{y("prerelease");return}try{const t=await n.forceCheckForUpdate();O(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"){y("prerelease");try{await n.updateSettings({updateChannel:"prerelease"})}catch{y("stable");return}try{const t=await n.forceCheckForUpdate();O(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":C,onClick:async()=>{const t=!C;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 ${C?"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 ${C?"translate-x-5":"translate-x-0"}`})})]}),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}),Se&&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:Se}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx("button",{type:"button",onClick:bt,disabled:$,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${$?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:$?"Checking...":"Check for updates"}),r!=null&&r.isServiceMode?e.jsx("button",{type:"button",onClick:ft,disabled:F||r.updateInProgress||!r.updateAvailable,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${F||r.updateInProgress||!r.updateAvailable?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:F||r.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=!Ae;jt(t),rt(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:Ae?"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{wt as SettingsPage};