| ... | ... | @@ -241,6 +241,32 @@ interface ClaudeTaskAgentState { |
| 241 | 241 | effort: string | undefined; |
| 242 | 242 | } |
| 243 | 243 | |
| 244 | /** |
| 245 | * How many racing snapshot models to buffer per session. A snapshot whose |
| 246 | * task_started never arrives would otherwise pin its entry for the session's |
| 247 | * lifetime; oldest entries evict first. |
| 248 | */ |
| 249 | const PENDING_TASK_MODEL_CAP = 64; |
| 250 | |
| 251 | /** |
| 252 | * Buffers a subagent snapshot's authoritative model under its |
| 253 | * parent_tool_use_id, for snapshots that beat their task_started to the |
| 254 | * stream. task_started consumes the entry when it registers the task. |
| 255 | */ |
| 256 | function rememberPendingTaskModel( |
| 257 | pending: Map<string, string>, |
| 258 | parentToolUseId: string, |
| 259 | model: string, |
| 260 | ): void { |
| 261 | pending.set(parentToolUseId, model); |
| 262 | if (pending.size > PENDING_TASK_MODEL_CAP) { |
| 263 | const oldest = pending.keys().next(); |
| 264 | if (!oldest.done) { |
| 265 | pending.delete(oldest.value); |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | |
| 244 | 270 | interface ClaudeSessionContext { |
| 245 | 271 | session: ProviderSession; |
| 246 | 272 | readonly promptQueue: Queue.Queue<PromptQueueItem>; |
| ... | ... | @@ -262,6 +288,12 @@ interface ClaudeSessionContext { |
| 262 | 288 | readonly inFlightTools: Map<number, ToolInFlight>; |
| 263 | 289 | readonly claudeTasks: Map<string, ClaudeTaskState>; |
| 264 | 290 | readonly taskAgents: Map<string, ClaudeTaskAgentState>; |
| 291 | /** |
| 292 | * Authoritative subagent models from assistant snapshots that arrived before |
| 293 | * their task_started registered the task, keyed by parent_tool_use_id. |
| 294 | * Written through `rememberPendingTaskModel`, consumed by task_started. |
| 295 | */ |
| 296 | readonly pendingTaskModels: Map<string, string>; |
| 265 | 297 | /** |
| 266 | 298 | * Last emitted workflow-member fingerprint per member slot. A coordinator |
| 267 | 299 | * task_progress repeats the FULL member array every tick; without a |
| ... | ... | @@ -2875,8 +2907,18 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 2875 | 2907 | const owningTaskId = agentIdForParentToolUse(context.taskAgents, assistantParentToolUseId); |
| 2876 | 2908 | const snapshotModel = trimmedString(message.message.model); |
| 2877 | 2909 | const owningAgent = owningTaskId ? context.taskAgents.get(owningTaskId) : undefined; |
| 2878 | | if (owningAgent && snapshotModel) { |
| 2879 | | owningAgent.model = snapshotModel; |
| 2910 | if (snapshotModel) { |
| 2911 | if (owningAgent) { |
| 2912 | owningAgent.model = snapshotModel; |
| 2913 | } else { |
| 2914 | // The snapshot beat its task_started (or its tool_use_id was never |
| 2915 | // recorded): hold the model until the task registers. |
| 2916 | rememberPendingTaskModel( |
| 2917 | context.pendingTaskModels, |
| 2918 | assistantParentToolUseId, |
| 2919 | snapshotModel, |
| 2920 | ); |
| 2921 | } |
| 2880 | 2922 | } |
| 2881 | 2923 | context.lastAssistantUuid = message.uuid; |
| 2882 | 2924 | yield* updateResumeCursor(context); |
| ... | ... | @@ -3190,12 +3232,20 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 3190 | 3232 | const owningAgentId = launchingTool?.agentId; |
| 3191 | 3233 | // Model/effort: the Agent tool's input carries explicit overrides; |
| 3192 | 3234 | // absent ones inherit the session's selection (SDK behavior). |
| 3193 | | // Subagent assistant snapshots later refine model with the |
| 3194 | | // authoritative API id. AgentInput.effort may be a named level or an |
| 3195 | | // integer. |
| 3235 | // Subagent assistant snapshots refine model with the authoritative API |
| 3236 | // id: one that already arrived is buffered and outranks the seed here, |
| 3237 | // later ones refine the record in place. AgentInput.effort may be a |
| 3238 | // named level or an integer. |
| 3196 | 3239 | const launchInput = launchingTool?.input; |
| 3240 | const toolUseId = message.tool_use_id; |
| 3241 | const bufferedModel = toolUseId ? context.pendingTaskModels.get(toolUseId) : undefined; |
| 3242 | if (toolUseId) { |
| 3243 | context.pendingTaskModels.delete(toolUseId); |
| 3244 | } |
| 3197 | 3245 | const model = |
| 3198 | | trimmedString(launchInput?.model) ?? trimmedString(context.session.model ?? undefined); |
| 3246 | bufferedModel ?? |
| 3247 | trimmedString(launchInput?.model) ?? |
| 3248 | trimmedString(context.session.model ?? undefined); |
| 3199 | 3249 | const rawLaunchEffort = launchInput?.effort; |
| 3200 | 3250 | const effort = |
| 3201 | 3251 | trimmedString(rawLaunchEffort) ?? |
| ... | ... | @@ -3807,6 +3857,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 3807 | 3857 | const inFlightTools = new Map<number, ToolInFlight>(); |
| 3808 | 3858 | const claudeTasks = new Map<string, ClaudeTaskState>(); |
| 3809 | 3859 | const taskAgents = new Map<string, ClaudeTaskAgentState>(); |
| 3860 | const pendingTaskModels = new Map<string, string>(); |
| 3810 | 3861 | const workflowMemberFingerprints = new Map<string, string>(); |
| 3811 | 3862 | const liveTaskIds = new Set<string>(); |
| 3812 | 3863 | |
| ... | ... | @@ -4270,6 +4321,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 4270 | 4321 | inFlightTools, |
| 4271 | 4322 | claudeTasks, |
| 4272 | 4323 | taskAgents, |
| 4324 | pendingTaskModels, |
| 4273 | 4325 | workflowMemberFingerprints, |
| 4274 | 4326 | liveTaskIds, |
| 4275 | 4327 | turnState: undefined, |