@hellcoder/companion 0.107.1-preview.20260708075901.9f18d7b → 0.108.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 (40) hide show
  1. package/dist/assets/{AgentsPage-t4s7ZO2P.js → AgentsPage-DI5hdCnz.js} +5 -5
  2. package/dist/assets/{CronManager-C_0weC_z.js → CronManager-yX8i4C_S.js} +1 -1
  3. package/dist/assets/DashboardPage-D1nc0wHf.js +1 -0
  4. package/dist/assets/{IntegrationsPage-Gte53t7Z.js → IntegrationsPage-iSfvR9vX.js} +1 -1
  5. package/dist/assets/{LinearOAuthSettingsPage-DHJ73rFh.js → LinearOAuthSettingsPage-omExXsGc.js} +1 -1
  6. package/dist/assets/{LinearSettingsPage-D7ZHYkum.js → LinearSettingsPage-C4oBf1iO.js} +1 -1
  7. package/dist/assets/{Playground-DBWsv4R6.js → Playground-CjiZI9QY.js} +1 -1
  8. package/dist/assets/{PromptsPage-Cnjs3hD9.js → PromptsPage-CU6z49U6.js} +1 -1
  9. package/dist/assets/{RunsPage-B5PsNevy.js → RunsPage-mcU7OUoZ.js} +1 -1
  10. package/dist/assets/{SandboxManager-DpFA1it7.js → SandboxManager-BkYS-dK5.js} +1 -1
  11. package/dist/assets/SettingsPage-CyHMmTBB.js +1 -0
  12. package/dist/assets/{TailscalePage-CMG1g7e2.js → TailscalePage-CRyiyMcJ.js} +1 -1
  13. package/dist/assets/index-DMoY-FDD.css +1 -0
  14. package/dist/assets/index-DOle27vf.js +136 -0
  15. package/dist/assets/{sw-register-CS-6kwSW.js → sw-register-Dqdfho7x.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 +16 -0
  20. package/server/claude-cli-runner.ts +92 -0
  21. package/server/dashboard-scheduler.test.ts +73 -0
  22. package/server/dashboard-scheduler.ts +46 -0
  23. package/server/dashboard-store.test.ts +101 -0
  24. package/server/dashboard-store.ts +121 -0
  25. package/server/dashboard-summarizer.test.ts +216 -0
  26. package/server/dashboard-summarizer.ts +290 -0
  27. package/server/dashboard-types.ts +70 -0
  28. package/server/index.ts +4 -0
  29. package/server/linear-connections.test.ts +16 -0
  30. package/server/routes/dashboard-routes.test.ts +139 -0
  31. package/server/routes/dashboard-routes.ts +101 -0
  32. package/server/routes/settings-routes.ts +47 -1
  33. package/server/routes.test.ts +112 -0
  34. package/server/routes.ts +4 -0
  35. package/server/settings-manager.test.ts +12 -0
  36. package/server/settings-manager.ts +45 -1
  37. package/server/ws-bridge-codex.test.ts +24 -0
  38. package/dist/assets/SettingsPage-v8ndyXOz.js +0 -1
  39. package/dist/assets/index-Bp3e_166.js +0 -136
  40. package/dist/assets/index-D4AeOLki.css +0 -1
@@ -0,0 +1,139 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from "vitest";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import { Hono } from "hono";
6
+ import { registerDashboardRoutes } from "./dashboard-routes.js";
7
+ import { DashboardStore } from "../dashboard-store.js";
8
+ import { _resetForTest } from "../settings-manager.js";
9
+ import { _resetDashboardRunStateForTest } from "../dashboard-summarizer.js";
10
+ import type { DashboardRunMeta, DashboardSessionSummary } from "../dashboard-types.js";
11
+
12
+ // Validates the dashboard REST surface: GET serves purely from the stored
13
+ // nightly data (never live sessions), joins companion-managed sessions via
14
+ // cliSessionId, and the manual run trigger requires the Claude Code CLI
15
+ // (the summarizer authenticates via the CLI login, not an API key).
16
+
17
+ const CLI_SESSION_ID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
18
+
19
+ function makeSummary(overrides: Partial<DashboardSessionSummary> = {}): DashboardSessionSummary {
20
+ return {
21
+ sessionId: CLI_SESSION_ID,
22
+ cwd: "/root/projects/demo",
23
+ gitBranch: "main",
24
+ slug: "demo-task",
25
+ summary: "Work is underway.",
26
+ status: "in_progress",
27
+ openItems: ["finish tests"],
28
+ archivable: false,
29
+ transcriptMtimeMs: 1000,
30
+ lastActivityAt: 1000,
31
+ summarizedAt: 2000,
32
+ model: "claude-haiku-4-5",
33
+ ...overrides,
34
+ };
35
+ }
36
+
37
+ function buildApp(store: DashboardStore, cliAvailable: boolean): Hono {
38
+ const app = new Hono();
39
+ registerDashboardRoutes(app, {
40
+ store,
41
+ isCliAvailable: () => cliAvailable,
42
+ listCompanionSessions: () => [
43
+ {
44
+ sessionId: "companion-1",
45
+ cliSessionId: CLI_SESSION_ID,
46
+ name: "Fix login",
47
+ userName: "Moritz",
48
+ archived: false,
49
+ },
50
+ ],
51
+ });
52
+ return app;
53
+ }
54
+
55
+ describe("dashboard routes", () => {
56
+ let storeDir: string;
57
+ let store: DashboardStore;
58
+ let app: Hono;
59
+
60
+ beforeEach(() => {
61
+ storeDir = mkdtempSync(join(tmpdir(), "dashboard-routes-test-"));
62
+ store = new DashboardStore(storeDir);
63
+ _resetForTest(join(storeDir, "settings.json"));
64
+ _resetDashboardRunStateForTest();
65
+ app = buildApp(store, true);
66
+ });
67
+
68
+ afterEach(() => {
69
+ _resetForTest();
70
+ rmSync(storeDir, { recursive: true, force: true });
71
+ });
72
+
73
+ it("GET /dashboard returns stored summaries joined with companion sessions", async () => {
74
+ store.saveSummary(makeSummary());
75
+ const meta: DashboardRunMeta = {
76
+ lastRunAt: 1,
77
+ lastRunCompletedAt: 2,
78
+ lastRunStatus: "success",
79
+ trigger: "scheduled",
80
+ model: "claude-haiku-4-5",
81
+ sessionsProcessed: 1,
82
+ sessionsSkipped: 0,
83
+ sessionsFailed: 0,
84
+ };
85
+ store.saveRunMeta(meta);
86
+
87
+ const res = await app.request("/dashboard");
88
+ expect(res.status).toBe(200);
89
+ const data = await res.json();
90
+
91
+ expect(data.enabled).toBe(false); // opt-in defaults to off
92
+ expect(data.claudeCliAvailable).toBe(true);
93
+ expect(data.runMeta).toEqual(meta);
94
+ expect(data.progress.state).toBe("idle");
95
+ expect(data.sessions).toHaveLength(1);
96
+ expect(data.sessions[0].companionSessionId).toBe("companion-1");
97
+ expect(data.sessions[0].displayName).toBe("Fix login");
98
+ expect(data.sessions[0].userName).toBe("Moritz");
99
+ });
100
+
101
+ it("GET /dashboard leaves external (non-companion) sessions unlinked", async () => {
102
+ store.saveSummary(makeSummary({ sessionId: "11111111-2222-3333-4444-555555555555" }));
103
+ const res = await app.request("/dashboard");
104
+ const data = await res.json();
105
+ expect(data.sessions[0].companionSessionId).toBeUndefined();
106
+ });
107
+
108
+ it("POST /dashboard/run returns 400 when the Claude CLI is unavailable", async () => {
109
+ const noCliApp = buildApp(store, false);
110
+ const res = await noCliApp.request("/dashboard/run", { method: "POST" });
111
+ expect(res.status).toBe(400);
112
+ const data = await res.json();
113
+ expect(data.error).toContain("Claude Code CLI");
114
+ });
115
+
116
+ it("POST /dashboard/run starts a background run when the CLI is available", async () => {
117
+ // Point discovery at an empty dir so the background run finds no sessions
118
+ // (and therefore never actually invokes the CLI).
119
+ const emptyProjects = mkdtempSync(join(tmpdir(), "dashboard-empty-projects-"));
120
+ const prevProjectsDir = process.env.CLAUDE_PROJECTS_DIR;
121
+ process.env.CLAUDE_PROJECTS_DIR = emptyProjects;
122
+ try {
123
+ const res = await app.request("/dashboard/run", { method: "POST" });
124
+ expect(res.status).toBe(200);
125
+ expect((await res.json()).started).toBe(true);
126
+
127
+ // Wait for the fire-and-forget run to finish and write run meta.
128
+ await new Promise((resolve) => setTimeout(resolve, 50));
129
+ const status = await app.request("/dashboard/run/status");
130
+ const statusData = await status.json();
131
+ expect(statusData.progress.state).toBe("idle");
132
+ expect(statusData.runMeta?.trigger).toBe("manual");
133
+ } finally {
134
+ if (prevProjectsDir === undefined) delete process.env.CLAUDE_PROJECTS_DIR;
135
+ else process.env.CLAUDE_PROJECTS_DIR = prevProjectsDir;
136
+ rmSync(emptyProjects, { recursive: true, force: true });
137
+ }
138
+ });
139
+ });
@@ -0,0 +1,101 @@
1
+ import type { Hono } from "hono";
2
+ import { getSettings } from "../settings-manager.js";
3
+ import { isClaudeCliAvailable } from "../claude-cli-runner.js";
4
+ import { getDashboardStore, type DashboardStore } from "../dashboard-store.js";
5
+ import {
6
+ getDashboardRunProgress,
7
+ isDashboardRunActive,
8
+ runDashboardUpdate,
9
+ } from "../dashboard-summarizer.js";
10
+ import type { DashboardSessionSummary } from "../dashboard-types.js";
11
+
12
+ /**
13
+ * Minimal slice of SdkSessionInfo needed to join companion-managed sessions
14
+ * onto discovered Claude transcripts (matched via cliSessionId).
15
+ */
16
+ export interface CompanionSessionLink {
17
+ sessionId: string;
18
+ cliSessionId?: string;
19
+ name?: string;
20
+ userName?: string;
21
+ archived?: boolean;
22
+ }
23
+
24
+ export interface DashboardRouteDeps {
25
+ listCompanionSessions?: () => CompanionSessionLink[];
26
+ store?: DashboardStore;
27
+ /** Override the Claude CLI availability check (tests). */
28
+ isCliAvailable?: () => boolean;
29
+ }
30
+
31
+ export interface DashboardSessionEntry extends DashboardSessionSummary {
32
+ /** Companion session id when this transcript belongs to a companion-managed session. */
33
+ companionSessionId?: string;
34
+ companionArchived?: boolean;
35
+ displayName?: string;
36
+ userName?: string;
37
+ }
38
+
39
+ export function registerDashboardRoutes(api: Hono, deps: DashboardRouteDeps = {}): void {
40
+ const store = () => deps.store || getDashboardStore();
41
+ const cliAvailable = () => (deps.isCliAvailable ? deps.isCliAvailable() : isClaudeCliAvailable());
42
+
43
+ // Dashboard data — served purely from the nightly store, never from live sessions.
44
+ api.get("/dashboard", (c) => {
45
+ const settings = getSettings();
46
+ const summaries = store().loadAllSummaries();
47
+
48
+ const links = new Map<string, CompanionSessionLink>();
49
+ try {
50
+ for (const session of deps.listCompanionSessions?.() ?? []) {
51
+ if (session.cliSessionId) links.set(session.cliSessionId, session);
52
+ }
53
+ } catch (err) {
54
+ console.warn("[dashboard] Failed to enumerate companion sessions:", err);
55
+ }
56
+
57
+ const sessions: DashboardSessionEntry[] = summaries.map((summary) => {
58
+ const link = links.get(summary.sessionId);
59
+ return {
60
+ ...summary,
61
+ companionSessionId: link?.sessionId,
62
+ companionArchived: link?.archived === true ? true : undefined,
63
+ displayName: link?.name,
64
+ userName: link?.userName,
65
+ };
66
+ });
67
+
68
+ return c.json({
69
+ enabled: settings.dashboardEnabled,
70
+ model: settings.dashboardModel,
71
+ runHour: settings.dashboardRunHour,
72
+ claudeCliAvailable: cliAvailable(),
73
+ runMeta: store().loadRunMeta(),
74
+ progress: getDashboardRunProgress(),
75
+ sessions,
76
+ });
77
+ });
78
+
79
+ // Manual "Update now" trigger. Runs in the background; poll run/status for progress.
80
+ api.post("/dashboard/run", (c) => {
81
+ if (isDashboardRunActive()) {
82
+ return c.json({ error: "A dashboard update is already running", progress: getDashboardRunProgress() }, 409);
83
+ }
84
+ if (!cliAvailable()) {
85
+ return c.json({ error: "Claude Code CLI not found — the summarizer uses your Claude Code login" }, 400);
86
+ }
87
+
88
+ runDashboardUpdate({ trigger: "manual", store: deps.store }).catch((err) => {
89
+ console.warn("[dashboard] Manual update failed:", err);
90
+ });
91
+
92
+ return c.json({ started: true });
93
+ });
94
+
95
+ api.get("/dashboard/run/status", (c) => {
96
+ return c.json({
97
+ progress: getDashboardRunProgress(),
98
+ runMeta: store().loadRunMeta(),
99
+ });
100
+ });
101
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Hono } from "hono";
2
- import { DEFAULT_ANTHROPIC_MODEL, getSettings, updateSettings, type UpdateChannel, type CliBridgeMode } from "../settings-manager.js";
2
+ import { DASHBOARD_MODEL_OPTIONS, DEFAULT_ANTHROPIC_MODEL, getSettings, updateSettings, type UpdateChannel, type CliBridgeMode } from "../settings-manager.js";
3
3
  import { linearCache } from "../linear-cache.js";
4
4
  import { listConnections } from "../linear-connections.js";
5
5
  import { hasContainerCodexAuth } from "../codex-container-auth.js";
@@ -26,6 +26,10 @@ export function registerSettingsRoutes(api: Hono): void {
26
26
  aiValidationEnabled: settings.aiValidationEnabled,
27
27
  aiValidationAutoApprove: settings.aiValidationAutoApprove,
28
28
  aiValidationAutoDeny: settings.aiValidationAutoDeny,
29
+ dashboardEnabled: settings.dashboardEnabled,
30
+ dashboardModel: settings.dashboardModel,
31
+ dashboardRunHour: settings.dashboardRunHour,
32
+ dashboardMaxSessionsPerRun: settings.dashboardMaxSessionsPerRun,
29
33
  publicUrl: settings.publicUrl,
30
34
  updateChannel: settings.updateChannel,
31
35
  dockerAutoUpdate: settings.dockerAutoUpdate,
@@ -72,6 +76,26 @@ export function registerSettingsRoutes(api: Hono): void {
72
76
  if (body.aiValidationAutoDeny !== undefined && typeof body.aiValidationAutoDeny !== "boolean") {
73
77
  return c.json({ error: "aiValidationAutoDeny must be a boolean" }, 400);
74
78
  }
79
+ if (body.dashboardEnabled !== undefined && typeof body.dashboardEnabled !== "boolean") {
80
+ return c.json({ error: "dashboardEnabled must be a boolean" }, 400);
81
+ }
82
+ if (body.dashboardModel !== undefined && !DASHBOARD_MODEL_OPTIONS.includes(body.dashboardModel)) {
83
+ return c.json({ error: `dashboardModel must be one of: ${DASHBOARD_MODEL_OPTIONS.join(", ")}` }, 400);
84
+ }
85
+ if (
86
+ body.dashboardRunHour !== undefined
87
+ && (typeof body.dashboardRunHour !== "number" || !Number.isInteger(body.dashboardRunHour)
88
+ || body.dashboardRunHour < 0 || body.dashboardRunHour > 23)
89
+ ) {
90
+ return c.json({ error: "dashboardRunHour must be an integer between 0 and 23" }, 400);
91
+ }
92
+ if (
93
+ body.dashboardMaxSessionsPerRun !== undefined
94
+ && (typeof body.dashboardMaxSessionsPerRun !== "number" || !Number.isInteger(body.dashboardMaxSessionsPerRun)
95
+ || body.dashboardMaxSessionsPerRun < 1 || body.dashboardMaxSessionsPerRun > 200)
96
+ ) {
97
+ return c.json({ error: "dashboardMaxSessionsPerRun must be an integer between 1 and 200" }, 400);
98
+ }
75
99
  if (body.publicUrl !== undefined) {
76
100
  if (typeof body.publicUrl !== "string") {
77
101
  return c.json({ error: "publicUrl must be a string" }, 400);
@@ -122,6 +146,8 @@ export function registerSettingsRoutes(api: Hono): void {
122
146
  || body.linearOAuthWebhookSecret !== undefined
123
147
  || body.aiValidationEnabled !== undefined || body.aiValidationAutoApprove !== undefined
124
148
  || body.aiValidationAutoDeny !== undefined
149
+ || body.dashboardEnabled !== undefined || body.dashboardModel !== undefined
150
+ || body.dashboardRunHour !== undefined || body.dashboardMaxSessionsPerRun !== undefined
125
151
  || body.publicUrl !== undefined
126
152
  || body.updateChannel !== undefined
127
153
  || body.dockerAutoUpdate !== undefined
@@ -208,6 +234,22 @@ export function registerSettingsRoutes(api: Hono): void {
208
234
  typeof body.aiValidationAutoDeny === "boolean"
209
235
  ? body.aiValidationAutoDeny
210
236
  : undefined,
237
+ dashboardEnabled:
238
+ typeof body.dashboardEnabled === "boolean"
239
+ ? body.dashboardEnabled
240
+ : undefined,
241
+ dashboardModel:
242
+ typeof body.dashboardModel === "string"
243
+ ? body.dashboardModel
244
+ : undefined,
245
+ dashboardRunHour:
246
+ typeof body.dashboardRunHour === "number"
247
+ ? body.dashboardRunHour
248
+ : undefined,
249
+ dashboardMaxSessionsPerRun:
250
+ typeof body.dashboardMaxSessionsPerRun === "number"
251
+ ? body.dashboardMaxSessionsPerRun
252
+ : undefined,
211
253
  publicUrl:
212
254
  typeof body.publicUrl === "string"
213
255
  ? body.publicUrl.trim().replace(/\/+$/, "")
@@ -249,6 +291,10 @@ export function registerSettingsRoutes(api: Hono): void {
249
291
  aiValidationEnabled: settings.aiValidationEnabled,
250
292
  aiValidationAutoApprove: settings.aiValidationAutoApprove,
251
293
  aiValidationAutoDeny: settings.aiValidationAutoDeny,
294
+ dashboardEnabled: settings.dashboardEnabled,
295
+ dashboardModel: settings.dashboardModel,
296
+ dashboardRunHour: settings.dashboardRunHour,
297
+ dashboardMaxSessionsPerRun: settings.dashboardMaxSessionsPerRun,
252
298
  publicUrl: settings.publicUrl,
253
299
  updateChannel: settings.updateChannel,
254
300
  dockerAutoUpdate: settings.dockerAutoUpdate,
@@ -102,6 +102,10 @@ vi.mock("./settings-manager.js", () => ({
102
102
  aiValidationEnabled: false,
103
103
  aiValidationAutoApprove: true,
104
104
  aiValidationAutoDeny: false,
105
+ dashboardEnabled: false,
106
+ dashboardModel: "claude-haiku-4-5",
107
+ dashboardRunHour: 3,
108
+ dashboardMaxSessionsPerRun: 30,
105
109
  publicUrl: "",
106
110
  updateChannel: "stable",
107
111
  dockerAutoUpdate: false,
@@ -1146,6 +1150,10 @@ describe("GET /api/sessions/:id/archive-info", () => {
1146
1150
  aiValidationEnabled: false,
1147
1151
  aiValidationAutoApprove: true,
1148
1152
  aiValidationAutoDeny: false,
1153
+ dashboardEnabled: false,
1154
+ dashboardModel: "claude-haiku-4-5",
1155
+ dashboardRunHour: 3,
1156
+ dashboardMaxSessionsPerRun: 30,
1149
1157
  publicUrl: "",
1150
1158
  updateChannel: "stable",
1151
1159
  dockerAutoUpdate: false,
@@ -1517,6 +1525,10 @@ describe("GET /api/settings", () => {
1517
1525
  aiValidationEnabled: false,
1518
1526
  aiValidationAutoApprove: true,
1519
1527
  aiValidationAutoDeny: false,
1528
+ dashboardEnabled: false,
1529
+ dashboardModel: "claude-haiku-4-5",
1530
+ dashboardRunHour: 3,
1531
+ dashboardMaxSessionsPerRun: 30,
1520
1532
  publicUrl: "",
1521
1533
  updateChannel: "stable",
1522
1534
  dockerAutoUpdate: false,
@@ -1546,6 +1558,10 @@ describe("GET /api/settings", () => {
1546
1558
  aiValidationEnabled: false,
1547
1559
  aiValidationAutoApprove: true,
1548
1560
  aiValidationAutoDeny: false,
1561
+ dashboardEnabled: false,
1562
+ dashboardModel: "claude-haiku-4-5",
1563
+ dashboardRunHour: 3,
1564
+ dashboardMaxSessionsPerRun: 30,
1549
1565
  publicUrl: "",
1550
1566
  updateChannel: "stable",
1551
1567
  dockerAutoUpdate: false,
@@ -1575,6 +1591,10 @@ describe("GET /api/settings", () => {
1575
1591
  aiValidationEnabled: false,
1576
1592
  aiValidationAutoApprove: true,
1577
1593
  aiValidationAutoDeny: false,
1594
+ dashboardEnabled: false,
1595
+ dashboardModel: "claude-haiku-4-5",
1596
+ dashboardRunHour: 3,
1597
+ dashboardMaxSessionsPerRun: 30,
1578
1598
  publicUrl: "",
1579
1599
  updateChannel: "stable",
1580
1600
  dockerAutoUpdate: false,
@@ -1604,6 +1624,10 @@ describe("GET /api/settings", () => {
1604
1624
  aiValidationEnabled: false,
1605
1625
  aiValidationAutoApprove: true,
1606
1626
  aiValidationAutoDeny: false,
1627
+ dashboardEnabled: false,
1628
+ dashboardModel: "claude-haiku-4-5",
1629
+ dashboardRunHour: 3,
1630
+ dashboardMaxSessionsPerRun: 30,
1607
1631
  publicUrl: "",
1608
1632
  updateChannel: "stable",
1609
1633
  dockerAutoUpdate: false,
@@ -1634,6 +1658,10 @@ describe("GET /api/settings", () => {
1634
1658
  aiValidationEnabled: false,
1635
1659
  aiValidationAutoApprove: true,
1636
1660
  aiValidationAutoDeny: false,
1661
+ dashboardEnabled: false,
1662
+ dashboardModel: "claude-haiku-4-5",
1663
+ dashboardRunHour: 3,
1664
+ dashboardMaxSessionsPerRun: 30,
1637
1665
  publicUrl: "https://example.com",
1638
1666
  updateChannel: "stable",
1639
1667
  dockerAutoUpdate: false,
@@ -1672,6 +1700,10 @@ describe("PUT /api/settings", () => {
1672
1700
  aiValidationEnabled: false,
1673
1701
  aiValidationAutoApprove: true,
1674
1702
  aiValidationAutoDeny: false,
1703
+ dashboardEnabled: false,
1704
+ dashboardModel: "claude-haiku-4-5",
1705
+ dashboardRunHour: 3,
1706
+ dashboardMaxSessionsPerRun: 30,
1675
1707
  publicUrl: "",
1676
1708
  updateChannel: "stable",
1677
1709
  dockerAutoUpdate: false,
@@ -1723,6 +1755,10 @@ describe("PUT /api/settings", () => {
1723
1755
  aiValidationEnabled: false,
1724
1756
  aiValidationAutoApprove: true,
1725
1757
  aiValidationAutoDeny: false,
1758
+ dashboardEnabled: false,
1759
+ dashboardModel: "claude-haiku-4-5",
1760
+ dashboardRunHour: 3,
1761
+ dashboardMaxSessionsPerRun: 30,
1726
1762
  publicUrl: "",
1727
1763
  updateChannel: "stable",
1728
1764
  dockerAutoUpdate: false,
@@ -1752,6 +1788,10 @@ describe("PUT /api/settings", () => {
1752
1788
  aiValidationEnabled: false,
1753
1789
  aiValidationAutoApprove: true,
1754
1790
  aiValidationAutoDeny: false,
1791
+ dashboardEnabled: false,
1792
+ dashboardModel: "claude-haiku-4-5",
1793
+ dashboardRunHour: 3,
1794
+ dashboardMaxSessionsPerRun: 30,
1755
1795
  publicUrl: "",
1756
1796
  updateChannel: "stable",
1757
1797
  dockerAutoUpdate: false,
@@ -1798,6 +1838,10 @@ describe("PUT /api/settings", () => {
1798
1838
  aiValidationEnabled: false,
1799
1839
  aiValidationAutoApprove: true,
1800
1840
  aiValidationAutoDeny: false,
1841
+ dashboardEnabled: false,
1842
+ dashboardModel: "claude-haiku-4-5",
1843
+ dashboardRunHour: 3,
1844
+ dashboardMaxSessionsPerRun: 30,
1801
1845
  publicUrl: "",
1802
1846
  updateChannel: "stable",
1803
1847
  dockerAutoUpdate: false,
@@ -1895,6 +1939,10 @@ describe("PUT /api/settings", () => {
1895
1939
  aiValidationEnabled: false,
1896
1940
  aiValidationAutoApprove: true,
1897
1941
  aiValidationAutoDeny: false,
1942
+ dashboardEnabled: false,
1943
+ dashboardModel: "claude-haiku-4-5",
1944
+ dashboardRunHour: 3,
1945
+ dashboardMaxSessionsPerRun: 30,
1898
1946
  publicUrl: "https://my-server.com",
1899
1947
  updateChannel: "stable",
1900
1948
  dockerAutoUpdate: false,
@@ -2147,6 +2195,10 @@ describe("GET /api/linear/issues", () => {
2147
2195
  aiValidationEnabled: false,
2148
2196
  aiValidationAutoApprove: true,
2149
2197
  aiValidationAutoDeny: false,
2198
+ dashboardEnabled: false,
2199
+ dashboardModel: "claude-haiku-4-5",
2200
+ dashboardRunHour: 3,
2201
+ dashboardMaxSessionsPerRun: 30,
2150
2202
  publicUrl: "",
2151
2203
  updateChannel: "stable",
2152
2204
  dockerAutoUpdate: false,
@@ -2183,6 +2235,10 @@ describe("GET /api/linear/issues", () => {
2183
2235
  aiValidationEnabled: false,
2184
2236
  aiValidationAutoApprove: true,
2185
2237
  aiValidationAutoDeny: false,
2238
+ dashboardEnabled: false,
2239
+ dashboardModel: "claude-haiku-4-5",
2240
+ dashboardRunHour: 3,
2241
+ dashboardMaxSessionsPerRun: 30,
2186
2242
  publicUrl: "",
2187
2243
  updateChannel: "stable",
2188
2244
  dockerAutoUpdate: false,
@@ -2272,6 +2328,10 @@ describe("GET /api/linear/issues", () => {
2272
2328
  aiValidationEnabled: false,
2273
2329
  aiValidationAutoApprove: true,
2274
2330
  aiValidationAutoDeny: false,
2331
+ dashboardEnabled: false,
2332
+ dashboardModel: "claude-haiku-4-5",
2333
+ dashboardRunHour: 3,
2334
+ dashboardMaxSessionsPerRun: 30,
2275
2335
  publicUrl: "",
2276
2336
  updateChannel: "stable",
2277
2337
  dockerAutoUpdate: false,
@@ -2368,6 +2428,10 @@ describe("GET /api/linear/issues", () => {
2368
2428
  aiValidationEnabled: false,
2369
2429
  aiValidationAutoApprove: true,
2370
2430
  aiValidationAutoDeny: false,
2431
+ dashboardEnabled: false,
2432
+ dashboardModel: "claude-haiku-4-5",
2433
+ dashboardRunHour: 3,
2434
+ dashboardMaxSessionsPerRun: 30,
2371
2435
  publicUrl: "",
2372
2436
  updateChannel: "stable",
2373
2437
  dockerAutoUpdate: false,
@@ -2429,6 +2493,10 @@ describe("GET /api/linear/connection", () => {
2429
2493
  aiValidationEnabled: false,
2430
2494
  aiValidationAutoApprove: true,
2431
2495
  aiValidationAutoDeny: false,
2496
+ dashboardEnabled: false,
2497
+ dashboardModel: "claude-haiku-4-5",
2498
+ dashboardRunHour: 3,
2499
+ dashboardMaxSessionsPerRun: 30,
2432
2500
  publicUrl: "",
2433
2501
  updateChannel: "stable",
2434
2502
  dockerAutoUpdate: false,
@@ -2465,6 +2533,10 @@ describe("GET /api/linear/connection", () => {
2465
2533
  aiValidationEnabled: false,
2466
2534
  aiValidationAutoApprove: true,
2467
2535
  aiValidationAutoDeny: false,
2536
+ dashboardEnabled: false,
2537
+ dashboardModel: "claude-haiku-4-5",
2538
+ dashboardRunHour: 3,
2539
+ dashboardMaxSessionsPerRun: 30,
2468
2540
  publicUrl: "",
2469
2541
  updateChannel: "stable",
2470
2542
  dockerAutoUpdate: false,
@@ -2523,6 +2595,10 @@ describe("POST /api/linear/issues/:id/transition", () => {
2523
2595
  aiValidationEnabled: false,
2524
2596
  aiValidationAutoApprove: true,
2525
2597
  aiValidationAutoDeny: false,
2598
+ dashboardEnabled: false,
2599
+ dashboardModel: "claude-haiku-4-5",
2600
+ dashboardRunHour: 3,
2601
+ dashboardMaxSessionsPerRun: 30,
2526
2602
  publicUrl: "",
2527
2603
  updateChannel: "stable",
2528
2604
  dockerAutoUpdate: false,
@@ -2563,6 +2639,10 @@ describe("POST /api/linear/issues/:id/transition", () => {
2563
2639
  aiValidationEnabled: false,
2564
2640
  aiValidationAutoApprove: true,
2565
2641
  aiValidationAutoDeny: false,
2642
+ dashboardEnabled: false,
2643
+ dashboardModel: "claude-haiku-4-5",
2644
+ dashboardRunHour: 3,
2645
+ dashboardMaxSessionsPerRun: 30,
2566
2646
  publicUrl: "",
2567
2647
  updateChannel: "stable",
2568
2648
  dockerAutoUpdate: false,
@@ -2602,6 +2682,10 @@ describe("POST /api/linear/issues/:id/transition", () => {
2602
2682
  aiValidationEnabled: false,
2603
2683
  aiValidationAutoApprove: true,
2604
2684
  aiValidationAutoDeny: false,
2685
+ dashboardEnabled: false,
2686
+ dashboardModel: "claude-haiku-4-5",
2687
+ dashboardRunHour: 3,
2688
+ dashboardMaxSessionsPerRun: 30,
2605
2689
  publicUrl: "",
2606
2690
  updateChannel: "stable",
2607
2691
  dockerAutoUpdate: false,
@@ -2643,6 +2727,10 @@ describe("POST /api/linear/issues/:id/transition", () => {
2643
2727
  aiValidationEnabled: false,
2644
2728
  aiValidationAutoApprove: true,
2645
2729
  aiValidationAutoDeny: false,
2730
+ dashboardEnabled: false,
2731
+ dashboardModel: "claude-haiku-4-5",
2732
+ dashboardRunHour: 3,
2733
+ dashboardMaxSessionsPerRun: 30,
2646
2734
  publicUrl: "",
2647
2735
  updateChannel: "stable",
2648
2736
  dockerAutoUpdate: false,
@@ -2718,6 +2806,10 @@ describe("POST /api/linear/issues/:id/transition", () => {
2718
2806
  aiValidationEnabled: false,
2719
2807
  aiValidationAutoApprove: true,
2720
2808
  aiValidationAutoDeny: false,
2809
+ dashboardEnabled: false,
2810
+ dashboardModel: "claude-haiku-4-5",
2811
+ dashboardRunHour: 3,
2812
+ dashboardMaxSessionsPerRun: 30,
2721
2813
  publicUrl: "",
2722
2814
  updateChannel: "stable",
2723
2815
  dockerAutoUpdate: false,
@@ -2772,6 +2864,10 @@ describe("GET /api/linear/projects", () => {
2772
2864
  aiValidationEnabled: false,
2773
2865
  aiValidationAutoApprove: true,
2774
2866
  aiValidationAutoDeny: false,
2867
+ dashboardEnabled: false,
2868
+ dashboardModel: "claude-haiku-4-5",
2869
+ dashboardRunHour: 3,
2870
+ dashboardMaxSessionsPerRun: 30,
2775
2871
  publicUrl: "",
2776
2872
  updateChannel: "stable",
2777
2873
  dockerAutoUpdate: false,
@@ -2808,6 +2904,10 @@ describe("GET /api/linear/projects", () => {
2808
2904
  aiValidationEnabled: false,
2809
2905
  aiValidationAutoApprove: true,
2810
2906
  aiValidationAutoDeny: false,
2907
+ dashboardEnabled: false,
2908
+ dashboardModel: "claude-haiku-4-5",
2909
+ dashboardRunHour: 3,
2910
+ dashboardMaxSessionsPerRun: 30,
2811
2911
  publicUrl: "",
2812
2912
  updateChannel: "stable",
2813
2913
  dockerAutoUpdate: false,
@@ -2874,6 +2974,10 @@ describe("GET /api/linear/project-issues", () => {
2874
2974
  aiValidationEnabled: false,
2875
2975
  aiValidationAutoApprove: true,
2876
2976
  aiValidationAutoDeny: false,
2977
+ dashboardEnabled: false,
2978
+ dashboardModel: "claude-haiku-4-5",
2979
+ dashboardRunHour: 3,
2980
+ dashboardMaxSessionsPerRun: 30,
2877
2981
  publicUrl: "",
2878
2982
  updateChannel: "stable",
2879
2983
  dockerAutoUpdate: false,
@@ -2910,6 +3014,10 @@ describe("GET /api/linear/project-issues", () => {
2910
3014
  aiValidationEnabled: false,
2911
3015
  aiValidationAutoApprove: true,
2912
3016
  aiValidationAutoDeny: false,
3017
+ dashboardEnabled: false,
3018
+ dashboardModel: "claude-haiku-4-5",
3019
+ dashboardRunHour: 3,
3020
+ dashboardMaxSessionsPerRun: 30,
2913
3021
  publicUrl: "",
2914
3022
  updateChannel: "stable",
2915
3023
  dockerAutoUpdate: false,
@@ -2991,6 +3099,10 @@ describe("GET /api/linear/project-issues", () => {
2991
3099
  aiValidationEnabled: false,
2992
3100
  aiValidationAutoApprove: true,
2993
3101
  aiValidationAutoDeny: false,
3102
+ dashboardEnabled: false,
3103
+ dashboardModel: "claude-haiku-4-5",
3104
+ dashboardRunHour: 3,
3105
+ dashboardMaxSessionsPerRun: 30,
2994
3106
  publicUrl: "",
2995
3107
  updateChannel: "stable",
2996
3108
  dockerAutoUpdate: false,
package/server/routes.ts CHANGED
@@ -26,6 +26,7 @@ import { registerMetricsRoutes } from "./routes/metrics-routes.js";
26
26
  import { registerLinearAgentWebhookRoute, registerLinearAgentProtectedRoutes } from "./routes/linear-agent-routes.js";
27
27
  import { registerPromptRoutes } from "./routes/prompt-routes.js";
28
28
  import { registerSettingsRoutes } from "./routes/settings-routes.js";
29
+ import { registerDashboardRoutes } from "./routes/dashboard-routes.js";
29
30
  import { registerTailscaleRoutes } from "./routes/tailscale-routes.js";
30
31
  import { registerGitRoutes } from "./routes/git-routes.js";
31
32
  import { registerSystemRoutes } from "./routes/system-routes.js";
@@ -1275,6 +1276,9 @@ export function createRoutes(
1275
1276
 
1276
1277
  registerPromptRoutes(api);
1277
1278
  registerSettingsRoutes(api);
1279
+ registerDashboardRoutes(api, {
1280
+ listCompanionSessions: () => launcher.listSessions(),
1281
+ });
1278
1282
 
1279
1283
  // ─── Tailscale ──────────────────────────────────────────────────────
1280
1284
 
@@ -45,6 +45,10 @@ describe("settings-manager", () => {
45
45
  aiValidationEnabled: false,
46
46
  aiValidationAutoApprove: true,
47
47
  aiValidationAutoDeny: false,
48
+ dashboardEnabled: false,
49
+ dashboardModel: "claude-haiku-4-5",
50
+ dashboardRunHour: 3,
51
+ dashboardMaxSessionsPerRun: 30,
48
52
  publicUrl: "",
49
53
  updateChannel: "stable",
50
54
  dockerAutoUpdate: false,
@@ -105,6 +109,10 @@ describe("settings-manager", () => {
105
109
  aiValidationEnabled: false,
106
110
  aiValidationAutoApprove: true,
107
111
  aiValidationAutoDeny: false,
112
+ dashboardEnabled: false,
113
+ dashboardModel: "claude-haiku-4-5",
114
+ dashboardRunHour: 3,
115
+ dashboardMaxSessionsPerRun: 30,
108
116
  publicUrl: "",
109
117
  updateChannel: "stable",
110
118
  dockerAutoUpdate: false,
@@ -189,6 +197,10 @@ describe("settings-manager", () => {
189
197
  aiValidationEnabled: false,
190
198
  aiValidationAutoApprove: true,
191
199
  aiValidationAutoDeny: false,
200
+ dashboardEnabled: false,
201
+ dashboardModel: "claude-haiku-4-5",
202
+ dashboardRunHour: 3,
203
+ dashboardMaxSessionsPerRun: 30,
192
204
  publicUrl: "",
193
205
  updateChannel: "stable",
194
206
  dockerAutoUpdate: false,