diff --git a/package-lock.json b/package-lock.json
index eefe6eb0..5ec2ef4a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -96,9 +96,9 @@
}
},
"node_modules/@anthropic-ai/claude-agent-sdk": {
- "version": "0.2.91",
- "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.91.tgz",
- "integrity": "sha512-DCd5Ad5XKBbIIOMZ73L+c+e9azM6NtZzOtdKQAzykzRG/KxSCMraMAsMMQrJrIUMH3oTtHY7QuQimAiElVVVpA==",
+ "version": "0.2.98",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-agent-sdk/-/claude-agent-sdk-0.2.98.tgz",
+ "integrity": "sha512-pWUx+xY21rKy5wvX0eBZja7p8J5ykOYaHsykvdj9nkTbAVXmP1WusI1mP6jbBByJ8uBJeBc4beAPSZIFcdIpTA==",
"license": "SEE LICENSE IN README.md",
"dependencies": {
"@anthropic-ai/sdk": "^0.80.0",
@@ -2137,9 +2137,9 @@
}
},
"node_modules/@hono/node-server": {
- "version": "1.19.12",
- "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.12.tgz",
- "integrity": "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw==",
+ "version": "1.19.13",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz",
+ "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -4915,14 +4915,14 @@
"license": "MIT"
},
"node_modules/axios": {
- "version": "1.13.5",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
- "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz",
+ "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
- "proxy-from-env": "^1.1.0"
+ "proxy-from-env": "^2.1.0"
}
},
"node_modules/b4a": {
@@ -7558,9 +7558,9 @@
}
},
"node_modules/hono": {
- "version": "4.12.9",
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz",
- "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==",
+ "version": "4.12.12",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz",
+ "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -9513,8 +9513,13 @@
}
},
"node_modules/proxy-from-env": {
- "version": "1.1.0",
- "license": "MIT"
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
},
"node_modules/pump": {
"version": "3.0.3",
@@ -10787,9 +10792,9 @@
}
},
"node_modules/vite": {
- "version": "7.3.1",
- "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
- "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
+ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/tests/unit/cli/credential-scoping.test.ts b/tests/unit/cli/credential-scoping.test.ts
index f1626e6f..52f53251 100644
--- a/tests/unit/cli/credential-scoping.test.ts
+++ b/tests/unit/cli/credential-scoping.test.ts
@@ -68,9 +68,10 @@ describe('CredentialScopedCommand', () => {
beforeEach(() => {
process.env = { ...originalEnv };
- process.env.GITHUB_TOKEN = undefined;
- process.env.TRELLO_API_KEY = undefined;
- process.env.TRELLO_TOKEN = undefined;
+ delete process.env.GITHUB_TOKEN;
+ delete process.env.GITHUB_TOKEN_IMPLEMENTER;
+ delete process.env.TRELLO_API_KEY;
+ delete process.env.TRELLO_TOKEN;
});
afterEach(() => {
diff --git a/tests/unit/cli/dashboard/webhooks/webhooks.test.ts b/tests/unit/cli/dashboard/webhooks/webhooks.test.ts
index 4879cf7f..5e12f391 100644
--- a/tests/unit/cli/dashboard/webhooks/webhooks.test.ts
+++ b/tests/unit/cli/dashboard/webhooks/webhooks.test.ts
@@ -163,6 +163,7 @@ describe('WebhooksCreate (webhooks create)', () => {
callbackBaseUrl: baseConfig.serverUrl,
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: undefined,
});
});
@@ -182,6 +183,7 @@ describe('WebhooksCreate (webhooks create)', () => {
callbackBaseUrl: 'https://cascade.example.com',
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: undefined,
});
});
@@ -201,6 +203,7 @@ describe('WebhooksCreate (webhooks create)', () => {
callbackBaseUrl: baseConfig.serverUrl,
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: { github: 'ghp_testtoken123' },
});
});
@@ -242,6 +245,7 @@ describe('WebhooksDelete (webhooks delete)', () => {
callbackBaseUrl: baseConfig.serverUrl,
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: undefined,
});
});
@@ -261,6 +265,7 @@ describe('WebhooksDelete (webhooks delete)', () => {
callbackBaseUrl: 'https://cascade.example.com',
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: undefined,
});
});
@@ -280,6 +285,7 @@ describe('WebhooksDelete (webhooks delete)', () => {
callbackBaseUrl: baseConfig.serverUrl,
trelloOnly: false,
githubOnly: false,
+ gitlabOnly: false,
oneTimeTokens: { github: 'ghp_testtoken123' },
});
});
diff --git a/web/src/components/projects/project-work-table.tsx b/web/src/components/projects/project-work-table.tsx
index 8b224367..08a250ab 100644
--- a/web/src/components/projects/project-work-table.tsx
+++ b/web/src/components/projects/project-work-table.tsx
@@ -1,5 +1,6 @@
import { useNavigate } from '@tanstack/react-router';
import { ClipboardList, ExternalLink, GitPullRequest } from 'lucide-react';
+import { useTheme } from 'next-themes';
import { agentTypeLabel, getAgentColor } from '@/lib/chart-colors.js';
import { formatCostSummary } from '@/lib/utils.js';
import { WorkItemDurationBar } from './work-item-duration-bar.js';
@@ -234,6 +235,8 @@ export function ProjectWorkTable({
onPageChange,
projectAvgDurationMs,
}: ProjectWorkTableProps) {
+ const { resolvedTheme } = useTheme();
+ const isDark = resolvedTheme === 'dark';
const total = items.length;
const totalPages = Math.ceil(total / limit);
const currentPage = Math.floor(offset / limit) + 1;
@@ -257,7 +260,7 @@ export function ProjectWorkTable({
width: 10,
height: 10,
borderRadius: 2,
- background: getAgentColor(at),
+ background: getAgentColor(at, isDark),
flexShrink: 0,
}}
/>
diff --git a/web/src/components/projects/work-item-duration-bar.tsx b/web/src/components/projects/work-item-duration-bar.tsx
index a26f4159..de5431fb 100644
--- a/web/src/components/projects/work-item-duration-bar.tsx
+++ b/web/src/components/projects/work-item-duration-bar.tsx
@@ -1,3 +1,4 @@
+import { useTheme } from 'next-themes';
import { agentTypeLabel, getAgentColor } from '@/lib/chart-colors.js';
import { formatDuration } from '@/lib/utils.js';
@@ -22,7 +23,7 @@ export interface DurationSegment {
*
* Exported for testability.
*/
-export function buildDurationSegments(runs: RunSegmentInput[]): DurationSegment[] {
+export function buildDurationSegments(runs: RunSegmentInput[], dark = false): DurationSegment[] {
const runsWithDuration = runs.filter((r) => r.durationMs > 0);
if (runsWithDuration.length === 0) return [];
@@ -36,7 +37,7 @@ export function buildDurationSegments(runs: RunSegmentInput[]): DurationSegment[
agentType: run.agentType,
durationMs: run.durationMs,
status: run.status,
- color: getAgentColor(run.agentType),
+ color: getAgentColor(run.agentType, dark),
pct: totalMs > 0 ? (run.durationMs / totalMs) * 100 : 0,
label: `${agentTypeLabel(run.agentType)} #${count}`,
};
@@ -53,7 +54,9 @@ interface WorkItemDurationBarProps {
* Highlights in red when total > 2× project average (outlier).
*/
export function WorkItemDurationBar({ runs, projectAvgDurationMs }: WorkItemDurationBarProps) {
- const segments = buildDurationSegments(runs);
+ const { resolvedTheme } = useTheme();
+ const isDark = resolvedTheme === 'dark';
+ const segments = buildDurationSegments(runs, isDark);
if (segments.length === 0) {
return —;
diff --git a/web/src/components/runs/project-work-duration-chart.tsx b/web/src/components/runs/project-work-duration-chart.tsx
index ee2577ca..6441bdd0 100644
--- a/web/src/components/runs/project-work-duration-chart.tsx
+++ b/web/src/components/runs/project-work-duration-chart.tsx
@@ -1,3 +1,4 @@
+import { useTheme } from 'next-themes';
import {
Bar,
BarChart,
@@ -34,7 +35,10 @@ interface ChartEntry {
color: string;
}
-export function buildDurationChartData(byAgentType: AgentTypeBreakdown[]): ChartEntry[] {
+export function buildDurationChartData(
+ byAgentType: AgentTypeBreakdown[],
+ dark = false,
+): ChartEntry[] {
return byAgentType
.filter((breakdown) => breakdown.totalDurationMs > 0)
.map((breakdown) => ({
@@ -43,13 +47,15 @@ export function buildDurationChartData(byAgentType: AgentTypeBreakdown[]): Chart
totalDurationMs: breakdown.totalDurationMs,
runCount: breakdown.runCount,
avgDurationMs: breakdown.avgDurationMs ?? 0,
- color: getAgentColor(breakdown.agentType),
+ color: getAgentColor(breakdown.agentType, dark),
}))
.sort((a, b) => b.totalDurationMs - a.totalDurationMs);
}
export function ProjectWorkDurationChart({ byAgentType }: ProjectWorkDurationChartProps) {
- const data: ChartEntry[] = buildDurationChartData(byAgentType);
+ const { resolvedTheme } = useTheme();
+ const isDark = resolvedTheme === 'dark';
+ const data: ChartEntry[] = buildDurationChartData(byAgentType, isDark);
if (data.length === 0) {
return (
diff --git a/web/src/components/runs/work-item-cost-chart.tsx b/web/src/components/runs/work-item-cost-chart.tsx
index 630efe36..7a431025 100644
--- a/web/src/components/runs/work-item-cost-chart.tsx
+++ b/web/src/components/runs/work-item-cost-chart.tsx
@@ -1,3 +1,4 @@
+import { useTheme } from 'next-themes';
import { Cell, Label, Legend, Pie, PieChart, ResponsiveContainer, Tooltip } from 'recharts';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.js';
import { agentTypeLabel, getAgentColor } from '@/lib/chart-colors.js';
@@ -28,7 +29,7 @@ interface CostEntry {
color: string;
}
-function buildDataFromRuns(runs: WorkItemRun[]): CostEntry[] {
+function buildDataFromRuns(runs: WorkItemRun[], dark: boolean): CostEntry[] {
const costByAgent: Record = {};
for (const run of runs) {
if (run.costUsd != null) {
@@ -42,11 +43,11 @@ function buildDataFromRuns(runs: WorkItemRun[]): CostEntry[] {
name: agentTypeLabel(agentType),
agentType,
value,
- color: getAgentColor(agentType),
+ color: getAgentColor(agentType, dark),
}));
}
-function buildDataFromBreakdown(byAgentType: AgentTypeBreakdown[]): CostEntry[] {
+function buildDataFromBreakdown(byAgentType: AgentTypeBreakdown[], dark: boolean): CostEntry[] {
return byAgentType
.map((breakdown) => {
const cost = Number.parseFloat(breakdown.totalCostUsd);
@@ -54,16 +55,18 @@ function buildDataFromBreakdown(byAgentType: AgentTypeBreakdown[]): CostEntry[]
name: agentTypeLabel(breakdown.agentType),
agentType: breakdown.agentType,
value: Number.isNaN(cost) ? 0 : cost,
- color: getAgentColor(breakdown.agentType),
+ color: getAgentColor(breakdown.agentType, dark),
};
})
.filter((entry) => entry.value > 0);
}
export function WorkItemCostChart({ runs, byAgentType }: WorkItemCostChartProps) {
+ const { resolvedTheme } = useTheme();
+ const isDark = resolvedTheme === 'dark';
const data: CostEntry[] = byAgentType
- ? buildDataFromBreakdown(byAgentType)
- : buildDataFromRuns(runs ?? []);
+ ? buildDataFromBreakdown(byAgentType, isDark)
+ : buildDataFromRuns(runs ?? [], isDark);
const totalCost = data.reduce((sum, d) => sum + d.value, 0);
diff --git a/web/src/components/runs/work-item-duration-chart.tsx b/web/src/components/runs/work-item-duration-chart.tsx
index 302bc33a..9339730c 100644
--- a/web/src/components/runs/work-item-duration-chart.tsx
+++ b/web/src/components/runs/work-item-duration-chart.tsx
@@ -1,3 +1,4 @@
+import { useTheme } from 'next-themes';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.js';
import { agentTypeLabel, getAgentColor } from '@/lib/chart-colors.js';
import { formatDuration } from '@/lib/utils.js';
@@ -29,6 +30,8 @@ interface SegmentEntry {
}
export function WorkItemDurationChart({ runs }: WorkItemDurationChartProps) {
+ const { resolvedTheme } = useTheme();
+ const isDark = resolvedTheme === 'dark';
const runsWithDuration = runs.filter((r) => r.durationMs != null && r.durationMs > 0);
if (runsWithDuration.length === 0) {
@@ -62,7 +65,7 @@ export function WorkItemDurationChart({ runs }: WorkItemDurationChartProps) {
status: run.status,
model: run.model,
costUsd: run.costUsd,
- color: getAgentColor(run.agentType),
+ color: getAgentColor(run.agentType, isDark),
pct: totalMs > 0 ? (durationMs / totalMs) * 100 : 0,
};
});
@@ -72,7 +75,7 @@ export function WorkItemDurationChart({ runs }: WorkItemDurationChartProps) {
const legendItems = uniqueAgentTypes.map((at) => ({
agentType: at,
label: agentTypeLabel(at),
- color: getAgentColor(at),
+ color: getAgentColor(at, isDark),
}));
return (
diff --git a/web/src/components/settings/agent-definition-shared.tsx b/web/src/components/settings/agent-definition-shared.tsx
index 2bec5623..78cf2b47 100644
--- a/web/src/components/settings/agent-definition-shared.tsx
+++ b/web/src/components/settings/agent-definition-shared.tsx
@@ -135,7 +135,7 @@ export function Toggle({
}`}
>
diff --git a/web/src/index.css b/web/src/index.css
index b0968a3c..4887c98b 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -4,7 +4,7 @@
@custom-variant dark (&:is(.dark *));
-@theme inline {
+@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-card: oklch(1 0 0);
diff --git a/web/src/lib/chart-colors.ts b/web/src/lib/chart-colors.ts
index 99165da3..7e2436ab 100644
--- a/web/src/lib/chart-colors.ts
+++ b/web/src/lib/chart-colors.ts
@@ -15,7 +15,7 @@
// chart-4: oklch(0.828 0.189 84.429) ≈ #d4c02a (yellow)
// chart-5: oklch(0.769 0.188 70.08) ≈ #d99c27 (amber)
-// Static palette — visible in both themes, though not theme-adaptive
+// Light-mode palette
const CHART_PALETTE = [
'#e8642a', // chart-1: orange → planning
'#3aada0', // chart-2: teal → implementation
@@ -27,6 +27,24 @@ const CHART_PALETTE = [
'#2ecc71', // green → other agents
];
+// Dark-mode palette — brighter/lighter variants for visibility on dark backgrounds.
+// Hex approximations of the dark-mode oklch chart colors from index.css:
+// chart-1: oklch(0.488 0.243 264.376) ≈ #4d6ef5 (blue-violet)
+// chart-2: oklch(0.696 0.17 162.48) ≈ #38c98a (green)
+// chart-3: oklch(0.769 0.188 70.08) ≈ #e8a838 (amber)
+// chart-4: oklch(0.627 0.265 303.9) ≈ #c46cf0 (violet)
+// chart-5: oklch(0.645 0.246 16.439) ≈ #f0614d (red-orange)
+const CHART_PALETTE_DARK = [
+ '#f0844d', // orange (brighter) → planning
+ '#4dd6c8', // teal (brighter) → implementation
+ '#6fa8d0', // steel blue (brighter) → review
+ '#f0d44d', // yellow (brighter) → splitting
+ '#f0b84d', // amber (brighter) → debug
+ '#c084f5', // purple (brighter) → respond-to-review
+ '#f57070', // red (brighter) → respond-to-ci
+ '#4ade80', // green (brighter) → other agents
+];
+
const KNOWN_AGENT_TYPES: Record = {
planning: 0,
implementation: 1,
@@ -42,18 +60,22 @@ const KNOWN_AGENT_TYPES: Record = {
/**
* Returns a color string for the given agent type.
* Falls back to a consistent color based on the string hash for unknown types.
+ *
+ * @param agentType - The agent type identifier
+ * @param dark - When true, returns a brighter color suitable for dark backgrounds
*/
-export function getAgentColor(agentType: string): string {
+export function getAgentColor(agentType: string, dark = false): string {
+ const palette = dark ? CHART_PALETTE_DARK : CHART_PALETTE;
const idx = KNOWN_AGENT_TYPES[agentType];
if (idx !== undefined) {
- return CHART_PALETTE[idx];
+ return palette[idx];
}
// Hash-based fallback for unknown agent types
let hash = 0;
for (let i = 0; i < agentType.length; i++) {
- hash = (hash * 31 + agentType.charCodeAt(i)) % CHART_PALETTE.length;
+ hash = (hash * 31 + agentType.charCodeAt(i)) % palette.length;
}
- return CHART_PALETTE[Math.abs(hash) % CHART_PALETTE.length];
+ return palette[Math.abs(hash) % palette.length];
}
/**