@hellcoder/companion 0.110.3 → 0.111.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 (29) hide show
  1. package/dist/assets/{AgentsPage-Dye7KBqi.js → AgentsPage-DO5MiZyp.js} +1 -1
  2. package/dist/assets/{CronManager-DTlT02dA.js → CronManager-BSNkRBGn.js} +1 -1
  3. package/dist/assets/{DashboardPage-fvKg3Yqp.js → DashboardPage-DqwmmyJ6.js} +1 -1
  4. package/dist/assets/{IntegrationsPage-F68cGNc0.js → IntegrationsPage-CxavpMnn.js} +1 -1
  5. package/dist/assets/{LinearOAuthSettingsPage-SoGpGzIF.js → LinearOAuthSettingsPage-Bs_FXqPA.js} +1 -1
  6. package/dist/assets/{LinearSettingsPage-CPvMIKgr.js → LinearSettingsPage-zerN3EmK.js} +1 -1
  7. package/dist/assets/{Playground-CF7prjX2.js → Playground-DNlOjOnC.js} +6 -6
  8. package/dist/assets/{PromptsPage-vx71p6l9.js → PromptsPage-D20LKwla.js} +1 -1
  9. package/dist/assets/{RunsPage-CH4PZDnZ.js → RunsPage-ho-uEj6h.js} +1 -1
  10. package/dist/assets/{SandboxManager-YH5E0Jto.js → SandboxManager-BY2NPfsU.js} +1 -1
  11. package/dist/assets/SettingsPage-CvYCqkm1.js +1 -0
  12. package/dist/assets/{TailscalePage-OvQqy343.js → TailscalePage-pv47ZX2f.js} +1 -1
  13. package/dist/assets/{index-Daik8cew.js → index-C7qt99R_.js} +50 -50
  14. package/dist/assets/index-C9eyQq6F.css +1 -0
  15. package/dist/assets/{sw-register-CcXhHDAn.js → sw-register-DZ08pZIJ.js} +1 -1
  16. package/dist/index.html +2 -2
  17. package/dist/sw.js +1 -1
  18. package/package.json +1 -1
  19. package/server/auto-namer.test.ts +4 -0
  20. package/server/claude-adapter.ts +15 -1
  21. package/server/linear-connections.test.ts +4 -0
  22. package/server/routes/settings-routes.ts +10 -0
  23. package/server/routes.test.ts +76 -0
  24. package/server/session-orchestrator.test.ts +3 -0
  25. package/server/settings-manager.test.ts +3 -0
  26. package/server/settings-manager.ts +16 -1
  27. package/server/ws-bridge-codex.test.ts +6 -0
  28. package/dist/assets/SettingsPage-B2dQwyjl.js +0 -1
  29. package/dist/assets/index-DMoY-FDD.css +0 -1
@@ -1158,6 +1158,7 @@ describe("GET /api/sessions/:id/archive-info", () => {
1158
1158
  updateChannel: "stable",
1159
1159
  dockerAutoUpdate: false,
1160
1160
  proactiveKeepaliveEnabled: true,
1161
+ wedgeKillEnabled: true,
1161
1162
  updatedAt: 0,
1162
1163
  });
1163
1164
  const res = await app.request("/api/sessions/s1/archive-info", { method: "GET" });
@@ -1533,6 +1534,7 @@ describe("GET /api/settings", () => {
1533
1534
  updateChannel: "stable",
1534
1535
  dockerAutoUpdate: false,
1535
1536
  proactiveKeepaliveEnabled: true,
1537
+ wedgeKillEnabled: true,
1536
1538
  updatedAt: 123,
1537
1539
  });
1538
1540
 
@@ -1566,6 +1568,7 @@ describe("GET /api/settings", () => {
1566
1568
  updateChannel: "stable",
1567
1569
  dockerAutoUpdate: false,
1568
1570
  proactiveKeepaliveEnabled: true,
1571
+ wedgeKillEnabled: true,
1569
1572
  });
1570
1573
  });
1571
1574
 
@@ -1599,6 +1602,7 @@ describe("GET /api/settings", () => {
1599
1602
  updateChannel: "stable",
1600
1603
  dockerAutoUpdate: false,
1601
1604
  proactiveKeepaliveEnabled: true,
1605
+ wedgeKillEnabled: true,
1602
1606
  updatedAt: 123,
1603
1607
  });
1604
1608
 
@@ -1632,6 +1636,7 @@ describe("GET /api/settings", () => {
1632
1636
  updateChannel: "stable",
1633
1637
  dockerAutoUpdate: false,
1634
1638
  proactiveKeepaliveEnabled: true,
1639
+ wedgeKillEnabled: true,
1635
1640
  });
1636
1641
  });
1637
1642
 
