@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.
- package/dist/assets/{AgentsPage-Dye7KBqi.js → AgentsPage-DO5MiZyp.js} +1 -1
- package/dist/assets/{CronManager-DTlT02dA.js → CronManager-BSNkRBGn.js} +1 -1
- package/dist/assets/{DashboardPage-fvKg3Yqp.js → DashboardPage-DqwmmyJ6.js} +1 -1
- package/dist/assets/{IntegrationsPage-F68cGNc0.js → IntegrationsPage-CxavpMnn.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-SoGpGzIF.js → LinearOAuthSettingsPage-Bs_FXqPA.js} +1 -1
- package/dist/assets/{LinearSettingsPage-CPvMIKgr.js → LinearSettingsPage-zerN3EmK.js} +1 -1
- package/dist/assets/{Playground-CF7prjX2.js → Playground-DNlOjOnC.js} +6 -6
- package/dist/assets/{PromptsPage-vx71p6l9.js → PromptsPage-D20LKwla.js} +1 -1
- package/dist/assets/{RunsPage-CH4PZDnZ.js → RunsPage-ho-uEj6h.js} +1 -1
- package/dist/assets/{SandboxManager-YH5E0Jto.js → SandboxManager-BY2NPfsU.js} +1 -1
- package/dist/assets/SettingsPage-CvYCqkm1.js +1 -0
- package/dist/assets/{TailscalePage-OvQqy343.js → TailscalePage-pv47ZX2f.js} +1 -1
- package/dist/assets/{index-Daik8cew.js → index-C7qt99R_.js} +50 -50
- package/dist/assets/index-C9eyQq6F.css +1 -0
- package/dist/assets/{sw-register-CcXhHDAn.js → sw-register-DZ08pZIJ.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/auto-namer.test.ts +4 -0
- package/server/claude-adapter.ts +15 -1
- package/server/linear-connections.test.ts +4 -0
- package/server/routes/settings-routes.ts +10 -0
- package/server/routes.test.ts +76 -0
- package/server/session-orchestrator.test.ts +3 -0
- package/server/settings-manager.test.ts +3 -0
- package/server/settings-manager.ts +16 -1
- package/server/ws-bridge-codex.test.ts +6 -0
- package/dist/assets/SettingsPage-B2dQwyjl.js +0 -1
- package/dist/assets/index-DMoY-FDD.css +0 -1
package/server/routes.test.ts
CHANGED
|
@@ -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};
|