Renumbers the v2 migrations 044-052 to 045-053 behind main's 044_ClearAutomaticProjectModelDefaults, and adopts main's sticky new-thread selection (#9164), local-only worktree bases in the v2 launch path (#8751), the PR summary read for settlement (#9176), Claude per-cwd skills (#9210), the provider editor redesign with the branch's dedicated environment fields re-grafted (#8508), and the client half of continue-threads-across-restart (#9167). The server-side continuation markers stay unported: they live in the v1 session directory, and v2 recovery terminalizes running runs on restart, so the capability is withheld until the v2 runtime carries them.
Unified · Split
| @@ -153,6 +153,11 @@ export const COMPOSER_LAYOUT_TRANSITION = · 152 unmodified lines | |||
| 153 | ? undefined | 153 | ? undefined |
| 154 | : LinearTransition.duration(COMPOSER_TRANSITION_DURATION_MS).reduceMotion(ReduceMotion.System); | 154 | : LinearTransition.duration(COMPOSER_TRANSITION_DURATION_MS).reduceMotion(ReduceMotion.System); |
| 155 | 155 | ||
| 156 | const COMPOSER_ATTACHMENT_ENTERING = | ||
| 157 | Platform.OS === "android" | ||
| 158 | ? FadeIn.duration(160) | ||
| 159 | : FadeIn.delay(COMPOSER_TRANSITION_DURATION_MS).duration(160).reduceMotion(ReduceMotion.System); | ||
| 160 | |||
| 156 | const AnimatedGlassSurface = Animated.createAnimatedComponent(GlassSurface); | 161 | const AnimatedGlassSurface = Animated.createAnimatedComponent(GlassSurface); |
| 157 | 162 | ||
| 158 | export function ComposerSurface(props: { | 163 | export function ComposerSurface(props: { |
| @@ -622,10 +627,10 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer · 463 unmodified lines | |||
| 622 | onPickFiles={props.onPickDraftFiles} | 627 | onPickFiles={props.onPickDraftFiles} |
| 623 | /> | 628 | /> |
| 624 | ) : null} | 629 | ) : null} |
| 625 | {isExpanded ? ( | 630 | {isExpanded && props.draftAttachments.length > 0 ? ( |
| 626 | <Animated.View | 631 | <Animated.View |
| 627 | className={props.draftAttachments.length > 0 ? "px-[14px] pb-2.5" : undefined} | 632 | className="px-[14px] pb-2.5" |
| 628 | entering={FadeIn.duration(160)} | 633 | entering={COMPOSER_ATTACHMENT_ENTERING} |
| 629 | exiting={FadeOut.duration(120)} | 634 | exiting={FadeOut.duration(120)} |
| 630 | > | 635 | > |
| 631 | <ComposerAttachmentStrip | 636 | <ComposerAttachmentStrip |
| @@ -31,7 +31,6 @@ import * as ProjectFaviconResolver from "../project/ProjectFaviconResolver.ts"; · 30 unmodified lines | |||
| 31 | import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts"; | 31 | import * as RepositoryIdentityResolver from "../project/RepositoryIdentityResolver.ts"; |
| 32 | import * as ProjectService from "../project/ProjectService.ts"; | 32 | import * as ProjectService from "../project/ProjectService.ts"; |
| 33 | import * as T3ProjectFileLoader from "../project/T3ProjectFileLoader.ts"; | 33 | import * as T3ProjectFileLoader from "../project/T3ProjectFileLoader.ts"; |
| 34 | import * as ServerRuntimeStartup from "../serverRuntimeStartup.ts"; | ||
| 35 | import { | 34 | import { |
| 36 | clearPersistedServerRuntimeState, | 35 | clearPersistedServerRuntimeState, |
| 37 | readPersistedServerRuntimeState, | 36 | readPersistedServerRuntimeState, |
| @@ -513,7 +512,6 @@ const projectAddCommand = Command.make("add", { · 475 unmodified lines | |||
| 513 | projectId, | 512 | projectId, |
| 514 | title, | 513 | title, |
| 515 | workspaceRoot, | 514 | workspaceRoot, |
| 516 | defaultModelSelection: ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), | ||
| 517 | }); | 515 | }); |
| 518 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; | 516 | return `Added project ${projectId} (${title}) at ${workspaceRoot}.`; |
| 519 | }), | 517 | }), |
| @@ -245,7 +245,9 @@ it.layer(NodeServices.layer)("ServerEnvironmentLive", (it) => { · 244 unmodified lines | |||
| 245 | expect(withFd.capabilities.serverSelfUpdate).toBe("desktop-managed"); | 245 | expect(withFd.capabilities.serverSelfUpdate).toBe("desktop-managed"); |
| 246 | expect(withFd.capabilities.desktopAppUpdate).toBe(true); | 246 | expect(withFd.capabilities.desktopAppUpdate).toBe(true); |
| 247 | expect(withFd.capabilities.serverSelfUpdateProgress).toBe(true); | 247 | expect(withFd.capabilities.serverSelfUpdateProgress).toBe(true); |
| 248 | expect(withFd.capabilities.serverUpdateThreadContinuation).toBe(true); | 248 | // v2 recovery terminalizes running runs on restart, so continuation |
| 249 | // stays unadvertised until the v2 runtime carries the markers. | ||
| 250 | expect(withFd.capabilities.serverUpdateThreadContinuation).toBeUndefined(); | ||
| 249 | 251 | ||
| 250 | const withoutFd = yield* describeWith({ mode: "desktop" }); | 252 | const withoutFd = yield* describeWith({ mode: "desktop" }); |
| 251 | expect(withoutFd.capabilities.serverSelfUpdate).toBe("desktop-managed"); | 253 | expect(withoutFd.capabilities.serverSelfUpdate).toBe("desktop-managed"); |
| @@ -226,11 +226,12 @@ export const make = Effect.gen(function* () { · 225 unmodified lines | |||
| 226 | threadVisitedTracking: true, | 226 | threadVisitedTracking: true, |
| 227 | threadPullRequestLinking: true, | 227 | threadPullRequestLinking: true, |
| 228 | ...(serverSelfUpdate === null ? {} : { serverSelfUpdate }), | 228 | ...(serverSelfUpdate === null ? {} : { serverSelfUpdate }), |
| 229 | // Running-thread continuation across a self-update (#9167) marks v1 | ||
| 230 | // provider session bindings; the v2 recovery path terminalizes running | ||
| 231 | // runs on restart instead, so the capability stays unadvertised until | ||
| 232 | // ProviderRuntimeRecoveryService grows an equivalent. | ||
| 229 | ...(serverSelfUpdate === "boot-service" || desktopAppUpdate | 233 | ...(serverSelfUpdate === "boot-service" || desktopAppUpdate |
| 230 | ? { | 234 | ? { serverSelfUpdateProgress: true } |
| 231 | serverSelfUpdateProgress: true, | ||
| 232 | serverUpdateThreadContinuation: true, | ||
| 233 | } | ||
| 234 | : {}), | 235 | : {}), |
| 235 | ...(desktopAppUpdate ? { desktopAppUpdate: true } : {}), | 236 | ...(desktopAppUpdate ? { desktopAppUpdate: true } : {}), |
| 236 | }, | 237 | }, |
| @@ -240,20 +240,36 @@ export const make = Effect.gen(function* () { · 239 unmodified lines | |||
| 240 | .pipe(Effect.mapError(mapError(input, "update-thread", threadId))); | 240 | .pipe(Effect.mapError(mapError(input, "update-thread", threadId))); |
| 241 | } | 241 | } |
| 242 | let startRef = input.workspaceStrategy.baseRef; | 242 | let startRef = input.workspaceStrategy.baseRef; |
| 243 | if (input.workspaceStrategy.startFromOrigin === true) { | 243 | // "Start from origin" is a stored default; repos without the requested |
| 244 | // remote branch fall back to the local base branch. | ||
| 245 | const startFromOrigin = | ||
| 246 | input.workspaceStrategy.startFromOrigin === true && | ||
| 247 | (yield* git | ||
| 248 | .remoteExists({ cwd: project.workspaceRoot, remoteName: "origin" }) | ||
| 249 | .pipe(Effect.mapError(mapError(input, "provision-worktree", threadId)))); | ||
| 250 | if (startFromOrigin) { | ||
| 244 | yield* git | 251 | yield* git |
| 245 | .fetchRemote({ cwd: project.workspaceRoot, remoteName: "origin" }) | 252 | .fetchRemote({ cwd: project.workspaceRoot, remoteName: "origin" }) |
| 246 | .pipe(Effect.mapError(mapError(input, "provision-worktree", threadId))); | 253 | .pipe(Effect.mapError(mapError(input, "provision-worktree", threadId))); |
| 247 | startRef = yield* git | 254 | const remoteBaseExists = yield* git |
| 248 | .resolveRemoteTrackingCommit({ | 255 | .remoteBranchExists({ |
| 249 | cwd: project.workspaceRoot, | 256 | cwd: project.workspaceRoot, |
| 250 | refName: input.workspaceStrategy.baseRef, | 257 | refName: input.workspaceStrategy.baseRef, |
| 251 | fallbackRemoteName: "origin", | 258 | remoteName: "origin", |
| 252 | }) | 259 | }) |
| 253 | .pipe( | 260 | .pipe(Effect.mapError(mapError(input, "provision-worktree", threadId))); |
| 254 | Effect.map((resolved) => resolved.commitSha), | 261 | if (remoteBaseExists) { |
| 255 | Effect.mapError(mapError(input, "provision-worktree", threadId)), | 262 | startRef = yield* git |
| 256 | ); | 263 | .resolveRemoteTrackingCommit({ |
| 264 | cwd: project.workspaceRoot, | ||
| 265 | refName: input.workspaceStrategy.baseRef, | ||
| 266 | fallbackRemoteName: "origin", | ||
| 267 | }) | ||
| 268 | .pipe( | ||
| 269 | Effect.map((resolved) => resolved.commitSha), | ||
| 270 | Effect.mapError(mapError(input, "provision-worktree", threadId)), | ||
| 271 | ); | ||
| 272 | } | ||
| 257 | } | 273 | } |
| 258 | const worktree = yield* git | 274 | const worktree = yield* git |
| 259 | .createWorktree({ | 275 | .createWorktree({ |
| @@ -192,12 +192,18 @@ export const make = Effect.gen(function* () { · 191 unmodified lines | |||
| 192 | thread: (typeof candidates)[number], | 192 | thread: (typeof candidates)[number], |
| 193 | ) { | 193 | ) { |
| 194 | if (thread.linkedPullRequest != null) { | 194 | if (thread.linkedPullRequest != null) { |
| 195 | const detail = yield* pullRequests.detail({ | 195 | const summary = yield* pullRequests.summary( |
| 196 | projectId: thread.linkedPullRequest.projectId, | 196 | { |
| 197 | repository: thread.linkedPullRequest.repository, | 197 | projectId: thread.linkedPullRequest.projectId, |
| 198 | number: thread.linkedPullRequest.number, | 198 | repository: thread.linkedPullRequest.repository, |
| 199 | }); | 199 | number: thread.linkedPullRequest.number, |
| 200 | return { state: detail.state, updatedAt: detail.updatedAt } satisfies SettlementPullRequest; | 200 | }, |
| 201 | { recoverTransientFailure: false }, | ||
| 202 | ); | ||
| 203 | return { | ||
| 204 | state: summary.state, | ||
| 205 | updatedAt: summary.updatedAt, | ||
| 206 | } satisfies SettlementPullRequest; | ||
| 201 | } | 207 | } |
| 202 | if (thread.branch === null) return null; | 208 | if (thread.branch === null) return null; |
| 203 | const workspaceRoot = workspaceRootByProjectId.get(thread.projectId); | 209 | const workspaceRoot = workspaceRootByProjectId.get(thread.projectId); |
| @@ -219,7 +219,10 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" · 218 unmodified lines | |||
| 219 | projectId: command.projectId, | 219 | projectId: command.projectId, |
| 220 | title: command.title, | 220 | title: command.title, |
| 221 | workspaceRoot: command.workspaceRoot, | 221 | workspaceRoot: command.workspaceRoot, |
| 222 | defaultModelSelection: command.defaultModelSelection ?? null, | 222 | // Project creation has no user model choice. Older clients sent an |
| 223 | // automatic seed here, but only a metadata update records an | ||
| 224 | // explicit project default. | ||
| 225 | defaultModelSelection: null, | ||
| 223 | faviconPath: null, | 226 | faviconPath: null, |
| 224 | scripts: command.scripts ?? [], | 227 | scripts: command.scripts ?? [], |
| 225 | createdAt: command.createdAt, | 228 | createdAt: command.createdAt, |
| @@ -55,15 +55,16 @@ import Migration0040 from "./Migrations/040_ProjectionProjectFaviconPath.ts"; · 54 unmodified lines | |||
| 55 | import Migration0041 from "./Migrations/041_AuthSessionClientConnection.ts"; | 55 | import Migration0041 from "./Migrations/041_AuthSessionClientConnection.ts"; |
| 56 | import Migration0042 from "./Migrations/042_ProjectionThreadLinkedPullRequest.ts"; | 56 | import Migration0042 from "./Migrations/042_ProjectionThreadLinkedPullRequest.ts"; |
| 57 | import Migration0043 from "./Migrations/043_ProjectionThreadsUnsettledAt.ts"; | 57 | import Migration0043 from "./Migrations/043_ProjectionThreadsUnsettledAt.ts"; |
| 58 | import Migration0044 from "./Migrations/044_OrchestrationV2.ts"; | 58 | import Migration0044 from "./Migrations/044_ClearAutomaticProjectModelDefaults.ts"; |
| 59 | import Migration0045 from "./Migrations/045_OrchestrationV2Subagents.ts"; | 59 | import Migration0045 from "./Migrations/045_OrchestrationV2.ts"; |
| 60 | import Migration0046 from "./Migrations/046_OrchestrationV2Foundation.ts"; | 60 | import Migration0046 from "./Migrations/046_OrchestrationV2Subagents.ts"; |
| 61 | import Migration0047 from "./Migrations/047_OrchestrationV2ProviderSessionBindings.ts"; | 61 | import Migration0047 from "./Migrations/047_OrchestrationV2Foundation.ts"; |
| 62 | import Migration0048 from "./Migrations/048_OrchestrationV2ThreadLaunchWorkflows.ts"; | 62 | import Migration0048 from "./Migrations/048_OrchestrationV2ProviderSessionBindings.ts"; |
| 63 | import Migration0049 from "./Migrations/049_ApplicationEventSource.ts"; | 63 | import Migration0049 from "./Migrations/049_OrchestrationV2ThreadLaunchWorkflows.ts"; |
| 64 | import Migration0050 from "./Migrations/050_OrchestrationV2EffectCancellation.ts"; | 64 | import Migration0050 from "./Migrations/050_ApplicationEventSource.ts"; |
| 65 | import Migration0051 from "./Migrations/051_ScheduledTasks.ts"; | 65 | import Migration0051 from "./Migrations/051_OrchestrationV2EffectCancellation.ts"; |
| 66 | import Migration0052 from "./Migrations/052_LegacyV1ImportState.ts"; | 66 | import Migration0052 from "./Migrations/052_ScheduledTasks.ts"; |
| 67 | import Migration0053 from "./Migrations/053_LegacyV1ImportState.ts"; | ||
| 67 | 68 | ||
| 68 | /** | 69 | /** |
| 69 | * Migration loader with all migrations defined inline. | 70 | * Migration loader with all migrations defined inline. |
| @@ -119,15 +120,16 @@ export const migrationEntries = [ · 49 unmodified lines | |||
| 119 | [41, "AuthSessionClientConnection", Migration0041], | 120 | [41, "AuthSessionClientConnection", Migration0041], |
| 120 | [42, "ProjectionThreadLinkedPullRequest", Migration0042], | 121 | [42, "ProjectionThreadLinkedPullRequest", Migration0042], |
| 121 | [43, "ProjectionThreadsUnsettledAt", Migration0043], | 122 | [43, "ProjectionThreadsUnsettledAt", Migration0043], |
| 122 | [44, "OrchestrationV2", Migration0044], | 123 | [44, "ClearAutomaticProjectModelDefaults", Migration0044], |
| 123 | [45, "OrchestrationV2Subagents", Migration0045], | 124 | [45, "OrchestrationV2", Migration0045], |
| 124 | [46, "OrchestrationV2Foundation", Migration0046], | 125 | [46, "OrchestrationV2Subagents", Migration0046], |
| 125 | [47, "OrchestrationV2ProviderSessionBindings", Migration0047], | 126 | [47, "OrchestrationV2Foundation", Migration0047], |
| 126 | [48, "OrchestrationV2ThreadLaunchWorkflows", Migration0048], | 127 | [48, "OrchestrationV2ProviderSessionBindings", Migration0048], |
| 127 | [49, "ApplicationEventSource", Migration0049], | 128 | [49, "OrchestrationV2ThreadLaunchWorkflows", Migration0049], |
| 128 | [50, "OrchestrationV2EffectCancellation", Migration0050], | 129 | [50, "ApplicationEventSource", Migration0050], |
| 129 | [51, "ScheduledTasks", Migration0051], | 130 | [51, "OrchestrationV2EffectCancellation", Migration0051], |
| 130 | [52, "LegacyV1ImportState", Migration0052], | 131 | [52, "ScheduledTasks", Migration0052], |
| 132 | [53, "LegacyV1ImportState", Migration0053], | ||
| 131 | ] as const; | 133 | ] as const; |
| 132 | 134 | ||
| 133 | export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const); | 135 | export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const); |
| @@ -8,12 +8,12 @@ import * as NodeSqliteClient from "../NodeSqliteClient.ts"; · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); | 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); |
| 10 | 10 | ||
| 11 | layer("038_039_OrchestrationV2", (it) => { | 11 | layer("045_046_OrchestrationV2", (it) => { |
| 12 | it.effect("keeps released and private migration ids contiguous", () => | 12 | it.effect("keeps released and private migration ids contiguous", () => |
| 13 | Effect.sync(() => { | 13 | Effect.sync(() => { |
| 14 | assert.deepStrictEqual( | 14 | assert.deepStrictEqual( |
| 15 | migrationEntries.map(([id]) => id), | 15 | migrationEntries.map(([id]) => id), |
| 16 | Array.from({ length: 52 }, (_, index) => index + 1), | 16 | Array.from({ length: 53 }, (_, index) => index + 1), |
| 17 | ); | 17 | ); |
| 18 | }), | 18 | }), |
| 19 | ); | 19 | ); |
| @@ -22,7 +22,7 @@ layer("038_039_OrchestrationV2", (it) => { · 2 unmodified lines | |||
| 22 | Effect.gen(function* () { | 22 | Effect.gen(function* () { |
| 23 | const sql = yield* SqlClient.SqlClient; | 23 | const sql = yield* SqlClient.SqlClient; |
| 24 | 24 | ||
| 25 | yield* runMigrations({ toMigrationInclusive: 45 }); | 25 | yield* runMigrations({ toMigrationInclusive: 46 }); |
| 26 | 26 | ||
| 27 | const migrations = yield* sql<{ | 27 | const migrations = yield* sql<{ |
| 28 | readonly migration_id: number; | 28 | readonly migration_id: number; |
| @@ -30,24 +30,24 @@ layer("038_039_OrchestrationV2", (it) => { · 1 unmodified line | |||
| 30 | }>` | 30 | }>` |
| 31 | SELECT migration_id, name | 31 | SELECT migration_id, name |
| 32 | FROM effect_sql_migrations | 32 | FROM effect_sql_migrations |
| 33 | WHERE migration_id IN (42, 43, 44, 45) | 33 | WHERE migration_id IN (43, 44, 45, 46) |
| 34 | ORDER BY migration_id | 34 | ORDER BY migration_id |
| 35 | `; | 35 | `; |
| 36 | assert.deepStrictEqual(migrations, [ | 36 | assert.deepStrictEqual(migrations, [ |
| 37 | { | ||
| 38 | migration_id: 42, | ||
| 39 | name: "ProjectionThreadLinkedPullRequest", | ||
| 40 | }, | ||
| 41 | { | 37 | { |
| 42 | migration_id: 43, | 38 | migration_id: 43, |
| 43 | name: "ProjectionThreadsUnsettledAt", | 39 | name: "ProjectionThreadsUnsettledAt", |
| 44 | }, | 40 | }, |
| 45 | { | 41 | { |
| 46 | migration_id: 44, | 42 | migration_id: 44, |
| 47 | name: "OrchestrationV2", | 43 | name: "ClearAutomaticProjectModelDefaults", |
| 48 | }, | 44 | }, |
| 49 | { | 45 | { |
| 50 | migration_id: 45, | 46 | migration_id: 45, |
| 47 | name: "OrchestrationV2", | ||
| 48 | }, | ||
| 49 | { | ||
| 50 | migration_id: 46, | ||
| 51 | name: "OrchestrationV2Subagents", | 51 | name: "OrchestrationV2Subagents", |
| 52 | }, | 52 | }, |
| 53 | ]); | 53 | ]); |
| @@ -64,10 +64,10 @@ layer("038_039_OrchestrationV2", (it) => { · 10 unmodified lines | |||
| 64 | }), | 64 | }), |
| 65 | ); | 65 | ); |
| 66 | 66 | ||
| 67 | it.effect("backfills provider-session thread bindings in migration 047", () => | 67 | it.effect("backfills provider-session thread bindings in migration 048", () => |
| 68 | Effect.gen(function* () { | 68 | Effect.gen(function* () { |
| 69 | const sql = yield* SqlClient.SqlClient; | 69 | const sql = yield* SqlClient.SqlClient; |
| 70 | yield* runMigrations({ toMigrationInclusive: 46 }); | 70 | yield* runMigrations({ toMigrationInclusive: 47 }); |
| 71 | yield* sql` | 71 | yield* sql` |
| 72 | INSERT INTO orchestration_v2_projection_provider_sessions ( | 72 | INSERT INTO orchestration_v2_projection_provider_sessions ( |
| 73 | provider_session_id, | 73 | provider_session_id, |
| @@ -92,7 +92,7 @@ layer("038_039_OrchestrationV2", (it) => { · 18 unmodified lines | |||
| 92 | ) | 92 | ) |
| 93 | `; | 93 | `; |
| 94 | 94 | ||
| 95 | yield* runMigrations({ toMigrationInclusive: 47 }); | 95 | yield* runMigrations({ toMigrationInclusive: 48 }); |
| 96 | 96 | ||
| 97 | const bindings = yield* sql<{ | 97 | const bindings = yield* sql<{ |
| 98 | readonly provider_session_id: string; | 98 | readonly provider_session_id: string; |
| @@ -110,10 +110,10 @@ layer("038_039_OrchestrationV2", (it) => { · 11 unmodified lines | |||
| 110 | }), | 110 | }), |
| 111 | ); | 111 | ); |
| 112 | 112 | ||
| 113 | it.effect("preserves turn items with colliding ordinals in migration 046", () => | 113 | it.effect("preserves turn items with colliding ordinals in migration 047", () => |
| 114 | Effect.gen(function* () { | 114 | Effect.gen(function* () { |
| 115 | const sql = yield* SqlClient.SqlClient; | 115 | const sql = yield* SqlClient.SqlClient; |
| 116 | yield* runMigrations({ toMigrationInclusive: 45 }); | 116 | yield* runMigrations({ toMigrationInclusive: 46 }); |
| 117 | yield* sql` | 117 | yield* sql` |
| 118 | INSERT INTO orchestration_v2_projection_runs ( | 118 | INSERT INTO orchestration_v2_projection_runs ( |
| 119 | run_id, | 119 | run_id, |
| @@ -150,7 +150,7 @@ layer("038_039_OrchestrationV2", (it) => { · 30 unmodified lines | |||
| 150 | ('turn-item:d', 'thread:two', NULL, 42, 'assistant_message', 'completed', '2026-01-01T00:00:00.000Z', '{}') | 150 | ('turn-item:d', 'thread:two', NULL, 42, 'assistant_message', 'completed', '2026-01-01T00:00:00.000Z', '{}') |
| 151 | `; | 151 | `; |
| 152 | 152 | ||
| 153 | yield* runMigrations({ toMigrationInclusive: 46 }); | 153 | yield* runMigrations({ toMigrationInclusive: 47 }); |
| 154 | 154 | ||
| 155 | const positions = yield* sql<{ | 155 | const positions = yield* sql<{ |
| 156 | readonly thread_id: string; | 156 | readonly thread_id: string; |
| @@ -182,7 +182,7 @@ it.effect("upgrades a database already at released main migration 043", () => · 25 unmodified lines | |||
| 182 | assert.ok(snoozeColumns.some((column) => column.name === "snoozed_until")); | 182 | assert.ok(snoozeColumns.some((column) => column.name === "snoozed_until")); |
| 183 | assert.ok(snoozeColumns.some((column) => column.name === "snoozed_at")); | 183 | assert.ok(snoozeColumns.some((column) => column.name === "snoozed_at")); |
| 184 | 184 | ||
| 185 | yield* runMigrations({ toMigrationInclusive: 52 }); | 185 | yield* runMigrations({ toMigrationInclusive: 53 }); |
| 186 | 186 | ||
| 187 | const migrations = yield* sql<{ | 187 | const migrations = yield* sql<{ |
| 188 | readonly migration_id: number; | 188 | readonly migration_id: number; |
| @@ -190,7 +190,7 @@ it.effect("upgrades a database already at released main migration 043", () => · 1 unmodified line | |||
| 190 | }>` | 190 | }>` |
| 191 | SELECT migration_id, name | 191 | SELECT migration_id, name |
| 192 | FROM effect_sql_migrations | 192 | FROM effect_sql_migrations |
| 193 | WHERE migration_id BETWEEN 39 AND 52 | 193 | WHERE migration_id BETWEEN 39 AND 53 |
| 194 | ORDER BY migration_id | 194 | ORDER BY migration_id |
| 195 | `; | 195 | `; |
| 196 | assert.deepStrictEqual( | 196 | assert.deepStrictEqual( |
| @@ -201,15 +201,16 @@ it.effect("upgrades a database already at released main migration 043", () => · 4 unmodified lines | |||
| 201 | [41, "AuthSessionClientConnection"], | 201 | [41, "AuthSessionClientConnection"], |
| 202 | [42, "ProjectionThreadLinkedPullRequest"], | 202 | [42, "ProjectionThreadLinkedPullRequest"], |
| 203 | [43, "ProjectionThreadsUnsettledAt"], | 203 | [43, "ProjectionThreadsUnsettledAt"], |
| 204 | [44, "OrchestrationV2"], | 204 | [44, "ClearAutomaticProjectModelDefaults"], |
| 205 | [45, "OrchestrationV2Subagents"], | 205 | [45, "OrchestrationV2"], |
| 206 | [46, "OrchestrationV2Foundation"], | 206 | [46, "OrchestrationV2Subagents"], |
| 207 | [47, "OrchestrationV2ProviderSessionBindings"], | 207 | [47, "OrchestrationV2Foundation"], |
| 208 | [48, "OrchestrationV2ThreadLaunchWorkflows"], | 208 | [48, "OrchestrationV2ProviderSessionBindings"], |
| 209 | [49, "ApplicationEventSource"], | 209 | [49, "OrchestrationV2ThreadLaunchWorkflows"], |
| 210 | [50, "OrchestrationV2EffectCancellation"], | 210 | [50, "ApplicationEventSource"], |
| 211 | [51, "ScheduledTasks"], | 211 | [51, "OrchestrationV2EffectCancellation"], |
| 212 | [52, "LegacyV1ImportState"], | 212 | [52, "ScheduledTasks"], |
| 213 | [53, "LegacyV1ImportState"], | ||
| 213 | ], | 214 | ], |
| 214 | ); | 215 | ); |
| 215 | 216 | ||
File renamed without changes.
File renamed without changes.
| @@ -8,7 +8,7 @@ import * as NodeSqliteClient from "../NodeSqliteClient.ts"; · 7 unmodified lines | |||
| 8 | it.effect("backfills the V2 event driver from payload before the legacy provider column", () => | 8 | it.effect("backfills the V2 event driver from payload before the legacy provider column", () => |
| 9 | Effect.gen(function* () { | 9 | Effect.gen(function* () { |
| 10 | const sql = yield* SqlClient.SqlClient; | 10 | const sql = yield* SqlClient.SqlClient; |
| 11 | yield* runMigrations({ toMigrationInclusive: 45 }); | 11 | yield* runMigrations({ toMigrationInclusive: 46 }); |
| 12 | 12 | ||
| 13 | yield* sql` | 13 | yield* sql` |
| 14 | INSERT INTO orchestration_v2_events ( | 14 | INSERT INTO orchestration_v2_events ( |
| @@ -37,7 +37,7 @@ it.effect("backfills the V2 event driver from payload before the legacy provider · 22 unmodified lines | |||
| 37 | ) | 37 | ) |
| 38 | `; | 38 | `; |
| 39 | 39 | ||
| 40 | yield* runMigrations({ toMigrationInclusive: 46 }); | 40 | yield* runMigrations({ toMigrationInclusive: 47 }); |
| 41 | 41 | ||
| 42 | const events = yield* sql<{ | 42 | const events = yield* sql<{ |
| 43 | readonly event_id: string; | 43 | readonly event_id: string; |
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -8,11 +8,11 @@ import * as NodeSqliteClient from "../NodeSqliteClient.ts"; · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); | 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); |
| 10 | 10 | ||
| 11 | layer("043_ApplicationEventSource", (it) => { | 11 | layer("050_ApplicationEventSource", (it) => { |
| 12 | it.effect("moves V2 events and current project state behind one global sequence", () => | 12 | it.effect("moves V2 events and current project state behind one global sequence", () => |
| 13 | Effect.gen(function* () { | 13 | Effect.gen(function* () { |
| 14 | const sql = yield* SqlClient.SqlClient; | 14 | const sql = yield* SqlClient.SqlClient; |
| 15 | yield* runMigrations({ toMigrationInclusive: 48 }); | 15 | yield* runMigrations({ toMigrationInclusive: 49 }); |
| 16 | 16 | ||
| 17 | yield* sql` | 17 | yield* sql` |
| 18 | INSERT INTO orchestration_v2_events ( | 18 | INSERT INTO orchestration_v2_events ( |
| @@ -84,7 +84,7 @@ layer("043_ApplicationEventSource", (it) => { · 65 unmodified lines | |||
| 84 | ) | 84 | ) |
| 85 | `; | 85 | `; |
| 86 | 86 | ||
| 87 | yield* runMigrations({ toMigrationInclusive: 49 }); | 87 | yield* runMigrations({ toMigrationInclusive: 50 }); |
| 88 | 88 | ||
| 89 | const events = yield* sql<{ | 89 | const events = yield* sql<{ |
| 90 | readonly sequence: number; | 90 | readonly sequence: number; |
File renamed without changes.
| @@ -8,11 +8,11 @@ import * as NodeSqliteClient from "../NodeSqliteClient.ts"; · 7 unmodified lines | |||
| 8 | 8 | ||
| 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); | 9 | const layer = it.layer(Layer.mergeAll(NodeSqliteClient.layerMemory())); |
| 10 | 10 | ||
| 11 | layer("050_OrchestrationV2EffectCancellation", (it) => { | 11 | layer("051_OrchestrationV2EffectCancellation", (it) => { |
| 12 | it.effect("preserves existing effects and adds the cancelled terminal status", () => | 12 | it.effect("preserves existing effects and adds the cancelled terminal status", () => |
| 13 | Effect.gen(function* () { | 13 | Effect.gen(function* () { |
| 14 | const sql = yield* SqlClient.SqlClient; | 14 | const sql = yield* SqlClient.SqlClient; |
| 15 | yield* runMigrations({ toMigrationInclusive: 49 }); | 15 | yield* runMigrations({ toMigrationInclusive: 50 }); |
| 16 | yield* sql` | 16 | yield* sql` |
| 17 | INSERT INTO orchestration_v2_effect_outbox ( | 17 | INSERT INTO orchestration_v2_effect_outbox ( |
| 18 | effect_id, | 18 | effect_id, |
| @@ -47,7 +47,7 @@ layer("050_OrchestrationV2EffectCancellation", (it) => { · 28 unmodified lines | |||
| 47 | ) | 47 | ) |
| 48 | `; | 48 | `; |
| 49 | 49 | ||
| 50 | yield* runMigrations({ toMigrationInclusive: 50 }); | 50 | yield* runMigrations({ toMigrationInclusive: 51 }); |
| 51 | yield* sql` | 51 | yield* sql` |
| 52 | UPDATE orchestration_v2_effect_outbox | 52 | UPDATE orchestration_v2_effect_outbox |
| 53 | SET | 53 | SET |
File renamed without changes.
File renamed without changes.
File renamed without changes.
| @@ -61,6 +61,7 @@ import { · 60 unmodified lines | |||
| 61 | type ProviderSnapshotSettings, | 61 | type ProviderSnapshotSettings, |
| 62 | } from "../providerUpdateSettings.ts"; | 62 | } from "../providerUpdateSettings.ts"; |
| 63 | import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts"; | 63 | import { makeClaudeCapabilitiesCacheKey, makeClaudeContinuationGroupKey } from "./ClaudeHome.ts"; |
| 64 | import { discoverClaudeSkills } from "./ClaudeSkills.ts"; | ||
| 64 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); | 65 | const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); |
| 65 | 66 | ||
| 66 | const DRIVER_KIND = ProviderDriverKind.make("claudeAgent"); | 67 | const DRIVER_KIND = ProviderDriverKind.make("claudeAgent"); |
| @@ -246,6 +247,17 @@ export const ClaudeDriver: ProviderDriver<ClaudeSettings, ClaudeDriverEnv> = { · 179 unmodified lines | |||
| 246 | accentColor, | 247 | accentColor, |
| 247 | enabled, | 248 | enabled, |
| 248 | snapshot, | 249 | snapshot, |
| 250 | snapshotForCwd: (cwd: string) => | ||
| 251 | !effectiveConfig.enabled | ||
| 252 | ? snapshot.getSnapshot | ||
| 253 | : Effect.all([ | ||
| 254 | snapshot.getSnapshot, | ||
| 255 | discoverClaudeSkills(effectiveConfig, cwd, processEnv), | ||
| 256 | ]).pipe( | ||
| 257 | Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills })), | ||
| 258 | Effect.provideService(FileSystem.FileSystem, fileSystem), | ||
| 259 | Effect.provideService(Path.Path, path), | ||
| 260 | ), | ||
| 249 | orchestrationAdapter, | 261 | orchestrationAdapter, |
| 250 | textGeneration, | 262 | textGeneration, |
| 251 | } satisfies ProviderInstance; | 263 | } satisfies ProviderInstance; |
| @@ -10,7 +10,7 @@ import * as ServerConfig from "./config.ts"; · 9 unmodified lines | |||
| 10 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; | 10 | import * as ServerRuntimeStartup from "./serverRuntimeStartup.ts"; |
| 11 | 11 | ||
| 12 | it("uses the canonical Codex model for auto-bootstrap", () => { | 12 | it("uses the canonical Codex model for auto-bootstrap", () => { |
| 13 | assert.deepEqual(ServerRuntimeStartup.getAutoBootstrapDefaultModelSelection(), { | 13 | assert.deepEqual(ServerRuntimeStartup.getAutoBootstrapThreadModelSelection(), { |
| 14 | instanceId: ProviderInstanceId.make("codex"), | 14 | instanceId: ProviderInstanceId.make("codex"), |
| 15 | model: DEFAULT_MODEL, | 15 | model: DEFAULT_MODEL, |
| 16 | }); | 16 | }); |
| @@ -176,7 +176,7 @@ export const launchStartupHeartbeat = recordStartupHeartbeat.pipe( · 175 unmodified lines | |||
| 176 | Effect.asVoid, | 176 | Effect.asVoid, |
| 177 | ); | 177 | ); |
| 178 | 178 | ||
| 179 | export const getAutoBootstrapDefaultModelSelection = (): ModelSelection => ({ | 179 | export const getAutoBootstrapThreadModelSelection = (): ModelSelection => ({ |
| 180 | instanceId: ProviderInstanceId.make("codex"), | 180 | instanceId: ProviderInstanceId.make("codex"), |
| 181 | model: DEFAULT_MODEL, | 181 | model: DEFAULT_MODEL, |
| 182 | }); | 182 | }); |
| @@ -210,13 +210,14 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { · 27 unmodified lines | |||
| 210 | let bootstrapThreadId: ThreadId | undefined; | 210 | let bootstrapThreadId: ThreadId | undefined; |
| 211 | 211 | ||
| 212 | if (serverConfig.autoBootstrapProjectFromCwd) { | 212 | if (serverConfig.autoBootstrapProjectFromCwd) { |
| 213 | const defaultModelSelection = getAutoBootstrapDefaultModelSelection(); | 213 | // Project creation has no user model choice; only the bootstrap thread |
| 214 | // gets an automatic selection, and an explicit project default wins. | ||
| 215 | const threadModelSelection = getAutoBootstrapThreadModelSelection(); | ||
| 214 | const { project } = yield* projects.bootstrap({ | 216 | const { project } = yield* projects.bootstrap({ |
| 215 | commandId: CommandId.make(yield* randomUUID), | 217 | commandId: CommandId.make(yield* randomUUID), |
| 216 | projectId: ProjectId.make(yield* randomUUID), | 218 | projectId: ProjectId.make(yield* randomUUID), |
| 217 | title: path.basename(serverConfig.cwd) || "project", | 219 | title: path.basename(serverConfig.cwd) || "project", |
| 218 | workspaceRoot: serverConfig.cwd, | 220 | workspaceRoot: serverConfig.cwd, |
| 219 | defaultModelSelection, | ||
| 220 | }); | 221 | }); |
| 221 | const shell = yield* threads.getShellSnapshot(); | 222 | const shell = yield* threads.getShellSnapshot(); |
| 222 | const existingThread = shell.threads.find( | 223 | const existingThread = shell.threads.find( |
| @@ -228,7 +229,7 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { · 5 unmodified lines | |||
| 228 | commandId: CommandId.make(yield* randomUUID), | 229 | commandId: CommandId.make(yield* randomUUID), |
| 229 | projectId: project.id, | 230 | projectId: project.id, |
| 230 | title: "New thread", | 231 | title: "New thread", |
| 231 | modelSelection: project.defaultModelSelection ?? defaultModelSelection, | 232 | modelSelection: project.defaultModelSelection ?? threadModelSelection, |
| 232 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, | 233 | interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, |
| 233 | runtimeMode: "full-access", | 234 | runtimeMode: "full-access", |
| 234 | workspaceStrategy: { type: "root" }, | 235 | workspaceStrategy: { type: "root" }, |
| @@ -176,7 +176,6 @@ import * as SourceControlProviderRegistry from "./sourceControl/SourceControlPro · 175 unmodified lines | |||
| 176 | import * as GitVcsDriver from "./vcs/GitVcsDriver.ts"; | 176 | import * as GitVcsDriver from "./vcs/GitVcsDriver.ts"; |
| 177 | import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts"; | 177 | import * as VcsDriverRegistry from "./vcs/VcsDriverRegistry.ts"; |
| 178 | import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts"; | 178 | import * as VcsProjectConfig from "./vcs/VcsProjectConfig.ts"; |
| 179 | import * as VcsProcess from "./vcs/VcsProcess.ts"; | ||
| 180 | import * as PairingGrantStore from "./auth/PairingGrantStore.ts"; | 179 | import * as PairingGrantStore from "./auth/PairingGrantStore.ts"; |
| 181 | import * as SessionStore from "./auth/SessionStore.ts"; | 180 | import * as SessionStore from "./auth/SessionStore.ts"; |
| 182 | import { failEnvironmentAuthInvalid, failEnvironmentInternal } from "./auth/http.ts"; | 181 | import { failEnvironmentAuthInvalid, failEnvironmentInternal } from "./auth/http.ts"; |
| @@ -1840,6 +1839,10 @@ const makeWsRpcLayer = ( · 1657 unmodified lines | |||
| 1840 | observeRpcEffect(WS_METHODS.pullRequestsListStats, pullRequests.listStats(input), { | 1839 | observeRpcEffect(WS_METHODS.pullRequestsListStats, pullRequests.listStats(input), { |
| 1841 | "rpc.aggregate": "pull-requests", | 1840 | "rpc.aggregate": "pull-requests", |
| 1842 | }), | 1841 | }), |
| 1842 | [WS_METHODS.pullRequestsSummary]: (input) => | ||
| 1843 | observeRpcEffect(WS_METHODS.pullRequestsSummary, pullRequests.summary(input), { | ||
| 1844 | "rpc.aggregate": "pull-requests", | ||
| 1845 | }), | ||
| 1843 | [WS_METHODS.pullRequestsDetail]: (input) => | 1846 | [WS_METHODS.pullRequestsDetail]: (input) => |
| 1844 | observeRpcEffect(WS_METHODS.pullRequestsDetail, pullRequests.detail(input), { | 1847 | observeRpcEffect(WS_METHODS.pullRequestsDetail, pullRequests.detail(input), { |
| 1845 | "rpc.aggregate": "pull-requests", | 1848 | "rpc.aggregate": "pull-requests", |
| @@ -2612,7 +2615,6 @@ export const websocketRpcRouteLayer = Layer.unwrap( · 766 unmodified lines | |||
| 2612 | ), | 2615 | ), |
| 2613 | ), | 2616 | ), |
| 2614 | ), | 2617 | ), |
| 2615 | Layer.provide(VcsProcess.layer), | ||
| 2616 | ), | 2618 | ), |
| 2617 | ), | 2619 | ), |
| 2618 | ), | 2620 | ), |
| @@ -406,6 +406,7 @@ import { · 405 unmodified lines | |||
| 406 | resolveServerSelfUpdateCapability, | 406 | resolveServerSelfUpdateCapability, |
| 407 | serverUpdateGuidance, | 407 | serverUpdateGuidance, |
| 408 | supportsDesktopAppUpdate, | 408 | supportsDesktopAppUpdate, |
| 409 | supportsServerUpdateThreadContinuation, | ||
| 409 | } from "../versionSkew"; | 410 | } from "../versionSkew"; |
| 410 | import { useAssetUrls } from "../assets/assetUrls"; | 411 | import { useAssetUrls } from "../assets/assetUrls"; |
| 411 | import type { CodexArtifactTemplate } from "@t3tools/client-runtime/codex-artifact-templates"; | 412 | import type { CodexArtifactTemplate } from "@t3tools/client-runtime/codex-artifact-templates"; |
| @@ -2394,6 +2395,7 @@ function ChatViewContent(props: ChatViewProps) { · 1982 unmodified lines | |||
| 2394 | const serverUpdateEnvironmentId = activeThread?.environmentId ?? null; | 2395 | const serverUpdateEnvironmentId = activeThread?.environmentId ?? null; |
| 2395 | const versionMismatchSelfUpdate = resolveServerSelfUpdateCapability(serverConfig); | 2396 | const versionMismatchSelfUpdate = resolveServerSelfUpdateCapability(serverConfig); |
| 2396 | const versionMismatchDesktopAppUpdate = supportsDesktopAppUpdate(serverConfig); | 2397 | const versionMismatchDesktopAppUpdate = supportsDesktopAppUpdate(serverConfig); |
| 2398 | const versionMismatchThreadContinuation = supportsServerUpdateThreadContinuation(serverConfig); | ||
| 2397 | const serverUpdateState = useAtomValue( | 2399 | const serverUpdateState = useAtomValue( |
| 2398 | serverEnvironment.updateStateAtom(serverUpdateEnvironmentId), | 2400 | serverEnvironment.updateStateAtom(serverUpdateEnvironmentId), |
| 2399 | ); | 2401 | ); |
| @@ -2527,6 +2529,7 @@ function ChatViewContent(props: ChatViewProps) { · 127 unmodified lines | |||
| 2527 | serverLabel={versionMismatchServerLabel} | 2529 | serverLabel={versionMismatchServerLabel} |
| 2528 | selfUpdate={versionMismatchSelfUpdate} | 2530 | selfUpdate={versionMismatchSelfUpdate} |
| 2529 | desktopAppUpdate={versionMismatchDesktopAppUpdate} | 2531 | desktopAppUpdate={versionMismatchDesktopAppUpdate} |
| 2532 | threadContinuation={versionMismatchThreadContinuation} | ||
| 2530 | targetVersion={versionMismatch.clientVersion} | 2533 | targetVersion={versionMismatch.clientVersion} |
| 2531 | label={updateFailed ? "Retry" : "Update"} | 2534 | label={updateFailed ? "Retry" : "Update"} |
| 2532 | variant="ghost" | 2535 | variant="ghost" |
| @@ -2561,6 +2564,7 @@ function ChatViewContent(props: ChatViewProps) { · 28 unmodified lines | |||
| 2561 | serverUpdateEnvironmentId, | 2564 | serverUpdateEnvironmentId, |
| 2562 | versionMismatchSelfUpdate, | 2565 | versionMismatchSelfUpdate, |
| 2563 | versionMismatchDesktopAppUpdate, | 2566 | versionMismatchDesktopAppUpdate, |
| 2567 | versionMismatchThreadContinuation, | ||
| 2564 | versionMismatchServerLabel, | 2568 | versionMismatchServerLabel, |
| 2565 | ]); | 2569 | ]); |
| 2566 | const providerStatuses = serverConfig?.providers ?? EMPTY_PROVIDERS; | 2570 | const providerStatuses = serverConfig?.providers ?? EMPTY_PROVIDERS; |
| @@ -4429,7 +4429,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) · 4428 unmodified lines | |||
| 4429 | ) : null} | 4429 | ) : null} |
| 4430 | <ComposerFooterPrimaryActions | 4430 | <ComposerFooterPrimaryActions |
| 4431 | compact={isComposerPrimaryActionsCompact} | 4431 | compact={isComposerPrimaryActionsCompact} |
| 4432 | activeContextWindow={activeContextWindow} | 4432 | activeContextWindow={ |
| 4433 | settings.contextWindowMeterEnabled ? activeContextWindow : null | ||
| 4434 | } | ||
| 4433 | activeThreadModelDisplayName={activeThreadModelDisplayName} | 4435 | activeThreadModelDisplayName={activeThreadModelDisplayName} |
| 4434 | pendingAction={pendingPrimaryAction} | 4436 | pendingAction={pendingPrimaryAction} |
| 4435 | isRunning={phase === "running"} | 4437 | isRunning={phase === "running"} |
| @@ -5,6 +5,8 @@ import { · 4 unmodified lines | |||
| 5 | CopyIcon, | 5 | CopyIcon, |
| 6 | DownloadIcon, | 6 | DownloadIcon, |
| 7 | LoaderIcon, | 7 | LoaderIcon, |
| 8 | LockIcon, | ||
| 9 | LockOpenIcon, | ||
| 8 | PlusIcon, | 10 | PlusIcon, |
| 9 | Trash2Icon, | 11 | Trash2Icon, |
| 10 | XIcon, | 12 | XIcon, |
| @@ -33,12 +35,10 @@ import { useCopyToClipboard } from "../../hooks/useCopyToClipboard"; · 22 unmodified lines | |||
| 33 | import { normalizeProviderAccentColor } from "../../providerInstances"; | 35 | import { normalizeProviderAccentColor } from "../../providerInstances"; |
| 34 | import { Badge } from "../ui/badge"; | 36 | import { Badge } from "../ui/badge"; |
| 35 | import { Button } from "../ui/button"; | 37 | import { Button } from "../ui/button"; |
| 36 | import { Checkbox } from "../ui/checkbox"; | ||
| 37 | import { DraftInput } from "../ui/draft-input"; | 38 | import { DraftInput } from "../ui/draft-input"; |
| 38 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; | 39 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; |
| 39 | import { ScrollArea } from "../ui/scroll-area"; | 40 | import { ScrollArea } from "../ui/scroll-area"; |
| 40 | import { Switch } from "../ui/switch"; | 41 | import { Switch } from "../ui/switch"; |
| 41 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"; | ||
| 42 | import { stackedThreadToast, toastManager } from "../ui/toast"; | 42 | import { stackedThreadToast, toastManager } from "../ui/toast"; |
| 43 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; | 43 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 44 | import type { DriverOption, ProviderEnvironmentFieldDefinition } from "./providerDriverMeta"; | 44 | import type { DriverOption, ProviderEnvironmentFieldDefinition } from "./providerDriverMeta"; |
| @@ -58,6 +58,13 @@ import { · 13 unmodified lines | |||
| 58 | 58 | ||
| 59 | const ENVIRONMENT_VARIABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/; | 59 | const ENVIRONMENT_VARIABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/; |
| 60 | 60 | ||
| 61 | /** Label-left field grid for the Configuration tab: one row per field. */ | ||
| 62 | const PROVIDER_FIELD_GRID_CLASS_NAME = | ||
| 63 | "grid gap-x-4 gap-y-2.5 sm:grid-cols-[8rem_minmax(0,1fr)] sm:items-start"; | ||
| 64 | /** Full-width divider row that names the group of fields below it. */ | ||
| 65 | const PROVIDER_FIELD_GROUP_LABEL_CLASS_NAME = | ||
| 66 | "col-span-full mt-1 border-t border-border/60 pt-2.5 text-[11px] text-muted-foreground"; | ||
| 67 | |||
| 61 | let environmentVariableDraftId = 0; | 68 | let environmentVariableDraftId = 0; |
| 62 | const nextEnvironmentVariableDraftId = () => `provider-env-${environmentVariableDraftId++}`; | 69 | const nextEnvironmentVariableDraftId = () => `provider-env-${environmentVariableDraftId++}`; |
| 63 | 70 | ||
| @@ -130,6 +137,43 @@ function nextConfigBlobWithValue( · 66 unmodified lines | |||
| 130 | return base; | 137 | return base; |
| 131 | } | 138 | } |
| 132 | 139 | ||
| 140 | export function deriveProviderModelsForDisplay(input: { | ||
| 141 | readonly liveModels: ReadonlyArray<ServerProviderModel> | undefined; | ||
| 142 | readonly customModels: ReadonlyArray<string>; | ||
| 143 | }): ReadonlyArray<ServerProviderModel> { | ||
| 144 | const liveCustomModelsBySlug = new Map( | ||
| 145 | Arr.filterMap(input.liveModels ?? [], (model) => | ||
| 146 | model.isCustom ? Result.succeed([model.slug, model] as const) : Result.failVoid, | ||
| 147 | ), | ||
| 148 | ); | ||
| 149 | const serverModels = input.liveModels?.filter((model) => !model.isCustom) ?? []; | ||
| 150 | const customModels = input.customModels.map( | ||
| 151 | (slug) => | ||
| 152 | liveCustomModelsBySlug.get(slug) ?? { | ||
| 153 | slug, | ||
| 154 | name: slug, | ||
| 155 | isCustom: true, | ||
| 156 | capabilities: null, | ||
| 157 | }, | ||
| 158 | ); | ||
| 159 | return [...serverModels, ...customModels]; | ||
| 160 | } | ||
| 161 | |||
| 162 | function ProviderAuthEmail(props: { readonly email: string | undefined }) { | ||
| 163 | const email = props.email?.trim(); | ||
| 164 | if (!email) return null; | ||
| 165 | |||
| 166 | return ( | ||
| 167 | <RedactedSensitiveText | ||
| 168 | value={email} | ||
| 169 | ariaLabel="Toggle account email visibility" | ||
| 170 | revealTooltip="Click to reveal email" | ||
| 171 | hideTooltip="Click to hide email" | ||
| 172 | className="max-w-full truncate" | ||
| 173 | /> | ||
| 174 | ); | ||
| 175 | } | ||
| 176 | |||
| 133 | export function readProviderEnvironmentVariable( | 177 | export function readProviderEnvironmentVariable( |
| 134 | environment: ReadonlyArray<ProviderInstanceEnvironmentVariable> | undefined, | 178 | environment: ReadonlyArray<ProviderInstanceEnvironmentVariable> | undefined, |
| 135 | name: string, | 179 | name: string, |
| @@ -179,38 +223,50 @@ export function nextProviderEnvironmentWithFieldValue( · 43 unmodified lines | |||
| 179 | return next; | 223 | return next; |
| 180 | } | 224 | } |
| 181 | 225 | ||
| 182 | export function deriveProviderModelsForDisplay(input: { | 226 | function ProviderEnvironmentFieldRow(props: { |
| 183 | readonly liveModels: ReadonlyArray<ServerProviderModel> | undefined; | 227 | readonly field: ProviderEnvironmentFieldDefinition; |
| 184 | readonly customModels: ReadonlyArray<CustomModelDefinition>; | 228 | readonly variable: ProviderInstanceEnvironmentVariable | undefined; |
| 185 | }): ReadonlyArray<ServerProviderModel> { | 229 | readonly idPrefix: string; |
| 186 | const liveCustomModelsBySlug = new Map( | 230 | readonly onCommit: (field: ProviderEnvironmentFieldDefinition, value: string) => void; |
| 187 | Arr.filterMap(input.liveModels ?? [], (model) => | 231 | readonly onRemove: (field: ProviderEnvironmentFieldDefinition) => void; |
| 188 | model.isCustom ? Result.succeed([model.slug, model] as const) : Result.failVoid, | 232 | }) { |
| 189 | ), | 233 | const inputId = `${props.idPrefix}-environment-${props.field.name}`; |
| 190 | ); | 234 | const value = props.variable?.valueRedacted ? "" : (props.variable?.value ?? ""); |
| 191 | const serverModels = input.liveModels?.filter((model) => !model.isCustom) ?? []; | 235 | const placeholder = props.variable?.valueRedacted |
| 192 | const customModels = input.customModels.map((entry) => ({ | 236 | ? "Stored secret - enter a new value to replace" |
| 193 | slug: entry.slug, | 237 | : props.field.placeholder; |
| 194 | name: entry.name, | ||
| 195 | isCustom: true, | ||
| 196 | capabilities: | ||
| 197 | entry.capabilities ?? liveCustomModelsBySlug.get(entry.slug)?.capabilities ?? null, | ||
| 198 | })); | ||
| 199 | return [...serverModels, ...customModels]; | ||
| 200 | } | ||
| 201 | |||
| 202 | function ProviderAuthEmail(props: { readonly email: string | undefined }) { | ||
| 203 | const email = props.email?.trim(); | ||
| 204 | if (!email) return null; | ||
| 205 | 238 | ||
| 206 | return ( | 239 | return ( |
| 207 | <RedactedSensitiveText | 240 | <label htmlFor={inputId} className="block"> |
| 208 | value={email} | 241 | <span className="text-xs font-medium text-foreground">{props.field.label}</span> |
| 209 | ariaLabel="Toggle account email visibility" | 242 | <div className="mt-1.5 flex min-w-0 items-center gap-2"> |
| 210 | revealTooltip="Click to reveal email" | 243 | <DraftInput |
| 211 | hideTooltip="Click to hide email" | 244 | id={inputId} |
| 212 | className="max-w-full truncate" | 245 | className="min-w-0 flex-1" |
| 213 | /> | 246 | type={props.field.sensitive === false ? undefined : "password"} |
| 247 | autoComplete="off" | ||
| 248 | value={value} | ||
| 249 | onCommit={(next) => props.onCommit(props.field, next)} | ||
| 250 | placeholder={placeholder} | ||
| 251 | spellCheck={false} | ||
| 252 | /> | ||
| 253 | {props.variable ? ( | ||
| 254 | <Button | ||
| 255 | type="button" | ||
| 256 | size="icon-sm" | ||
| 257 | variant="ghost" | ||
| 258 | className="size-8 shrink-0 text-muted-foreground hover:text-destructive" | ||
| 259 | onClick={() => props.onRemove(props.field)} | ||
| 260 | aria-label={`Clear ${props.field.label}`} | ||
| 261 | > | ||
| 262 | <XIcon className="size-3.5" /> | ||
| 263 | </Button> | ||
| 264 | ) : null} | ||
| 265 | </div> | ||
| 266 | {props.field.description ? ( | ||
| 267 | <span className="mt-1 block text-xs text-muted-foreground">{props.field.description}</span> | ||
| 268 | ) : null} | ||
| 269 | </label> | ||
| 214 | ); | 270 | ); |
| 215 | } | 271 | } |
| 216 | 272 | ||
| @@ -284,166 +340,105 @@ function ProviderEnvironmentSection(props: { · 67 unmodified lines | |||
| 284 | publishRows(nextRows); | 340 | publishRows(nextRows); |
| 285 | }; | 341 | }; |
| 286 | 342 | ||
| 287 | return ( | 343 | const addVariable = () => |
| 288 | <div className="grid gap-2"> | 344 | setRows([ |
| 289 | <div className="flex items-center justify-between gap-3"> | 345 | ...rows, |
| 290 | <span className="text-xs font-medium text-foreground">Environment variables</span> | 346 | { |
| 291 | <Button | 347 | id: nextEnvironmentVariableDraftId(), |
| 292 | type="button" | 348 | name: "", |
| 293 | size="sm" | 349 | value: "", |
| 294 | variant="outline" | 350 | sensitive: true, |
| 295 | className="h-7 gap-1.5 px-2 text-xs" | 351 | }, |
| 296 | onClick={() => | 352 | ]); |
| 297 | setRows([ | ||
| 298 | ...rows, | ||
| 299 | { | ||
| 300 | id: nextEnvironmentVariableDraftId(), | ||
| 301 | name: "", | ||
| 302 | value: "", | ||
| 303 | sensitive: true, | ||
| 304 | }, | ||
| 305 | ]) | ||
| 306 | } | ||
| 307 | > | ||
| 308 | <PlusIcon className="size-3" /> | ||
| 309 | Add | ||
| 310 | </Button> | ||
| 311 | </div> | ||
| 312 | {rows.length === 0 ? ( | ||
| 313 | <p className="text-xs text-muted-foreground"> | ||
| 314 | Add variables to pass API keys, base URLs, or other per-instance CLI settings. | ||
| 315 | </p> | ||
| 316 | ) : ( | ||
| 317 | <div className="overflow-hidden rounded-md border border-border/70"> | ||
| 318 | <Table> | ||
| 319 | <TableHeader className="bg-muted/25 text-[11px] text-muted-foreground"> | ||
| 320 | <TableRow className="hover:bg-transparent"> | ||
| 321 | <TableHead>Variable</TableHead> | ||
| 322 | <TableHead>Value</TableHead> | ||
| 323 | <TableHead className="w-20">Sensitive</TableHead> | ||
| 324 | <TableHead className="w-12 text-right"> | ||
| 325 | <span className="sr-only">Options</span> | ||
| 326 | </TableHead> | ||
| 327 | </TableRow> | ||
| 328 | </TableHeader> | ||
| 329 | <TableBody> | ||
| 330 | {rows.map((variable, index) => ( | ||
| 331 | <TableRow | ||
| 332 | key={variable.id} | ||
| 333 | className="border-border/60 odd:bg-muted/20 even:bg-background/20" | ||
| 334 | > | ||
| 335 | <TableCell> | ||
| 336 | <DraftInput | ||
| 337 | value={variable.name} | ||
| 338 | onCommit={(name) => updateVariable(variable.id, { name: name.trim() })} | ||
| 339 | placeholder="VARIABLE_NAME" | ||
| 340 | spellCheck={false} | ||
| 341 | aria-label={`Environment variable name ${index + 1}`} | ||
| 342 | /> | ||
| 343 | </TableCell> | ||
| 344 | <TableCell> | ||
| 345 | <DraftInput | ||
| 346 | value={variable.valueRedacted ? "" : variable.value} | ||
| 347 | onCommit={(value) => updateVariable(variable.id, { value })} | ||
| 348 | type={variable.sensitive ? "password" : undefined} | ||
| 349 | autoComplete="off" | ||
| 350 | placeholder={ | ||
| 351 | variable.valueRedacted | ||
| 352 | ? "Stored secret - enter a new value to replace" | ||
| 353 | : "Value" | ||
| 354 | } | ||
| 355 | spellCheck={false} | ||
| 356 | aria-label={`Environment variable value ${index + 1}`} | ||
| 357 | /> | ||
| 358 | </TableCell> | ||
| 359 | <TableCell className="w-20"> | ||
| 360 | <div className="flex h-8 items-center justify-center"> | ||
| 361 | <Checkbox | ||
| 362 | checked={variable.sensitive} | ||
| 363 | onCheckedChange={(checked) => { | ||
| 364 | const sensitive = Boolean(checked); | ||
| 365 | updateVariable(variable.id, { | ||
| 366 | sensitive, | ||
| 367 | ...(sensitive && variable.valueRedacted === undefined | ||
| 368 | ? {} | ||
| 369 | : { valueRedacted: sensitive ? variable.valueRedacted : false }), | ||
| 370 | }); | ||
| 371 | }} | ||
| 372 | aria-label={`Mark environment variable ${variable.name || index + 1} as sensitive`} | ||
| 373 | /> | ||
| 374 | </div> | ||
| 375 | </TableCell> | ||
| 376 | <TableCell className="w-12"> | ||
| 377 | <div className="flex justify-end"> | ||
| 378 | <Button | ||
| 379 | type="button" | ||
| 380 | size="icon-sm" | ||
| 381 | variant="ghost" | ||
| 382 | className="size-8 text-muted-foreground hover:text-destructive" | ||
| 383 | onClick={() => removeVariable(variable.id)} | ||
| 384 | aria-label={`Remove environment variable ${variable.name || index + 1}`} | ||
| 385 | > | ||
| 386 | <XIcon className="size-3.5" /> | ||
| 387 | </Button> | ||
| 388 | </div> | ||
| 389 | </TableCell> | ||
| 390 | </TableRow> | ||
| 391 | ))} | ||
| 392 | </TableBody> | ||
| 393 | </Table> | ||
| 394 | </div> | ||
| 395 | )} | ||
| 396 | <span className="text-xs text-muted-foreground"> | ||
| 397 | Sensitive values are stored separately and are not returned to the app after saving. | ||
| 398 | </span> | ||
| 399 | </div> | ||
| 400 | ); | ||
| 401 | } | ||
| 402 | |||
| 403 | function ProviderEnvironmentFieldRow(props: { | ||
| 404 | readonly field: ProviderEnvironmentFieldDefinition; | ||
| 405 | readonly variable: ProviderInstanceEnvironmentVariable | undefined; | ||
| 406 | readonly idPrefix: string; | ||
| 407 | readonly onCommit: (field: ProviderEnvironmentFieldDefinition, value: string) => void; | ||
| 408 | readonly onRemove: (field: ProviderEnvironmentFieldDefinition) => void; | ||
| 409 | }) { | ||
| 410 | const inputId = `${props.idPrefix}-environment-${props.field.name}`; | ||
| 411 | const value = props.variable?.valueRedacted ? "" : (props.variable?.value ?? ""); | ||
| 412 | const placeholder = props.variable?.valueRedacted | ||
| 413 | ? "Stored secret - enter a new value to replace" | ||
| 414 | : props.field.placeholder; | ||
| 415 | 353 | ||
| 416 | return ( | 354 | return ( |
| 417 | <label htmlFor={inputId} className="block"> | 355 | <div className="min-w-0 space-y-1.5"> |
| 418 | <span className="text-xs font-medium text-foreground">{props.field.label}</span> | 356 | {rows.map((variable, index) => ( |
| 419 | <div className="mt-1.5 flex min-w-0 items-center gap-2"> | 357 | <div key={variable.id} className="flex min-w-0 items-center gap-1.5"> |
| 420 | <DraftInput | 358 | <DraftInput |
| 421 | id={inputId} | 359 | size="compact" |
| 422 | className="min-w-0 flex-1" | 360 | className="w-44 shrink-0 font-mono" |
| 423 | type={props.field.sensitive === false ? undefined : "password"} | 361 | value={variable.name} |
| 424 | autoComplete="off" | 362 | onCommit={(name) => updateVariable(variable.id, { name: name.trim() })} |
| 425 | value={value} | 363 | placeholder="VARIABLE_NAME" |
| 426 | onCommit={(next) => props.onCommit(props.field, next)} | 364 | spellCheck={false} |
| 427 | placeholder={placeholder} | 365 | aria-label={`Environment variable name ${index + 1}`} |
| 428 | spellCheck={false} | 366 | /> |
| 429 | /> | 367 | <span className="text-xs text-muted-foreground" aria-hidden> |
| 430 | {props.variable ? ( | 368 | = |
| 369 | </span> | ||
| 370 | <DraftInput | ||
| 371 | size="compact" | ||
| 372 | className="min-w-0 flex-1 font-mono" | ||
| 373 | value={variable.valueRedacted ? "" : variable.value} | ||
| 374 | onCommit={(value) => updateVariable(variable.id, { value })} | ||
| 375 | type={variable.sensitive ? "password" : undefined} | ||
| 376 | autoComplete="off" | ||
| 377 | placeholder={ | ||
| 378 | variable.valueRedacted ? "Stored secret, enter a new value to replace" : "value" | ||
| 379 | } | ||
| 380 | spellCheck={false} | ||
| 381 | aria-label={`Environment variable value ${index + 1}`} | ||
| 382 | /> | ||
| 383 | <Tooltip> | ||
| 384 | <TooltipTrigger | ||
| 385 | render={ | ||
| 386 | <Button | ||
| 387 | type="button" | ||
| 388 | size="icon-micro" | ||
| 389 | variant="ghost-muted" | ||
| 390 | className={cn( | ||
| 391 | "[--control-icon-color:currentColor]", | ||
| 392 | variable.sensitive && "text-foreground", | ||
| 393 | )} | ||
| 394 | onClick={() => { | ||
| 395 | const sensitive = !variable.sensitive; | ||
| 396 | updateVariable(variable.id, { | ||
| 397 | sensitive, | ||
| 398 | ...(sensitive && variable.valueRedacted === undefined | ||
| 399 | ? {} | ||
| 400 | : { valueRedacted: sensitive ? variable.valueRedacted : false }), | ||
| 401 | }); | ||
| 402 | }} | ||
| 403 | aria-pressed={variable.sensitive} | ||
| 404 | aria-label={`Mark environment variable ${variable.name || index + 1} as sensitive`} | ||
| 405 | > | ||
| 406 | {variable.sensitive ? ( | ||
| 407 | <LockIcon className="size-3" /> | ||
| 408 | ) : ( | ||
| 409 | <LockOpenIcon className="size-3" /> | ||
| 410 | )} | ||
| 411 | </Button> | ||
| 412 | } | ||
| 413 | /> | ||
| 414 | <TooltipPopup side="top"> | ||
| 415 | {variable.sensitive ? "Sensitive, stored separately" : "Plain text"} | ||
| 416 | </TooltipPopup> | ||
| 417 | </Tooltip> | ||
| 431 | <Button | 418 | <Button |
| 432 | type="button" | 419 | type="button" |
| 433 | size="icon-sm" | 420 | size="icon-micro" |
| 434 | variant="ghost" | 421 | variant="ghost-muted" |
| 435 | className="size-8 shrink-0 text-muted-foreground hover:text-destructive" | 422 | className="[--control-icon-color:currentColor] hover:text-destructive" |
| 436 | onClick={() => props.onRemove(props.field)} | 423 | onClick={() => removeVariable(variable.id)} |
| 437 | aria-label={`Clear ${props.field.label}`} | 424 | aria-label={`Remove environment variable ${variable.name || index + 1}`} |
| 438 | > | 425 | > |
| 439 | <XIcon className="size-3.5" /> | 426 | <XIcon className="size-3" /> |
| 440 | </Button> | 427 | </Button> |
| 441 | ) : null} | 428 | </div> |
| 429 | ))} | ||
| 430 | <div className="flex min-h-[1.875rem] flex-wrap items-center gap-x-3 gap-y-1"> | ||
| 431 | <Button type="button" size="xs" variant="ghost-muted" onClick={addVariable}> | ||
| 432 | <PlusIcon className="size-3" /> | ||
| 433 | Add variable | ||
| 434 | </Button> | ||
| 435 | <span className="text-xs text-muted-foreground"> | ||
| 436 | {rows.length === 0 | ||
| 437 | ? "API keys, base URLs, or other per-instance CLI settings." | ||
| 438 | : "Sensitive values are stored separately and never returned to the app."} | ||
| 439 | </span> | ||
| 442 | </div> | 440 | </div> |
| 443 | {props.field.description ? ( | 441 | </div> |
| 444 | <span className="mt-1 block text-xs text-muted-foreground">{props.field.description}</span> | ||
| 445 | ) : null} | ||
| 446 | </label> | ||
| 447 | ); | 442 | ); |
| 448 | } | 443 | } |
| 449 | 444 | ||
| @@ -458,7 +453,7 @@ interface ProviderInstanceCardProps { · 8 unmodified lines | |||
| 458 | readonly readOnly?: boolean | undefined; | 453 | readonly readOnly?: boolean | undefined; |
| 459 | readonly onUpdate: (nextInstance: ProviderInstanceConfig) => void; | 454 | readonly onUpdate: (nextInstance: ProviderInstanceConfig) => void; |
| 460 | /** | 455 | /** |
| 461 | * Pass `undefined` to hide the delete button entirely. Built-in default | 456 | * Pass `undefined` to hide the delete footer entirely. Built-in default |
| 462 | * instance slots use `undefined` — they can't be deleted without losing | 457 | * instance slots use `undefined` — they can't be deleted without losing |
| 463 | * the slot, and their "reset to defaults" affordance lives on an outer | 458 | * the slot, and their "reset to defaults" affordance lives on an outer |
| 464 | * reset button instead. Explicit `| undefined` in the type accommodates | 459 | * reset button instead. Explicit `| undefined` in the type accommodates |
| @@ -578,12 +573,6 @@ export function ProviderInstanceCard({ · 113 unmodified lines | |||
| 578 | const visibleTab = driverOption === undefined ? "configuration" : activeTab; | 573 | const visibleTab = driverOption === undefined ? "configuration" : activeTab; |
| 579 | 574 | ||
| 580 | const customModels = readConfigStringArray(instance.config, "customModels"); | 575 | const customModels = readConfigStringArray(instance.config, "customModels"); |
| 581 | const environmentFields = driverOption?.environmentFields ?? []; | ||
| 582 | const environmentFieldNames = new Set(environmentFields.map((field) => field.name)); | ||
| 583 | const genericEnvironment = providerEnvironmentWithoutNames( | ||
| 584 | instance.environment, | ||
| 585 | environmentFieldNames, | ||
| 586 | ); | ||
| 587 | // Server-returned models may lag behind settings writes. Treat probe | 576 | // Server-returned models may lag behind settings writes. Treat probe |
| 588 | // models as the source for built-ins only; custom rows come directly | 577 | // models as the source for built-ins only; custom rows come directly |
| 589 | // from the current instance config so add/remove reflects immediately. | 578 | // from the current instance config so add/remove reflects immediately. |
| @@ -591,6 +580,9 @@ export function ProviderInstanceCard({ · 1 unmodified line | |||
| 591 | liveModels: liveProvider?.models, | 580 | liveModels: liveProvider?.models, |
| 592 | customModels, | 581 | customModels, |
| 593 | }); | 582 | }); |
| 583 | const hiddenModelCount = modelsForDisplay.filter( | ||
| 584 | (model) => !model.isCustom && hiddenModels.includes(model.slug), | ||
| 585 | ).length; | ||
| 594 | 586 | ||
| 595 | const updateDisplayName = (value: string) => { | 587 | const updateDisplayName = (value: string) => { |
| 596 | const trimmed = value.trim(); | 588 | const trimmed = value.trim(); |
| @@ -644,7 +636,14 @@ export function ProviderInstanceCard({ · 47 unmodified lines | |||
| 644 | : (rest as ProviderInstanceConfig), | 636 | : (rest as ProviderInstanceConfig), |
| 645 | ); | 637 | ); |
| 646 | }; | 638 | }; |
| 647 | 639 | // Drivers that need a named secret (Cursor's API key) get a dedicated field; | |
| 640 | // the generic editor only shows the remaining variables. | ||
| 641 | const environmentFields = driverOption?.environmentFields ?? []; | ||
| 642 | const environmentFieldNames = new Set(environmentFields.map((field) => field.name)); | ||
| 643 | const genericEnvironment = providerEnvironmentWithoutNames( | ||
| 644 | instance.environment, | ||
| 645 | environmentFieldNames, | ||
| 646 | ); | ||
| 648 | const updateGenericEnvironment = ( | 647 | const updateGenericEnvironment = ( |
| 649 | environment: ReadonlyArray<ProviderInstanceEnvironmentVariable>, | 648 | environment: ReadonlyArray<ProviderInstanceEnvironmentVariable>, |
| 650 | ) => { | 649 | ) => { |
| @@ -653,11 +652,9 @@ export function ProviderInstanceCard({ · 2 unmodified lines | |||
| 653 | ); | 652 | ); |
| 654 | updateEnvironment([...dedicatedEnvironment, ...environment]); | 653 | updateEnvironment([...dedicatedEnvironment, ...environment]); |
| 655 | }; | 654 | }; |
| 656 | |||
| 657 | const updateEnvironmentField = (field: ProviderEnvironmentFieldDefinition, value: string) => { | 655 | const updateEnvironmentField = (field: ProviderEnvironmentFieldDefinition, value: string) => { |
| 658 | updateEnvironment(nextProviderEnvironmentWithFieldValue(instance.environment, field, value)); | 656 | updateEnvironment(nextProviderEnvironmentWithFieldValue(instance.environment, field, value)); |
| 659 | }; | 657 | }; |
| 660 | |||
| 661 | const removeEnvironmentField = (field: ProviderEnvironmentFieldDefinition) => { | 658 | const removeEnvironmentField = (field: ProviderEnvironmentFieldDefinition) => { |
| 662 | updateEnvironment(providerEnvironmentWithoutNames(instance.environment, new Set([field.name]))); | 659 | updateEnvironment(providerEnvironmentWithoutNames(instance.environment, new Set([field.name]))); |
| 663 | }; | 660 | }; |
| @@ -704,35 +701,9 @@ export function ProviderInstanceCard({ · 40 unmodified lines | |||
| 704 | </> | 701 | </> |
| 705 | ); | 702 | ); |
| 706 | 703 | ||
| 707 | const titleTailNode = ( | 704 | const titleTailNode = headerAction ? ( |
| 708 | <> | 705 | <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center">{headerAction}</span> |
| 709 | {headerAction ? ( | 706 | ) : null; |
| 710 | <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> | ||
| 711 | {headerAction} | ||
| 712 | </span> | ||
| 713 | ) : null} | ||
| 714 | {onDelete ? ( | ||
| 715 | <span className="inline-flex h-5 w-5 shrink-0 items-center justify-center"> | ||
| 716 | <Tooltip> | ||
| 717 | <TooltipTrigger | ||
| 718 | render={ | ||
| 719 | <Button | ||
| 720 | size="icon-micro" | ||
| 721 | variant="ghost" | ||
| 722 | className="text-muted-foreground hover:text-destructive" | ||
| 723 | onClick={onDelete} | ||
| 724 | aria-label={`Delete provider instance ${instanceId}`} | ||
| 725 | > | ||
| 726 | <Trash2Icon className="size-3" /> | ||
| 727 | </Button> | ||
| 728 | } | ||
| 729 | /> | ||
| 730 | <TooltipPopup side="top">Delete instance</TooltipPopup> | ||
| 731 | </Tooltip> | ||
| 732 | </span> | ||
| 733 | ) : null} | ||
| 734 | </> | ||
| 735 | ); | ||
| 736 | 707 | ||
| 737 | const versionCodeNode = versionLabel ? ( | 708 | const versionCodeNode = versionLabel ? ( |
| 738 | <code className="text-xs text-muted-foreground">{versionLabel}</code> | 709 | <code className="text-xs text-muted-foreground">{versionLabel}</code> |
| @@ -757,7 +728,7 @@ export function ProviderInstanceCard({ · 18 unmodified lines | |||
| 757 | className={cn( | 728 | className={cn( |
| 758 | // Sidebar-style selection with a fixed row height so the list stays | 729 | // Sidebar-style selection with a fixed row height so the list stays |
| 759 | // even; the status line clamps to two lines instead of growing. | 730 | // even; the status line clamps to two lines instead of growing. |
| 760 | "group flex h-19 items-start gap-3 rounded-md px-3 py-2 transition-colors", | 731 | "group flex min-h-19 items-start gap-3 rounded-md px-3 py-2 transition-colors", |
| 761 | // Foreground-alpha tint so the fill reads the same in light and dark themes. | 732 | // Foreground-alpha tint so the fill reads the same in light and dark themes. |
| 762 | selected ? "bg-foreground/8" : "hover:bg-foreground/4", | 733 | selected ? "bg-foreground/8" : "hover:bg-foreground/4", |
| 763 | )} | 734 | )} |
| @@ -839,7 +810,7 @@ export function ProviderInstanceCard({ · 75 unmodified lines | |||
| 839 | size="icon-xs" | 810 | size="icon-xs" |
| 840 | variant="ghost" | 811 | variant="ghost" |
| 841 | className={cn( | 812 | className={cn( |
| 842 | "size-5 rounded-sm p-0", | 813 | "size-5 rounded-sm p-0 [--control-icon-color:currentColor]", |
| 843 | versionAdvisory.emphasis === "strong" | 814 | versionAdvisory.emphasis === "strong" |
| 844 | ? "text-warning hover:text-warning" | 815 | ? "text-warning hover:text-warning" |
| 845 | : "text-muted-foreground hover:text-foreground", | 816 | : "text-muted-foreground hover:text-foreground", |
| @@ -947,6 +918,25 @@ export function ProviderInstanceCard({ · 101 unmodified lines | |||
| 947 | </p> | 918 | </p> |
| 948 | ) : null} | 919 | ) : null} |
| 949 | </div> | 920 | </div> |
| 921 | {onDelete ? ( | ||
| 922 | <span | ||
| 923 | inert={readOnly} | ||
| 924 | aria-disabled={readOnly || undefined} | ||
| 925 | className={cn("shrink-0", readOnly && "opacity-50")} | ||
| 926 | > | ||
| 927 | <Button | ||
| 928 | type="button" | ||
| 929 | size="xs" | ||
| 930 | variant="ghost" | ||
| 931 | className="[--control-icon-color:currentColor] text-muted-foreground hover:text-destructive" | ||
| 932 | onClick={onDelete} | ||
| 933 | aria-label={`Delete instance ${instanceId}`} | ||
| 934 | > | ||
| 935 | <Trash2Icon className="size-3" /> | ||
| 936 | Delete instance | ||
| 937 | </Button> | ||
| 938 | </span> | ||
| 939 | ) : null} | ||
| 950 | </div> | 940 | </div> |
| 951 | 941 | ||
| 952 | <div className="flex h-11 shrink-0 border-b border-border/70 px-1"> | 942 | <div className="flex h-11 shrink-0 border-b border-border/70 px-1"> |
| @@ -962,10 +952,14 @@ export function ProviderInstanceCard({ · 9 unmodified lines | |||
| 962 | <button | 952 | <button |
| 963 | type="button" | 953 | type="button" |
| 964 | aria-pressed={visibleTab === "models"} | 954 | aria-pressed={visibleTab === "models"} |
| 965 | className={providerSettingsTabClassName(visibleTab === "models")} | 955 | className={cn(providerSettingsTabClassName(visibleTab === "models"), "gap-1.5")} |
| 966 | onClick={() => setActiveTab("models")} | 956 | onClick={() => setActiveTab("models")} |
| 967 | > | 957 | > |
| 968 | Models | 958 | Models |
| 959 | <span className="font-normal text-muted-foreground/70"> | ||
| 960 | {modelsForDisplay.length} | ||
| 961 | {hiddenModelCount > 0 ? ` · ${hiddenModelCount} hidden` : ""} | ||
| 962 | </span> | ||
| 969 | </button> | 963 | </button> |
| 970 | ) : null} | 964 | ) : null} |
| 971 | </div> | 965 | </div> |
| @@ -980,76 +974,74 @@ export function ProviderInstanceCard({ · 8 unmodified lines | |||
| 980 | <div | 974 | <div |
| 981 | inert={readOnly} | 975 | inert={readOnly} |
| 982 | aria-disabled={readOnly || undefined} | 976 | aria-disabled={readOnly || undefined} |
| 983 | className={cn("space-y-5 px-4 py-5", readOnly && "opacity-50 select-none")} | 977 | className={cn("px-4 py-4", readOnly && "opacity-50 select-none")} |
| 984 | > | 978 | > |
| 985 | <div> | 979 | <div className={PROVIDER_FIELD_GRID_CLASS_NAME}> |
| 986 | <label htmlFor={`provider-instance-${instanceId}-display-name`} className="block"> | 980 | <label |
| 987 | <span className="text-xs font-medium text-foreground">Display name</span> | 981 | htmlFor={`provider-instance-${instanceId}-display-name`} |
| 982 | className="pt-1.5 text-xs font-medium text-foreground" | ||
| 983 | > | ||
| 984 | Display name | ||
| 985 | </label> | ||
| 986 | <div className="flex min-w-0 flex-wrap items-center gap-3"> | ||
| 988 | <DraftInput | 987 | <DraftInput |
| 989 | id={`provider-instance-${instanceId}-display-name`} | 988 | id={`provider-instance-${instanceId}-display-name`} |
| 990 | className="mt-1.5" | 989 | className="w-44" |
| 991 | value={instance.displayName ?? ""} | 990 | value={instance.displayName ?? ""} |
| 992 | onCommit={updateDisplayName} | 991 | onCommit={updateDisplayName} |
| 993 | placeholder={driverOption?.label ?? "Instance label"} | 992 | placeholder={driverOption?.label ?? "Instance label"} |
| 994 | spellCheck={false} | 993 | spellCheck={false} |
| 995 | /> | 994 | /> |
| 996 | <span className="mt-1 block text-xs text-muted-foreground"> | 995 | <ProviderAccentColorPicker |
| 997 | Optional label shown in the provider list. | 996 | layout="inline" |
| 998 | </span> | 997 | displayName={displayName} |
| 999 | </label> | 998 | value={accentColor} |
| 1000 | </div> | 999 | onCommit={updateAccentColor} |
| 1001 | 1000 | commitDelayMs={120} | |
| 1002 | <div> | 1001 | /> |
| 1003 | <ProviderAccentColorPicker | ||
| 1004 | displayName={displayName} | ||
| 1005 | value={accentColor} | ||
| 1006 | onCommit={updateAccentColor} | ||
| 1007 | commitDelayMs={120} | ||
| 1008 | description="Used to distinguish this instance in picker rails and model lists." | ||
| 1009 | /> | ||
| 1010 | </div> | ||
| 1011 | |||
| 1012 | <div> | ||
| 1013 | {environmentFields.length > 0 ? ( | ||
| 1014 | <div className="mb-4 grid gap-3"> | ||
| 1015 | {environmentFields.map((field) => ( | ||
| 1016 | <ProviderEnvironmentFieldRow | ||
| 1017 | key={field.name} | ||
| 1018 | field={field} | ||
| 1019 | variable={readProviderEnvironmentVariable(instance.environment, field.name)} | ||
| 1020 | idPrefix={`provider-instance-${instanceId}`} | ||
| 1021 | onCommit={updateEnvironmentField} | ||
| 1022 | onRemove={removeEnvironmentField} | ||
| 1023 | /> | ||
| 1024 | ))} | ||
| 1025 | </div> | ||
| 1026 | ) : null} | ||
| 1027 | <ProviderEnvironmentSection | ||
| 1028 | environment={genericEnvironment} | ||
| 1029 | onChange={updateGenericEnvironment} | ||
| 1030 | /> | ||
| 1031 | </div> | ||
| 1032 | |||
| 1033 | {driverOption ? ( | ||
| 1034 | <ProviderSettingsForm | ||
| 1035 | definition={driverOption} | ||
| 1036 | value={instance.config} | ||
| 1037 | idPrefix={`provider-instance-${instanceId}`} | ||
| 1038 | variant="card" | ||
| 1039 | onChange={updateConfig} | ||
| 1040 | /> | ||
| 1041 | ) : null} | ||
| 1042 | |||
| 1043 | {driverOption === undefined ? ( | ||
| 1044 | <div> | ||
| 1045 | <p className="text-xs text-muted-foreground"> | ||
| 1046 | This instance uses a driver ( | ||
| 1047 | <code className="text-foreground">{String(instance.driver)}</code>) that is not | ||
| 1048 | shipped with the current build. Configuration values are preserved but cannot be | ||
| 1049 | edited from this surface. | ||
| 1050 | </p> | ||
| 1051 | </div> | 1002 | </div> |
| 1052 | ) : null} | 1003 | <div className={PROVIDER_FIELD_GROUP_LABEL_CLASS_NAME}>Runtime</div> |
| 1004 | {driverOption ? ( | ||
| 1005 | <ProviderSettingsForm | ||
| 1006 | definition={driverOption} | ||
| 1007 | value={instance.config} | ||
| 1008 | idPrefix={`provider-instance-${instanceId}`} | ||
| 1009 | variant="grid" | ||
| 1010 | onChange={updateConfig} | ||
| 1011 | /> | ||
| 1012 | ) : ( | ||
| 1013 | <> | ||
| 1014 | <span className="pt-1.5 text-xs font-medium text-foreground">Driver</span> | ||
| 1015 | <p className="text-xs text-muted-foreground"> | ||
| 1016 | This instance uses a driver ( | ||
| 1017 | <code className="text-foreground">{String(instance.driver)}</code>) that is not | ||
| 1018 | shipped with the current build. Configuration values are preserved but cannot be | ||
| 1019 | edited from this surface. | ||
| 1020 | </p> | ||
| 1021 | </> | ||
| 1022 | )} | ||
| 1023 | <div className={PROVIDER_FIELD_GROUP_LABEL_CLASS_NAME}>Environment</div> | ||
| 1024 | <div className="col-span-full"> | ||
| 1025 | {environmentFields.length > 0 ? ( | ||
| 1026 | <div className="mb-4 grid gap-3"> | ||
| 1027 | {environmentFields.map((field) => ( | ||
| 1028 | <ProviderEnvironmentFieldRow | ||
| 1029 | key={field.name} | ||
| 1030 | field={field} | ||
| 1031 | variable={readProviderEnvironmentVariable(instance.environment, field.name)} | ||
| 1032 | idPrefix={`provider-instance-${instanceId}`} | ||
| 1033 | onCommit={updateEnvironmentField} | ||
| 1034 | onRemove={removeEnvironmentField} | ||
| 1035 | /> | ||
| 1036 | ))} | ||
| 1037 | </div> | ||
| 1038 | ) : null} | ||
| 1039 | <ProviderEnvironmentSection | ||
| 1040 | environment={genericEnvironment} | ||
| 1041 | onChange={updateGenericEnvironment} | ||
| 1042 | /> | ||
| 1043 | </div> | ||
| 1044 | </div> | ||
| 1053 | </div> | 1045 | </div> |
| 1054 | </ScrollArea> | 1046 | </ScrollArea> |
| 1055 | {driverOption !== undefined ? ( | 1047 | {driverOption !== undefined ? ( |
| @@ -1821,8 +1821,8 @@ code { · 1820 unmodified lines | |||
| 1821 | } | 1821 | } |
| 1822 | 1822 | ||
| 1823 | .chat-markdown ul { | 1823 | .chat-markdown ul { |
| 1824 | /* Reset for nested uls under a widened ol — --list-gutter is an inherited | 1824 | /* Reset for nested uls under a widened ol. --list-gutter is an inherited |
| 1825 | custom property, so without this a task-list under a 3+ digit ordered | 1825 | custom property, so without this a task-list under a multi-digit ordered |
| 1826 | list would inherit the outer gutter instead of its own default. */ | 1826 | list would inherit the outer gutter instead of its own default. */ |
| 1827 | --list-gutter: 1.25rem; | 1827 | --list-gutter: 1.25rem; |
| 1828 | padding-left: 1.25rem; | 1828 | padding-left: 1.25rem; |
| @@ -1831,7 +1831,7 @@ code { · 2 unmodified lines | |||
| 1831 | 1831 | ||
| 1832 | /* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but | 1832 | /* --list-gutter defaults to the same 1.25rem as .chat-markdown ul, but |
| 1833 | ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose | 1833 | ChatMarkdown's `ol` renderer widens it (via inline style) for lists whose |
| 1834 | widest marker is 3+ characters, so item 100+ isn't clipped by list-style-position: | 1834 | widest marker has multiple characters so it isn't clipped by list-style-position: |
| 1835 | outside painting the marker past the padding box. Reset it here too so a | 1835 | outside painting the marker past the padding box. Reset it here too so a |
| 1836 | nested ol without its own widened marker doesn't inherit the outer one. */ | 1836 | nested ol without its own widened marker doesn't inherit the outer one. */ |
| 1837 | .chat-markdown ol { | 1837 | .chat-markdown ol { |
| @@ -208,6 +208,9 @@ export const ClientSettingsSchema = Schema.Struct({ · 207 unmodified lines | |||
| 208 | confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 208 | confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 209 | confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | 209 | confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| 210 | confirmThreadUnpin: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 210 | confirmThreadUnpin: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 211 | continueThreadsAfterServerUpdate: Schema.Boolean.pipe( | ||
| 212 | Schema.withDecodingDefault(Effect.succeed(false)), | ||
| 213 | ), | ||
| 211 | dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe( | 214 | dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe( |
| 212 | Schema.withDecodingDefault(Effect.succeed([])), | 215 | Schema.withDecodingDefault(Effect.succeed([])), |
| 213 | ), | 216 | ), |
| @@ -269,6 +272,9 @@ export const ClientSettingsSchema = Schema.Struct({ · 55 unmodified lines | |||
| 269 | // default UI; this beta flag restores it (plus the /plan and /default slash | 272 | // default UI; this beta flag restores it (plus the /plan and /default slash |
| 270 | // commands) for users who still rely on the old workflow. | 273 | // commands) for users who still rely on the old workflow. |
| 271 | planModeEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | 274 | planModeEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), |
| 275 | // Legacy context window meter. The composer hides it by default; users who | ||
| 276 | // still want the old usage indicator can restore it from Settings. | ||
| 277 | contextWindowMeterEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), | ||
| 272 | showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), | 278 | showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), |
| 273 | // Legacy sidebar (the original per-project tree). Deliberately a fresh key | 279 | // Legacy sidebar (the original per-project tree). Deliberately a fresh key |
| 274 | // (was `sidebarV2Enabled` + `sidebarV2ConfiguredByUser`): decoding drops the | 280 | // (was `sidebarV2Enabled` + `sidebarV2ConfiguredByUser`): decoding drops the |
| @@ -1009,6 +1015,7 @@ export const ClientSettingsPatch = Schema.Struct({ · 734 unmodified lines | |||
| 1009 | confirmThreadArchive: Schema.optionalKey(Schema.Boolean), | 1015 | confirmThreadArchive: Schema.optionalKey(Schema.Boolean), |
| 1010 | confirmThreadDelete: Schema.optionalKey(Schema.Boolean), | 1016 | confirmThreadDelete: Schema.optionalKey(Schema.Boolean), |
| 1011 | confirmThreadUnpin: Schema.optionalKey(Schema.Boolean), | 1017 | confirmThreadUnpin: Schema.optionalKey(Schema.Boolean), |
| 1018 | continueThreadsAfterServerUpdate: Schema.optionalKey(Schema.Boolean), | ||
| 1012 | diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean), | 1019 | diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean), |
| 1013 | environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode), | 1020 | environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode), |
| 1014 | glassOpacity: Schema.optionalKey(GlassOpacity), | 1021 | glassOpacity: Schema.optionalKey(GlassOpacity), |
| @@ -1044,6 +1051,7 @@ export const ClientSettingsPatch = Schema.Struct({ · 29 unmodified lines | |||
| 1044 | ), | 1051 | ), |
| 1045 | ), | 1052 | ), |
| 1046 | planModeEnabled: Schema.optionalKey(Schema.Boolean), | 1053 | planModeEnabled: Schema.optionalKey(Schema.Boolean), |
| 1054 | contextWindowMeterEnabled: Schema.optionalKey(Schema.Boolean), | ||
| 1047 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), | 1055 | showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean), |
| 1048 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), | 1056 | legacySidebarEnabled: Schema.optionalKey(Schema.Boolean), |
| 1049 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), | 1057 | sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode), |
| @@ -3,6 +3,7 @@ import { ProviderDriverKind, ProviderInstanceId, type ModelCapabilities } from " · 2 unmodified lines | |||
| 3 | 3 | ||
| 4 | import { | 4 | import { |
| 5 | applyClaudePromptEffortPrefix, | 5 | applyClaudePromptEffortPrefix, |
| 6 | buildExplicitProviderOptionSelectionsFromDescriptors, | ||
| 6 | buildProviderOptionSelectionsFromDescriptors, | 7 | buildProviderOptionSelectionsFromDescriptors, |
| 7 | createModelCapabilities, | 8 | createModelCapabilities, |
| 8 | createModelSelection, | 9 | createModelSelection, |
| @@ -127,6 +128,22 @@ describe("descriptor helpers", () => { · 118 unmodified lines | |||
| 127 | ]); | 128 | ]); |
| 128 | }); | 129 | }); |
| 129 | 130 | ||
| 131 | it("builds dispatch options only from explicit selections", () => { | ||
| 132 | const descriptors = getProviderOptionDescriptors({ | ||
| 133 | caps: codexCaps, | ||
| 134 | selections: [{ id: "fastMode", value: true }], | ||
| 135 | }); | ||
| 136 | |||
| 137 | expect(buildExplicitProviderOptionSelectionsFromDescriptors(descriptors, undefined)).toBe( | ||
| 138 | undefined, | ||
| 139 | ); | ||
| 140 | expect( | ||
| 141 | buildExplicitProviderOptionSelectionsFromDescriptors(descriptors, [ | ||
| 142 | { id: "fastMode", value: true }, | ||
| 143 | ]), | ||
| 144 | ).toEqual([{ id: "fastMode", value: true }]); | ||
| 145 | }); | ||
| 146 | |||
| 130 | it("stores option selection arrays in model selections", () => { | 147 | it("stores option selection arrays in model selections", () => { |
| 131 | expect( | 148 | expect( |
| 132 | createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [ | 149 | createModelSelection(ProviderInstanceId.make("codex"), "gpt-5.4", [ |
| @@ -472,7 +472,7 @@ importers: · 471 unmodified lines | |||
| 472 | dependencies: | 472 | dependencies: |
| 473 | '@anthropic-ai/claude-agent-sdk': | 473 | '@anthropic-ai/claude-agent-sdk': |
| 474 | specifier: ^0.3.205 | 474 | specifier: ^0.3.205 |
| 475 | version: 0.3.252(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) | 475 | version: 0.3.257(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) |
| 476 | '@connectrpc/connect': | 476 | '@connectrpc/connect': |
| 477 | specifier: 1.7.0 | 477 | specifier: 1.7.0 |
| 478 | version: 1.7.0(@bufbuild/protobuf@1.10.0) | 478 | version: 1.7.0(@bufbuild/protobuf@1.10.0) |
| @@ -1012,8 +1012,8 @@ packages: · 533 unmodified lines | |||
| 1012 | '@alchemy.run/node-utils@0.0.5': | 1012 | '@alchemy.run/node-utils@0.0.5': |
| 1013 | resolution: {integrity: sha512-5agdhQxWBodxa5hDRyjnpx91RTU3g+qd5fxYB7uNDCaOzB0XC47UU/KHR6zf6jhz/NXf61gJS+vhYwn8NHeRoQ==} | 1013 | resolution: {integrity: sha512-5agdhQxWBodxa5hDRyjnpx91RTU3g+qd5fxYB7uNDCaOzB0XC47UU/KHR6zf6jhz/NXf61gJS+vhYwn8NHeRoQ==} |
| 1014 | 1014 | ||
| 1015 | '@anthropic-ai/claude-agent-sdk@0.3.252': | 1015 | '@anthropic-ai/claude-agent-sdk@0.3.257': |
| 1016 | resolution: {integrity: sha512-hCkyZFn3J46aAMNqS6AZbYz91FaLUmX5VvJOzYZqzlVBJN47OxXQugqOzqa6b6GOZRmwiqW2ck8J8TE7bQZswQ==} | 1016 | resolution: {integrity: sha512-Se55zXv48IYLg/WzoXzpbPLcq86suwDSbRUoNb69l4dkovorqS/47Xuy7MUo/gPNwwcPB4a+aqbXbshU33dcdQ==} |
| 1017 | engines: {node: '>=18.0.0'} | 1017 | engines: {node: '>=18.0.0'} |
| 1018 | peerDependencies: | 1018 | peerDependencies: |
| 1019 | '@anthropic-ai/sdk': '>=0.93.0' | 1019 | '@anthropic-ai/sdk': '>=0.93.0' |
| @@ -10540,7 +10540,7 @@ snapshots: · 9520 unmodified lines | |||
| 10540 | 10540 | ||
| 10541 | '@alchemy.run/node-utils@0.0.5': {} | 10541 | '@alchemy.run/node-utils@0.0.5': {} |
| 10542 | 10542 | ||
| 10543 | '@anthropic-ai/claude-agent-sdk@0.3.252(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': | 10543 | '@anthropic-ai/claude-agent-sdk@0.3.257(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3)': |
| 10544 | dependencies: | 10544 | dependencies: |
| 10545 | '@anthropic-ai/sdk': 0.93.0(zod@4.4.3) | 10545 | '@anthropic-ai/sdk': 0.93.0(zod@4.4.3) |
| 10546 | '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) | 10546 | '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) |