@@ -1666,6 +1671,7 @@ describe("GET /api/settings", () => {
1666
1671
  updateChannel: "stable",
1667
1672
  dockerAutoUpdate: false,
1668
1673
  proactiveKeepaliveEnabled: true,
1674
+ wedgeKillEnabled: true,
1669
1675
  updatedAt: 100,
1670
1676
  });
1671
1677
 
@@ -1708,6 +1714,7 @@ describe("PUT /api/settings", () => {
1708
1714
  updateChannel: "stable",
1709
1715
  dockerAutoUpdate: false,
1710
1716
  proactiveKeepaliveEnabled: true,
1717
+ wedgeKillEnabled: true,
1711
1718
  updatedAt: 456,
1712
1719
  });
1713
1720
 
@@ -1763,6 +1770,7 @@ describe("PUT /api/settings", () => {
1763
1770
  updateChannel: "stable",
1764
1771
  dockerAutoUpdate: false,
1765
1772
  proactiveKeepaliveEnabled: true,
1773
+ wedgeKillEnabled: true,
1766
1774
  });
1767
1775
  });
1768
1776
 
@@ -1796,6 +1804,7 @@ describe("PUT /api/settings", () => {
1796
1804
  updateChannel: "stable",
1797
1805
  dockerAutoUpdate: false,
1798
1806
  proactiveKeepaliveEnabled: true,
1807
+ wedgeKillEnabled: true,
1799
1808
  updatedAt: 789,
1800
1809
  });
1801
1810
 
@@ -1846,6 +1855,7 @@ describe("PUT /api/settings", () => {
1846
1855
  updateChannel: "stable",
1847
1856
  dockerAutoUpdate: false,
1848
1857
  proactiveKeepaliveEnabled: true,
1858
+ wedgeKillEnabled: true,
1849
1859
  updatedAt: 999,
1850
1860
  });
1851
1861
 
@@ -1915,6 +1925,55 @@ describe("PUT /api/settings", () => {
1915
1925
  expect(json).toEqual({ error: "updateChannel must be 'stable' or 'prerelease'" });
1916
1926
  });
1917
1927
 
1928
+ // Type validation for every settings field. Each branch returns 400 before
1929
+ // touching updateSettings, so a malformed client cannot write a bad value
1930
+ // into settings.json (which is read at startup and would persist the damage).
1931
+ it.each([
1932
+ ["anthropicApiKey", 123, "anthropicApiKey must be a string"],
1933
+ ["anthropicModel", 123, "anthropicModel must be a string"],
1934
+ ["wedgeKillEnabled", "yes", "wedgeKillEnabled must be a boolean"],
1935
+ ["proactiveKeepaliveEnabled", "yes", "proactiveKeepaliveEnabled must be a boolean"],
1936
+ ["cliBridgeMode", "nonsense", "cliBridgeMode must be 'loopback' or 'jsonHandoff'"],
1937
+ ])("rejects a non-conforming %s with 400", async (field, value, expectedError) => {
1938
+ const res = await app.request("/api/settings", {
1939
+ method: "PUT",
1940
+ headers: { "Content-Type": "application/json" },
1941
+ body: JSON.stringify({ [field]: value }),
1942
+ });
1943
+
1944
+ expect(res.status).toBe(400);
1945
+ const body = await res.json();
1946
+ expect(body.error).toBe(expectedError);
1947
+ // The rejection must happen before any write is attempted.
1948
+ expect(settingsManager.updateSettings).not.toHaveBeenCalled();
1949
+ });
1950
+
1951
+ it("rejects an empty body with 400", async () => {
1952
+ // Guards the hasAnyField check: an empty PUT would otherwise write a
1953
+ // no-op settings object and bump updatedAt for no reason.
1954
+ const res = await app.request("/api/settings", {
1955
+ method: "PUT",
1956
+ headers: { "Content-Type": "application/json" },
1957
+ body: JSON.stringify({}),
1958
+ });
1959
+
1960
+ expect(res.status).toBe(400);
1961
+ const body = await res.json();
1962
+ expect(body.error).toBe("At least one settings field is required");
1963
+ });
1964
+
1965
+ it("rejects a malformed JSON body with 400", async () => {
1966
+ // c.req.json() is guarded by .catch(() => ({})), so invalid JSON must fall
1967
+ // through to the same "no fields" rejection rather than throwing a 500.
1968
+ const res = await app.request("/api/settings", {
1969
+ method: "PUT",
1970
+ headers: { "Content-Type": "application/json" },
1971
+ body: "{not json",
1972
+ });
1973
+
1974
+ expect(res.status).toBe(400);
1975
+ });
1976
+
1918
1977
  // Verifies that PUT /api/settings accepts a publicUrl string and passes
