Skip to content

Commit 00b267a

Browse files
committed
🤖 feat: subagent tasks and reliable report delivery
Change-Id: I98401f98f52a9ba82adc854ef796fa7da0494553 Signed-off-by: Thomas Kosiewski <tk@coder.com>
1 parent beaf779 commit 00b267a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+6255
-70
lines changed

.storybook/mocks/orpc.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ import type {
1313
} from "@/common/orpc/types";
1414
import type { ChatStats } from "@/common/types/chatStats";
1515
import { DEFAULT_RUNTIME_CONFIG } from "@/common/constants/workspace";
16+
import {
17+
DEFAULT_TASK_SETTINGS,
18+
normalizeSubagentAiDefaults,
19+
normalizeTaskSettings,
20+
type SubagentAiDefaults,
21+
type TaskSettings,
22+
} from "@/common/types/tasks";
1623
import { createAsyncMessageQueue } from "@/common/utils/asyncMessageQueue";
1724

1825
/** Session usage data structure matching SessionUsageFileSchema */
@@ -46,6 +53,10 @@ export interface MockSessionUsage {
4653
export interface MockORPCClientOptions {
4754
projects?: Map<string, ProjectConfig>;
4855
workspaces?: FrontendWorkspaceMetadata[];
56+
/** Initial task settings for config.getConfig (e.g., Settings → Tasks section) */
57+
taskSettings?: Partial<TaskSettings>;
58+
/** Initial per-subagent AI defaults for config.getConfig (e.g., Settings → Tasks section) */
59+
subagentAiDefaults?: SubagentAiDefaults;
4960
/** Per-workspace chat callback. Return messages to emit, or use the callback for streaming. */
5061
onChat?: (workspaceId: string, emit: (msg: WorkspaceChatMessage) => void) => (() => void) | void;
5162
/** Mock for executeBash per workspace */
@@ -123,6 +134,8 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
123134
mcpServers = new Map(),
124135
mcpOverrides = new Map(),
125136
mcpTestResults = new Map(),
137+
taskSettings: initialTaskSettings,
138+
subagentAiDefaults: initialSubagentAiDefaults,
126139
} = options;
127140

128141
// Feature flags
@@ -140,6 +153,8 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
140153
};
141154

142155
const workspaceMap = new Map(workspaces.map((w) => [w.id, w]));
156+
let taskSettings = normalizeTaskSettings(initialTaskSettings ?? DEFAULT_TASK_SETTINGS);
157+
let subagentAiDefaults = normalizeSubagentAiDefaults(initialSubagentAiDefaults ?? {});
143158

144159
const mockStats: ChatStats = {
145160
consumers: [],
@@ -172,6 +187,16 @@ export function createMockORPCClient(options: MockORPCClientOptions = {}): APICl
172187
getSshHost: async () => null,
173188
setSshHost: async () => undefined,
174189
},
190+
config: {
191+
getConfig: async () => ({ taskSettings, subagentAiDefaults }),
192+
saveConfig: async (input: { taskSettings: unknown; subagentAiDefaults?: unknown }) => {
193+
taskSettings = normalizeTaskSettings(input.taskSettings);
194+
if (input.subagentAiDefaults !== undefined) {
195+
subagentAiDefaults = normalizeSubagentAiDefaults(input.subagentAiDefaults);
196+
}
197+
return undefined;
198+
},
199+
},
175200
providers: {
176201
list: async () => providersList,
177202
getConfig: async () => providersConfig,

src/browser/components/ModelSelector.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ interface ModelSelectorProps {
2020
onChange: (value: string) => void;
2121
models: string[];
2222
hiddenModels?: string[];
23+
emptyLabel?: string;
24+
inputPlaceholder?: string;
2325
onComplete?: () => void;
2426
defaultModel?: string | null;
2527
onSetDefaultModel?: (model: string) => void;
@@ -39,6 +41,8 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
3941
onChange,
4042
models,
4143
hiddenModels = [],
44+
emptyLabel,
45+
inputPlaceholder,
4246
onComplete,
4347
defaultModel,
4448
onSetDefaultModel,
@@ -229,6 +233,19 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
229233
}, [highlightedIndex]);
230234

