Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion apps/code/src/main/services/agent/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const sessionConfigSelectGroupSchema = z
})
.passthrough();

export const sessionConfigOptionSchema = z
const sessionConfigSelectSchema = z
.object({
id: z.string(),
name: z.string(),
Expand All @@ -97,6 +97,23 @@ export const sessionConfigOptionSchema = z
})
.passthrough();

const sessionConfigBooleanSchema = z
.object({
id: z.string(),
name: z.string(),
type: z.literal("boolean"),
currentValue: z.boolean(),
category: z.string().nullish(),
description: z.string().nullish(),
_meta: z.record(z.string(), z.unknown()).nullish(),
})
.passthrough();

export const sessionConfigOptionSchema = z.union([
sessionConfigSelectSchema,
sessionConfigBooleanSchema,
]);

export type SessionConfigOption = z.infer<typeof sessionConfigOptionSchema>;

export const sessionResponseSchema = z.object({
Expand Down
5 changes: 4 additions & 1 deletion apps/code/src/main/services/agent/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,10 @@ export class AgentService extends TypedEventEmitter<AgentServiceEvents> {
const updatedModeOption = session.configOptions?.find(
(opt) => opt.category === "mode",
);
if (updatedModeOption) {
if (
updatedModeOption &&
typeof updatedModeOption.currentValue === "string"
) {
session.config.permissionMode = updatedModeOption.currentValue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ export function AttachmentMenu({
</div>
) : (
<div className="issue-picker">
<IssuePicker repoPath={repoPath!} onSelect={handleIssueSelect} />
<IssuePicker
repoPath={repoPath ?? ""}
onSelect={handleIssueSelect}
/>
</div>
)}
</Popover.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
SessionConfigOption,
SessionConfigSelectGroup,
SessionConfigSelectOption,
SessionConfigSelectOptions,
} from "@agentclientprotocol/sdk";
import {
Circle,
Expand Down Expand Up @@ -60,7 +61,7 @@ interface ModeIndicatorInputProps {
}

function flattenOptions(
options: SessionConfigOption["options"],
options: SessionConfigSelectOptions,
): SessionConfigSelectOption[] {
if (options.length === 0) return [];
if ("group" in options[0]) {
Expand All @@ -75,7 +76,7 @@ export function ModeIndicatorInput({
modeOption,
onCycleMode,
}: ModeIndicatorInputProps) {
if (!modeOption) return null;
if (!modeOption || modeOption.type !== "select") return null;

const id = modeOption.currentValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export function TutorialStep({ onComplete, onBack }: TutorialStepProps) {
const currentExecutionMode =
getCurrentModeFromConfigOptions(modeOption ? [modeOption] : undefined) ??
"plan";
const currentReasoningLevel = thoughtOption?.currentValue;
const currentReasoningLevel =
thoughtOption?.type === "select" ? thoughtOption.currentValue : undefined;

// Task creation — use whatever model the user picked
const { isCreatingTask, canSubmit, handleSubmit } = useTaskCreation({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,33 @@ export function ModelSelector({
const session = useSessionForTask(taskId);
const modelOption = useModelConfigOptionForTask(taskId);

const options = modelOption ? flattenSelectOptions(modelOption.options) : [];
const selectOption = modelOption?.type === "select" ? modelOption : undefined;
const options = selectOption
? flattenSelectOptions(selectOption.options)
: [];
const groupedOptions = useMemo(() => {
if (!modelOption || modelOption.options.length === 0) return [];
if ("group" in modelOption.options[0]) {
return modelOption.options as SessionConfigSelectGroup[];
if (!selectOption || selectOption.options.length === 0) return [];
if ("group" in selectOption.options[0]) {
return selectOption.options as SessionConfigSelectGroup[];
}
return [];
}, [modelOption]);
}, [selectOption]);

if (!modelOption || options.length === 0) return null;
if (!selectOption || options.length === 0) return null;

const handleChange = (value: string) => {
onModelChange?.(value);

if (taskId && session?.status === "connected") {
getSessionService().setSessionConfigOption(taskId, modelOption.id, value);
getSessionService().setSessionConfigOption(
taskId,
selectOption.id,
value,
);
}
};

const currentValue = modelOption.currentValue;
const currentValue = selectOption.currentValue;
const currentLabel =
options.find((opt) => opt.value === currentValue)?.name ?? currentValue;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function ReasoningLevelSelector({
const thoughtOption = useThoughtLevelConfigOptionForTask(taskId);
const adapter = useAdapterForTask(taskId);

if (!thoughtOption) {
if (!thoughtOption || thoughtOption.type !== "select") {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,19 @@ export function UnifiedModelSelector({
const session = useSessionForTask(taskId);
const modelOption = useModelConfigOptionForTask(taskId);

const options = modelOption ? flattenSelectOptions(modelOption.options) : [];
const selectOption = modelOption?.type === "select" ? modelOption : undefined;
const options = selectOption
? flattenSelectOptions(selectOption.options)
: [];
const groupedOptions = useMemo(() => {
if (!modelOption || modelOption.options.length === 0) return [];
if ("group" in modelOption.options[0]) {
return modelOption.options as SessionConfigSelectGroup[];
if (!selectOption || selectOption.options.length === 0) return [];
if ("group" in selectOption.options[0]) {
return selectOption.options as SessionConfigSelectGroup[];
}
return [];
}, [modelOption]);
}, [selectOption]);

const currentValue = modelOption?.currentValue;
const currentValue = selectOption?.currentValue;
const currentLabel =
options.find((opt) => opt.value === currentValue)?.name ?? currentValue;

Expand Down
21 changes: 12 additions & 9 deletions apps/code/src/renderer/features/sessions/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,10 +355,9 @@ export class SessionService {
this.subscribeToChannel(taskRunId);

try {
const persistedMode = getConfigOptionByCategory(
persistedConfigOptions,
"mode",
)?.currentValue;
const modeOpt = getConfigOptionByCategory(persistedConfigOptions, "mode");
const persistedMode =
modeOpt?.type === "select" ? modeOpt.currentValue : undefined;

trpcClient.workspace.verify
.query({ taskId })
Expand Down Expand Up @@ -429,7 +428,7 @@ export class SessionService {
.mutate({
sessionId: taskRunId,
configId: opt.id,
value: opt.currentValue,
value: String(opt.currentValue),
})
.catch((error) => {
log.warn(
Expand Down Expand Up @@ -1636,7 +1635,9 @@ export class SessionService {

// Optimistic update
const updatedOptions = configOptions.map((opt) =>
opt.id === configId ? { ...opt, currentValue: value } : opt,
opt.id === configId
? ({ ...opt, currentValue: value } as SessionConfigOption)
: opt,
);
sessionStoreSetters.updateSession(session.taskRunId, {
configOptions: updatedOptions,
Expand All @@ -1652,15 +1653,17 @@ export class SessionService {
} catch (error) {
// Rollback on error
const rolledBackOptions = configOptions.map((opt) =>
opt.id === configId ? { ...opt, currentValue: previousValue } : opt,
opt.id === configId
? ({ ...opt, currentValue: previousValue } as SessionConfigOption)
: opt,
);
sessionStoreSetters.updateSession(session.taskRunId, {
configOptions: rolledBackOptions,
});
updatePersistedConfigOptionValue(
session.taskRunId,
configId,
previousValue,
String(previousValue),
);
log.error("Failed to set session config option", {
taskId,
Expand Down Expand Up @@ -1697,7 +1700,7 @@ export class SessionService {
track(ANALYTICS_EVENTS.SESSION_CONFIG_CHANGED, {
task_id: taskId,
category,
from_value: configOption.currentValue,
from_value: String(configOption.currentValue),
to_value: value,
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export const useSessionConfigStore = create<SessionConfigStore>()(
if (!existing) return state;

const updated = existing.map((opt) =>
opt.id === configId ? { ...opt, currentValue: value } : opt,
opt.id === configId
? ({ ...opt, currentValue: value } as SessionConfigOption)
: opt,
);

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ export function mergeConfigOptions(
return live.map((liveOpt) => {
const persistedOpt = persistedMap.get(liveOpt.id);
if (persistedOpt) {
// Use persisted currentValue if available
return { ...liveOpt, currentValue: persistedOpt.currentValue };
return {
...liveOpt,
currentValue: persistedOpt.currentValue,
} as SessionConfigOption;
}
return liveOpt;
});
Expand All @@ -145,7 +147,7 @@ export function cycleModeOption(
modeOption: SessionConfigOption | undefined,
allowBypassPermissions: boolean,
): string | undefined {
if (!modeOption) return undefined;
if (!modeOption || modeOption.type !== "select") return undefined;

const allOptions = flattenSelectOptions(modeOption.options);
const filteredOptions = allowBypassPermissions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,15 @@ export function TaskInput() {

// Get current values from preview session config options for task creation.
// Defaults ensure values are always passed even before the preview session loads.
const currentModel = modelOption?.currentValue;
const currentModel =
modelOption?.type === "select" ? modelOption.currentValue : undefined;
const modeFallback =
defaultInitialTaskMode === "last_used" ? lastUsedInitialTaskMode : "plan";
const currentExecutionMode =
getCurrentModeFromConfigOptions(modeOption ? [modeOption] : undefined) ??
modeFallback;
const currentReasoningLevel = thoughtOption?.currentValue;
const currentReasoningLevel =
thoughtOption?.type === "select" ? thoughtOption.currentValue : undefined;

const branchForTaskCreation =
effectiveWorkspaceMode === "worktree" || effectiveWorkspaceMode === "cloud"
Expand Down
3 changes: 0 additions & 3 deletions apps/code/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ interface ImportMetaEnv {
readonly VITE_POSTHOG_UI_HOST?: string;
}

// This interface is used by TypeScript to type-check `import.meta.env` in Vite.
// It appears unused in this file, but it is intentionally kept for global augmentation.
// biome-ignore lint: noUnusedVariables
interface ImportMeta {
readonly env: ImportMetaEnv;
}
4 changes: 2 additions & 2 deletions packages/agent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
"vitest": "^2.1.8"
},
"dependencies": {
"@agentclientprotocol/sdk": "0.15.0",
"@anthropic-ai/claude-agent-sdk": "0.2.71",
"@agentclientprotocol/sdk": "0.16.1",
"@anthropic-ai/claude-agent-sdk": "0.2.76",
"@anthropic-ai/sdk": "^0.78.0",
"@hono/node-server": "^1.19.9",
"@opentelemetry/api-logs": "^0.208.0",
Expand Down
7 changes: 3 additions & 4 deletions packages/agent/src/adapters/claude/UPSTREAM.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
## Fork Point

- **Forked**: v0.10.9, commit `5411e0f4`, Dec 2 2025
- **Last sync**: v0.21.0, commit `c13edf3b02ddaf49f8e8b9e11b02cbf17869b57d`, March 9 2026
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.71, `@agentclientprotocol/sdk` 0.15.0
- **Last sync**: v0.22.2, commit `07db59e`, March 25 2026
- **SDK**: `@anthropic-ai/claude-agent-sdk` 0.2.76, `@agentclientprotocol/sdk` 0.16.1

## File Mapping

Expand Down Expand Up @@ -49,13 +49,12 @@ Fork of `@anthropic-ai/claude-agent-acp`. Upstream repo: https://github.com/anth
| Model resolution | `initializationResult.models` from SDK | `fetchGatewayModels()` from gateway API | Different model backend |
| permissionMode | Hardcoded `"default"` | Reads from `meta.permissionMode` | More flexible mode selection |
| Session storage | `this.sessions[sessionId]` (multi) | `this.session` (single) | Architectural choice |
| ExitPlanMode denial | `interrupt: true` | `interrupt: false` | Better UX — lets Claude refine plan |
| bypassPermissions | `updatedPermissions` with `destination: "session"` | No `updatedPermissions` | Different permission persistence |
| Auth methods | Always returns `claude-login` auth method | Returns empty `authMethods` | Auth handled externally |

## Next Sync

1. Check upstream changelog since v0.21.0
1. Check upstream changelog since v0.22.2
2. Diff upstream source against PostHog Code using the file mapping above
3. Port in phases: bug fixes first, then features
4. After each phase: `pnpm --filter agent typecheck && pnpm --filter agent build && pnpm lint`
Expand Down
Loading
Loading