1919
1978
  // it (trimmed, trailing-slash-stripped) to updateSettings
1920
1979
  it("accepts and saves publicUrl string", async () => {
@@ -1947,6 +2006,7 @@ describe("PUT /api/settings", () => {
1947
2006
  updateChannel: "stable",
1948
2007
  dockerAutoUpdate: false,
1949
2008
  proactiveKeepaliveEnabled: true,
2009
+ wedgeKillEnabled: true,
1950
2010
  updatedAt: 500,
1951
2011
  });
1952
2012
 
@@ -2203,6 +2263,7 @@ describe("GET /api/linear/issues", () => {
2203
2263
  updateChannel: "stable",
2204
2264
  dockerAutoUpdate: false,
2205
2265
  proactiveKeepaliveEnabled: true,
2266
+ wedgeKillEnabled: true,
2206
2267
  updatedAt: 0,
2207
2268
  });
2208
2269
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2243,6 +2304,7 @@ describe("GET /api/linear/issues", () => {
2243
2304
  updateChannel: "stable",
2244
2305
  dockerAutoUpdate: false,
2245
2306
  proactiveKeepaliveEnabled: true,
2307
+ wedgeKillEnabled: true,
2246
2308
  updatedAt: 0,
2247
2309
  });
2248
2310
 
@@ -2336,6 +2398,7 @@ describe("GET /api/linear/issues", () => {
2336
2398
  updateChannel: "stable",
2337
2399
  dockerAutoUpdate: false,
2338
2400
  proactiveKeepaliveEnabled: true,
2401
+ wedgeKillEnabled: true,
2339
2402
  updatedAt: 0,
2340
2403
  });
2341
2404
 
@@ -2436,6 +2499,7 @@ describe("GET /api/linear/issues", () => {
2436
2499
  updateChannel: "stable",
2437
2500
  dockerAutoUpdate: false,
2438
2501
  proactiveKeepaliveEnabled: true,
2502
+ wedgeKillEnabled: true,
2439
2503
  updatedAt: 0,
2440
2504
  });
2441
2505
 
@@ -2501,6 +2565,7 @@ describe("GET /api/linear/connection", () => {
2501
2565
  updateChannel: "stable",
2502
2566
  dockerAutoUpdate: false,
2503
2567
  proactiveKeepaliveEnabled: true,
2568
+ wedgeKillEnabled: true,
2504
2569
  updatedAt: 0,
2505
2570
  });
2506
2571
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2541,6 +2606,7 @@ describe("GET /api/linear/connection", () => {
2541
2606
  updateChannel: "stable",
2542
2607
  dockerAutoUpdate: false,
2543
2608
  proactiveKeepaliveEnabled: true,
2609
+ wedgeKillEnabled: true,
2544
2610
  updatedAt: 0,
2545
2611
  });
2546
2612
 
@@ -2603,6 +2669,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2603
2669
  updateChannel: "stable",
2604
2670
  dockerAutoUpdate: false,
2605
2671
  proactiveKeepaliveEnabled: true,
2672
+ wedgeKillEnabled: true,
2606
2673
  updatedAt: 0,
2607
2674
  });
2608
2675
 
@@ -2647,6 +2714,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2647
2714
  updateChannel: "stable",
2648
2715
  dockerAutoUpdate: false,
2649
2716
  proactiveKeepaliveEnabled: true,
2717
+ wedgeKillEnabled: true,
2650
2718
  updatedAt: 0,
2651
2719
  });
2652
2720
 
@@ -2690,6 +2758,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2690
2758
  updateChannel: "stable",
2691
2759
  dockerAutoUpdate: false,
2692
2760
  proactiveKeepaliveEnabled: true,
2761
+ wedgeKillEnabled: true,
2693
2762
  updatedAt: 0,
2694
2763
  });
2695
2764
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2735,6 +2804,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2735
2804
  updateChannel: "stable",
2736
2805
  dockerAutoUpdate: false,
2737
2806
  proactiveKeepaliveEnabled: true,
2807
+ wedgeKillEnabled: true,
2738
2808
  updatedAt: 0,
2739
2809
  });
2740
2810
 
@@ -2814,6 +2884,7 @@ describe("POST /api/linear/issues/:id/transition", () => {
2814
2884
  updateChannel: "stable",
2815
2885
  dockerAutoUpdate: false,
2816
2886
  proactiveKeepaliveEnabled: true,
2887
+ wedgeKillEnabled: true,
2817
2888
  updatedAt: 0,
2818
2889
  });
2819
2890
 
@@ -2872,6 +2943,7 @@ describe("GET /api/linear/projects", () => {
2872
2943
  updateChannel: "stable",
2873
2944
  dockerAutoUpdate: false,
2874
2945
  proactiveKeepaliveEnabled: true,
2946
+ wedgeKillEnabled: true,
2875
2947
  updatedAt: 0,
2876
2948
  });