231235
if (!isEditing) {
236+
if (value.trim().length === 0) {
237+
return (
238+
<div ref={containerRef} className="relative flex items-center gap-1">
239+
<div
240+
className="text-muted-light hover:bg-hover flex cursor-pointer items-center gap-1 rounded-sm px-1 py-0.5 text-[11px] transition-colors duration-200"
241+
onClick={handleClick}
242+
>
243+
<span>{emptyLabel ?? ""}</span>
244+
</div>
245+
</div>
246+
);
247+
}
248+
232249
const gatewayActive = gateway.isModelRoutingThroughGateway(value);
233250

234251
// Parse provider and model name from value (format: "provider:model-name")
@@ -276,7 +293,7 @@ export const ModelSelector = forwardRef<ModelSelectorRef, ModelSelectorProps>(
276293
value={inputValue}
277294
onChange={handleInputChange}
278295
onKeyDown={handleKeyDown}
279-
placeholder="provider:model-name"
296+
placeholder={inputPlaceholder ?? "provider:model-name"}
280297
className="text-light bg-dark border-border-light font-monospace focus:border-exec-mode w-48 rounded-sm border px-1 py-0.5 text-[10px] leading-[11px] outline-none"
281298
/>
282299
{error && (

src/browser/components/ProjectSidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
partitionWorkspacesByAge,
1818
formatDaysThreshold,
1919
AGE_THRESHOLDS_DAYS,
20+
computeWorkspaceDepthMap,
2021
} from "@/browser/utils/ui/workspaceFiltering";
2122
import { Tooltip, TooltipTrigger, TooltipContent } from "./ui/tooltip";
2223
import SecretsModal from "./SecretsModal";
@@ -608,6 +609,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
608609
{(() => {
609610
const allWorkspaces =
610611
sortedWorkspacesByProject.get(projectPath) ?? [];
612+
const depthByWorkspaceId = computeWorkspaceDepthMap(allWorkspaces);
611613
const { recent, buckets } = partitionWorkspacesByAge(
612614
allWorkspaces,
613615
workspaceRecency
@@ -625,6 +627,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
625627
onSelectWorkspace={handleSelectWorkspace}
626628
onRemoveWorkspace={handleRemoveWorkspace}
627629
onToggleUnread={_onToggleUnread}
630+
depth={depthByWorkspaceId[metadata.id] ?? 0}
628631
/>
629632
);
630633

src/browser/components/Settings/SettingsModal.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from "react";
2-
import { Settings, Key, Cpu, X, Briefcase, FlaskConical } from "lucide-react";
2+
import { Settings, Key, Cpu, X, Briefcase, FlaskConical, Bot } from "lucide-react";
33
import { useSettings } from "@/browser/contexts/SettingsContext";
44
import { Dialog, DialogContent, DialogTitle, VisuallyHidden } from "@/browser/components/ui/dialog";
55
import { GeneralSection } from "./sections/GeneralSection";
6+
import { TasksSection } from "./sections/TasksSection";
67
import { ProvidersSection } from "./sections/ProvidersSection";
78
import { ModelsSection } from "./sections/ModelsSection";
89
import { Button } from "@/browser/components/ui/button";
@@ -17,6 +18,12 @@ const SECTIONS: SettingsSection[] = [
1718
icon: <Settings className="h-4 w-4" />,
1819
component: GeneralSection,
1920
},
21+
{
22+
id: "tasks",
23+
label: "Agents",
24+
icon: <Bot className="h-4 w-4" />,
25+
component: TasksSection,
26+
},
2027
{
2128
id: "providers",
2229
label: "Providers",

0 commit comments

Comments
 (0)