2877
2949
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -2912,6 +2984,7 @@ describe("GET /api/linear/projects", () => {
2912
2984
  updateChannel: "stable",
2913
2985
  dockerAutoUpdate: false,
2914
2986
  proactiveKeepaliveEnabled: true,
2987
+ wedgeKillEnabled: true,
2915
2988
  updatedAt: 0,
2916
2989
  });
2917
2990
 
@@ -2982,6 +3055,7 @@ describe("GET /api/linear/project-issues", () => {
2982
3055
  updateChannel: "stable",
2983
3056
  dockerAutoUpdate: false,
2984
3057
  proactiveKeepaliveEnabled: true,
3058
+ wedgeKillEnabled: true,
2985
3059
  updatedAt: 0,
2986
3060
  });
2987
3061
  vi.mocked(resolveApiKey).mockReturnValue(null);
@@ -3022,6 +3096,7 @@ describe("GET /api/linear/project-issues", () => {
3022
3096
  updateChannel: "stable",
3023
3097
  dockerAutoUpdate: false,
3024
3098
  proactiveKeepaliveEnabled: true,
3099
+ wedgeKillEnabled: true,
3025
3100
  updatedAt: 0,
3026
3101
  });
3027
3102
 
@@ -3107,6 +3182,7 @@ describe("GET /api/linear/project-issues", () => {
3107
3182
  updateChannel: "stable",
3108
3183
  dockerAutoUpdate: false,
3109
3184
  proactiveKeepaliveEnabled: true,
3185
+ wedgeKillEnabled: true,
3110
3186
  updatedAt: 0,
3111
3187
  });
3112
3188
 
@@ -50,6 +50,7 @@ vi.mock("./settings-manager.js", () => ({
50
50
  openaiApiKey: "",
51
51
  onboardingCompleted: false,
52
52
  proactiveKeepaliveEnabled: true,
53
+ wedgeKillEnabled: true,
53
54
  })),
54
55
  }));
55
56
 
@@ -247,6 +248,7 @@ describe("SessionOrchestrator", () => {
247
248
  openaiApiKey: "",
248
249
  onboardingCompleted: false,
249
250
  proactiveKeepaliveEnabled: true,
251
+ wedgeKillEnabled: true,
250
252
  } as any);
251
253
  vi.mocked(containerManager.createContainer).mockReturnValue({
252
254
  containerId: "cid-1",
@@ -1831,6 +1833,7 @@ describe("SessionOrchestrator", () => {
1831
1833
  vi.mocked(settingsManager.getSettings).mockReturnValue({
1832
1834
  ...settingsManager.getSettings(),
1833
1835
  proactiveKeepaliveEnabled: false,
1836
+ wedgeKillEnabled: true,
1834
1837
  });
1835
1838
  deps.launcher.getSession.mockReturnValue({
1836
1839
  archived: false,
@@ -53,6 +53,7 @@ describe("settings-manager", () => {
53
53
  updateChannel: "stable",
54
54
  dockerAutoUpdate: false,
55
55
  proactiveKeepaliveEnabled: true,
56
+ wedgeKillEnabled: true,
56
57
  cliBridgeMode: "loopback",
57
58
  claudeBridgeMode: "none",
58
59
  claudeBridgeIngressUrl: "",
@@ -117,6 +118,7 @@ describe("settings-manager", () => {
117
118
  updateChannel: "stable",
118
119
  dockerAutoUpdate: false,
119
120
  proactiveKeepaliveEnabled: true,
121
+ wedgeKillEnabled: true,
120
122
  cliBridgeMode: "loopback",
121
123
  claudeBridgeMode: "none",
122
124
  claudeBridgeIngressUrl: "",
@@ -207,6 +209,7 @@ describe("settings-manager", () => {
207
209
  updateChannel: "stable",
208
210
  dockerAutoUpdate: false,
209
211
  proactiveKeepaliveEnabled: true,
212
+ wedgeKillEnabled: true,
210
213
  cliBridgeMode: "loopback",
211
214
  claudeBridgeMode: "none",
212
215
  claudeBridgeIngressUrl: "",
@@ -92,6 +92,18 @@ export interface CompanionSettings {
92
92
  * cron) alive. Disable to experiment with letting dead sessions stay dead.
93
93
  */
94
94
  proactiveKeepaliveEnabled: boolean;
95
+ /**
96
+ * When true (default), a CLI whose stdout closes but which does not exit is
97
+ * treated as wedged and killed so recovery can proceed.
98
+ *
99
+ * This machinery was built for older CLI behaviour. Every kill sampled since
100
+ * the /proc instrumentation landed has been a *healthy* process — typically
101
+ * one supervising a background tool call — so it is plausible the wedge it
102
+ * guards against no longer occurs. Disable to test that: the transport is
103
+ * still torn down and the session still relaunches, but the process is left
104
+ * to exit on its own instead of being SIGTERMed.
105
+ */
106
+ wedgeKillEnabled: boolean;
95
107
  /** See CliBridgeMode. Defaults to "loopback". Optional in fixtures; normalize() applies the default. */
96
108
  cliBridgeMode?: CliBridgeMode;
97
109
  /** See ClaudeBridgeMode. Defaults to "none". Persists across companion restarts. */
@@ -136,6 +148,7 @@ let settings: CompanionSettings = {
136
148
  updateChannel: "stable",
137
149
  dockerAutoUpdate: false,
138
150
  proactiveKeepaliveEnabled: true,
151
+ wedgeKillEnabled: true,
139
152
  cliBridgeMode: "loopback",
140
153
  claudeBridgeMode: "none",
141
154
  claudeBridgeIngressUrl: "",
@@ -187,6 +200,7 @@ function normalize(raw: Partial<CompanionSettings> | null | undefined): Companio
187
200
  updateChannel: raw?.updateChannel === "prerelease" ? "prerelease" : "stable",
188
201
  dockerAutoUpdate: typeof raw?.dockerAutoUpdate === "boolean" ? raw.dockerAutoUpdate : false,
189
202
  proactiveKeepaliveEnabled: typeof raw?.proactiveKeepaliveEnabled === "boolean" ? raw.proactiveKeepaliveEnabled : true,
203
+ wedgeKillEnabled: typeof raw?.wedgeKillEnabled === "boolean" ? raw.wedgeKillEnabled : true,
190
204
  cliBridgeMode: raw?.cliBridgeMode === "jsonHandoff" ? "jsonHandoff" : "loopback",
191
205
  claudeBridgeMode: raw?.claudeBridgeMode === "patched" ? "patched" : "none",
192
206
  claudeBridgeIngressUrl: typeof raw?.claudeBridgeIngressUrl === "string" ? raw.claudeBridgeIngressUrl : "",
@@ -220,7 +234,7 @@ export function getSettings(): CompanionSettings {
220
234
  }
221
235
 
222
236
  export function updateSettings(
223
- 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" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
237
+ 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" | "wedgeKillEnabled" | "cliBridgeMode" | "claudeBridgeMode" | "claudeBridgeIngressUrl" | "claudeCompatBannerDismissedVersion">>,
224
238
  ): CompanionSettings {
225
239
  ensureLoaded();
226
240
  settings = normalize({
@@ -252,6 +266,7 @@ export function updateSettings(
252
266
  updateChannel: patch.updateChannel ?? settings.updateChannel,
253
267
  dockerAutoUpdate: patch.dockerAutoUpdate ?? settings.dockerAutoUpdate,
254
268
  proactiveKeepaliveEnabled: patch.proactiveKeepaliveEnabled ?? settings.proactiveKeepaliveEnabled,
269
+ wedgeKillEnabled: patch.wedgeKillEnabled ?? settings.wedgeKillEnabled,
255
270
  cliBridgeMode: patch.cliBridgeMode ?? settings.cliBridgeMode,
256
271
  claudeBridgeMode: patch.claudeBridgeMode ?? settings.claudeBridgeMode,
257
272
  claudeBridgeIngressUrl: patch.claudeBridgeIngressUrl ?? settings.claudeBridgeIngressUrl,
@@ -140,6 +140,7 @@ describe("attachCodexAdapterHandlers", () => {
140
140
  updateChannel: "stable",
141
141
  dockerAutoUpdate: false,
142
142
  proactiveKeepaliveEnabled: true,
143
+ wedgeKillEnabled: true,
143
144
  updatedAt: 0,
144
145
  });
145
146
  });
@@ -1131,6 +1132,7 @@ describe("attachCodexAdapterHandlers", () => {
1131
1132
  updateChannel: "stable",
1132
1133
  dockerAutoUpdate: false,
1133
1134
  proactiveKeepaliveEnabled: true,
1135
+ wedgeKillEnabled: true,
1134
1136
  updatedAt: 0,
1135
1137
  });
1136
1138
  }
@@ -1312,6 +1314,7 @@ describe("attachCodexAdapterHandlers", () => {
1312
1314
  updateChannel: "stable",
1313
1315
  dockerAutoUpdate: false,
1314
1316
  proactiveKeepaliveEnabled: true,
1317
+ wedgeKillEnabled: true,
1315
1318
  updatedAt: 0,
1316
1319
  });
1317
1320
 
@@ -1363,6 +1366,7 @@ describe("attachCodexAdapterHandlers", () => {
1363
1366
  updateChannel: "stable",
1364
1367
  dockerAutoUpdate: false,
1365
1368
  proactiveKeepaliveEnabled: true,
1369
+ wedgeKillEnabled: true,
1366
1370
  updatedAt: 0,
1367
1371
  });
1368
1372
 
@@ -1479,6 +1483,7 @@ describe("attachCodexAdapterHandlers", () => {
1479
1483
  updateChannel: "stable",
1480
1484
  dockerAutoUpdate: false,
1481
1485
  proactiveKeepaliveEnabled: true,
1486
+ wedgeKillEnabled: true,
1482
1487
  updatedAt: 0,
1483
1488
  });
1484
1489
 
@@ -1614,6 +1619,7 @@ describe("attachCodexAdapterHandlers", () => {
1614
1619
  updateChannel: "stable",
1615
1620
  dockerAutoUpdate: false,
1616
1621
  proactiveKeepaliveEnabled: true,
1622
+ wedgeKillEnabled: true,
1617
1623
  updatedAt: 0,
1618
1624
  });
1619
1625
 
@@ -1 +0,0 @@
1
- import{r as s,u as o,g as mt,a as c,j as e,n as ht,b as ft,s as bt}from"./index-Daik8cew.js";const re=[{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 vt({embedded:ie=!1}){const[p,le]=s.useState(""),[de,ue]=s.useState("claude-sonnet-4-6"),[d,xe]=s.useState(!1),[w,We]=s.useState(!0),[b,S]=s.useState(!1),[pe,g]=s.useState(""),[me,v]=s.useState(!1),ze=o(t=>t.darkMode),Je=o(t=>t.toggleDarkMode),he=o(t=>t.diffBase),Xe=o(t=>t.setDiffBase),Ze=o(t=>t.notificationSound),et=o(t=>t.toggleNotificationSound),fe=o(t=>t.notificationDesktop),be=o(t=>t.setNotificationDesktop),r=o(t=>t.updateInfo),P=o(t=>t.setUpdateInfo),tt=o(t=>t.setUpdateOverlayActive),st=typeof Notification<"u",[x,y]=s.useState("stable"),[C,I]=s.useState(!1),[A,V]=s.useState(!0),[ge,D]=s.useState("loopback"),[O,ve]=s.useState(!1),[M,ye]=s.useState(!1),[je,j]=s.useState(""),[Ne,U]=s.useState(""),[ke,at]=s.useState(mt()),[E,$]=s.useState(!1),[L,K]=s.useState(!0),[F,H]=s.useState(!1),[B,Q]=s.useState(!1),[we,_]=s.useState("claude-haiku-4-5"),[Se,G]=s.useState(3),[Ce,q]=s.useState(30),[T,Y]=s.useState(""),[Ae,Ue]=s.useState("general"),[ct,Ee]=s.useState(!1),[W,Te]=s.useState(!1),[N,m]=s.useState(null),[h,Re]=s.useState(""),[z,Pe]=s.useState(!1),[f,Ie]=s.useState(""),[J,Ve]=s.useState(!1),[X,De]=s.useState(!1),[nt,Z]=s.useState(!1),[Oe,Me]=s.useState(""),[ot,$e]=s.useState(!1),[rt,Le]=s.useState(!1),[k,Ke]=s.useState(null),[ee,Fe]=s.useState(!1),[u,He]=s.useState(null),[R,it]=s.useState(0),[te,Be]=s.useState(!1),[se,Qe]=s.useState(!1),[lt,_e]=s.useState(!1),Ge=s.useRef(null),ae=s.useRef({});s.useEffect(()=>{const t=Ge.current;if(!t)return;const a=new IntersectionObserver(n=>{var Ye;let i=null;for(const oe of n)oe.isIntersecting&&(!i||oe.boundingClientRect.top<i.boundingClientRect.top)&&(i=oe);(Ye=i==null?void 0:i.target)!=null&&Ye.id&&Ue(i.target.id)},{root:t,rootMargin:"-10% 0px -70% 0px",threshold:0});for(const n of re){const i=ae.current[n.id];i&&a.observe(i)}return()=>a.disconnect()},[w]);const qe=s.useCallback(t=>{Ue(t);const a=ae.current[t];a&&a.scrollIntoView({behavior:"smooth",block:"start"})},[]);s.useEffect(()=>{c.getSettings().then(t=>{xe(t.anthropicApiKeyConfigured),Pe(t.claudeCodeOAuthTokenConfigured),Ve(t.openaiApiKeyConfigured),ue(t.anthropicModel||"claude-sonnet-4-6"),typeof t.aiValidationEnabled=="boolean"&&$(t.aiValidationEnabled),typeof t.aiValidationAutoApprove=="boolean"&&K(t.aiValidationAutoApprove),typeof t.aiValidationAutoDeny=="boolean"&&H(t.aiValidationAutoDeny),typeof t.dashboardEnabled=="boolean"&&Q(t.dashboardEnabled),typeof t.dashboardModel=="string"&&t.dashboardModel&&_(t.dashboardModel),typeof t.dashboardRunHour=="number"&&G(t.dashboardRunHour),typeof t.dashboardMaxSessionsPerRun=="number"&&q(t.dashboardMaxSessionsPerRun),(t.updateChannel==="stable"||t.updateChannel==="prerelease")&&y(t.updateChannel),typeof t.dockerAutoUpdate=="boolean"&&I(t.dockerAutoUpdate),typeof t.proactiveKeepaliveEnabled=="boolean"&&V(t.proactiveKeepaliveEnabled),(t.cliBridgeMode==="loopback"||t.cliBridgeMode==="jsonHandoff")&&D(t.cliBridgeMode),typeof t.publicUrl=="string"&&(Y(t.publicUrl),o.getState().setPublicUrl(t.publicUrl))}).catch(t=>g(t instanceof Error?t.message:String(t))).finally(()=>We(!1)),c.getAuthToken().then(t=>Ke(t.token)).catch(()=>{})},[]);async function dt(t){t.preventDefault(),S(!0),g(""),v(!1);try{const a=p.trim(),n={anthropicModel:de.trim()||"claude-sonnet-4-6"};a&&(n.anthropicApiKey=a);const i=await c.updateSettings(n);xe(i.anthropicApiKeyConfigured),le(""),v(!0),setTimeout(()=>v(!1),1800)}catch(a){g(a instanceof Error?a.message:String(a))}finally{S(!1)}}async function ce(t){const a=t==="aiValidationEnabled"?E:t==="aiValidationAutoApprove"?L:F,n=!a;t==="aiValidationEnabled"?$(n):t==="aiValidationAutoApprove"?K(n):H(n);try{await c.updateSettings({[t]:n})}catch{t==="aiValidationEnabled"?$(a):t==="aiValidationAutoApprove"?K(a):H(a)}}async function ut(){const t=B;Q(!t);try{await c.updateSettings({dashboardEnabled:!t})}catch{Q(t)}}async function ne(t,a){try{await c.updateSettings(t)}catch{a()}}async function xt(){ve(!0),j(""),U("");try{const t=await c.forceCheckForUpdate();P(t),t.updateAvailable&&t.latestVersion?j(`Update v${t.latestVersion} is available.`):j("You are up to date.")}catch(t){U(t instanceof Error?t.message:String(t))}finally{ve(!1)}}async function pt(){ye(!0),j(""),U("");try{localStorage.setItem("companion_docker_prompt_pending","1");const t=await c.triggerUpdate();j(t.message),tt(!0)}catch(t){localStorage.removeItem("companion_docker_prompt_pending"),U(t instanceof Error?t.message:String(t)),ye(!1)}}const l=s.useCallback(t=>a=>{ae.current[t]=a},[]);return e.jsxs("div",{className:`${ie?"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."})]}),!ie&&e.jsx("button",{onClick:()=>{const t=o.getState().currentSessionId;t?ht(t):ft()},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:re.map(t=>e.jsx("button",{type:"button",onClick:()=>qe(t.id),className:`shrink-0 px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${Ae===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:re.map(t=>e.jsx("button",{type:"button",onClick:()=>qe(t.id),className:`text-left px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors cursor-pointer ${Ae===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:Ge,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:Je,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:ze?"Dark":"Light"})]}),e.jsxs("button",{type:"button",onClick:()=>Xe(he==="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:he==="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:ge,onChange:async t=>{const a=t.target.value==="jsonHandoff"?"jsonHandoff":"loopback",n=ge;D(a);try{await c.updateSettings({cliBridgeMode:a})}catch{D(n)}},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;V(t);try{await c.updateSettings({proactiveKeepaliveEnabled:t})}catch{V(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("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=>Y(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()=>{S(!0),g("");try{const t=await c.updateSettings({publicUrl:T.trim()});Y(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:b,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:b?"Saving...":me?"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:k?ee?k:"••••••••••••••••":e.jsx("span",{className:"text-cc-muted",children:"Loading..."})}),e.jsx("button",{type:"button",onClick:()=>Fe(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:ee?"Hide token":"Show token",children:ee?"Hide":"Show"}),e.jsx("button",{type:"button",onClick:()=>{k&&navigator.clipboard.writeText(k).then(()=>{_e(!0),setTimeout(()=>_e(!1),1500)})},disabled:!k,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:lt?"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:()=>it(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()=>{Be(!0);try{const t=await c.getAuthQr();He(t.qrCodes)}catch{}finally{Be(!1)}},disabled:te,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${te?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:te?"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 c.regenerateAuthToken();Ke(t.token),Fe(!0),He(null)}catch{}finally{Qe(!1)}}},disabled:se,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${se?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-error/10 hover:bg-cc-error/20 text-cc-error cursor-pointer"}`,children:se?"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:et,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:Ze?"On":"Off"})]}),st&&e.jsxs("button",{type:"button",onClick:async()=>{if(fe)be(!1);else{if(Notification.permission!=="granted"&&await Notification.requestPermission()!=="granted")return;be(!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:fe?"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:z&&!ot&&!h?"••••••••••••••••":h,onChange:t=>Re(t.target.value),onFocus:()=>$e(!0),onBlur:()=>$e(!1),placeholder:z?"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:z?"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:J&&!rt&&!f?"••••••••••••••••":f,onChange:t=>Ie(t.target.value),onFocus:()=>Le(!0),onBlur:()=>Le(!1),placeholder:J?"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:J?"OpenAI key configured":"OpenAI key not configured"})]}),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}),nt&&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:X||!h.trim()&&!f.trim(),onClick:async()=>{De(!0),Me(""),Z(!1);try{const t={};h.trim()&&(t.claudeCodeOAuthToken=h.trim()),f.trim()&&(t.openaiApiKey=f.trim());const a=await c.updateSettings(t);Pe(a.claudeCodeOAuthTokenConfigured),Ve(a.openaiApiKeyConfigured),Re(""),Ie(""),Z(!0),setTimeout(()=>Z(!1),1800)}catch(t){Me(t instanceof Error?t.message:String(t))}finally{De(!1)}},className:`px-4 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${X||!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:X?"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:dt,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&&!ct&&!p?"••••••••••••••••":p,onChange:t=>{le(t.target.value),m(null)},onFocus:()=>Ee(!0),onBlur:()=>Ee(!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:de,onChange:t=>ue(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"})]}),pe&&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:pe}),me&&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:W||!p.trim(),onClick:async()=>{Te(!0),m(null);try{const t=await c.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{Te(!1)}},className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${W||!p.trim()?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:W?"Verifying...":"Verify"}),e.jsx("button",{type:"submit",disabled:b||w,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${b||w?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-primary hover:bg-cc-primary-hover text-white cursor-pointer"}`,children:b?"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:()=>ce("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 ${E&&d?"text-cc-success":"text-cc-muted"}`,children:E&&d?"On":"Off"})]}),!d&&e.jsx("p",{className:"text-[11px] text-cc-warning",children:"Configure an Anthropic API key above to enable AI validation."}),E&&d&&e.jsxs(e.Fragment,{children:[e.jsxs("button",{type:"button",onClick:()=>ce("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 ${L?"text-cc-success":"text-cc-muted"}`,children:L?"On":"Off"})]}),e.jsxs("button",{type:"button",onClick:()=>ce("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:"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:ut,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 ${B?"text-cc-success":"text-cc-muted"}`,children:B?"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:we,onChange:t=>{const a=we;_(t.target.value),ne({dashboardModel:t.target.value},()=>_(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:Se,onChange:t=>{const a=Se,n=Number(t.target.value);G(n),ne({dashboardRunHour:n},()=>G(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:Ce,onChange:t=>{const a=Ce,n=Number(t.target.value);q(n),ne({dashboardMaxSessionsPerRun:n},()=>q(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 c.updateSettings({updateChannel:"stable"})}catch{y("prerelease");return}try{const t=await c.forceCheckForUpdate();P(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 c.updateSettings({updateChannel:"prerelease"})}catch{y("stable");return}try{const t=await c.forceCheckForUpdate();P(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;I(t);try{await c.updateSettings({dockerAutoUpdate:t})}catch{I(!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"}`})})]}),Ne&&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:Ne}),je&&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:je}),e.jsxs("div",{className:"flex flex-wrap gap-2",children:[e.jsx("button",{type:"button",onClick:xt,disabled:O,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${O?"bg-cc-hover text-cc-muted cursor-not-allowed":"bg-cc-hover hover:bg-cc-active text-cc-fg cursor-pointer"}`,children:O?"Checking...":"Check for updates"}),r!=null&&r.isServiceMode?e.jsx("button",{type:"button",onClick:pt,disabled:M||r.updateInProgress||!r.updateAvailable,className:`px-3 py-2 min-h-[44px] rounded-lg text-sm font-medium transition-colors ${M||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:M||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=!ke;bt(t),at(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:ke?"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{vt as SettingsPage};