yzcode-cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/assistant/sessionHistory.ts +87 -0
  2. package/bootstrap/state.ts +1769 -0
  3. package/bridge/bridgeApi.ts +539 -0
  4. package/bridge/bridgeConfig.ts +48 -0
  5. package/bridge/bridgeDebug.ts +135 -0
  6. package/bridge/bridgeEnabled.ts +202 -0
  7. package/bridge/bridgeMain.ts +2999 -0
  8. package/bridge/bridgeMessaging.ts +461 -0
  9. package/bridge/bridgePermissionCallbacks.ts +43 -0
  10. package/bridge/bridgePointer.ts +210 -0
  11. package/bridge/bridgeStatusUtil.ts +163 -0
  12. package/bridge/bridgeUI.ts +530 -0
  13. package/bridge/capacityWake.ts +56 -0
  14. package/bridge/codeSessionApi.ts +168 -0
  15. package/bridge/createSession.ts +384 -0
  16. package/bridge/debugUtils.ts +141 -0
  17. package/bridge/envLessBridgeConfig.ts +165 -0
  18. package/bridge/flushGate.ts +71 -0
  19. package/bridge/inboundAttachments.ts +175 -0
  20. package/bridge/inboundMessages.ts +80 -0
  21. package/bridge/initReplBridge.ts +569 -0
  22. package/bridge/jwtUtils.ts +256 -0
  23. package/bridge/pollConfig.ts +110 -0
  24. package/bridge/pollConfigDefaults.ts +82 -0
  25. package/bridge/remoteBridgeCore.ts +1008 -0
  26. package/bridge/replBridge.ts +2406 -0
  27. package/bridge/replBridgeHandle.ts +36 -0
  28. package/bridge/replBridgeTransport.ts +370 -0
  29. package/bridge/sessionIdCompat.ts +57 -0
  30. package/bridge/sessionRunner.ts +550 -0
  31. package/bridge/trustedDevice.ts +210 -0
  32. package/bridge/types.ts +262 -0
  33. package/bridge/workSecret.ts +127 -0
  34. package/buddy/CompanionSprite.tsx +371 -0
  35. package/buddy/companion.ts +133 -0
  36. package/buddy/prompt.ts +36 -0
  37. package/buddy/sprites.ts +514 -0
  38. package/buddy/types.ts +148 -0
  39. package/buddy/useBuddyNotification.tsx +98 -0
  40. package/coordinator/coordinatorMode.ts +369 -0
  41. package/memdir/findRelevantMemories.ts +141 -0
  42. package/memdir/memdir.ts +507 -0
  43. package/memdir/memoryAge.ts +53 -0
  44. package/memdir/memoryScan.ts +94 -0
  45. package/memdir/memoryTypes.ts +271 -0
  46. package/memdir/paths.ts +278 -0
  47. package/memdir/teamMemPaths.ts +292 -0
  48. package/memdir/teamMemPrompts.ts +100 -0
  49. package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
  50. package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
  51. package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
  52. package/migrations/migrateFennecToOpus.ts +45 -0
  53. package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
  54. package/migrations/migrateOpusToOpus1m.ts +43 -0
  55. package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
  56. package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
  57. package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
  58. package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
  59. package/migrations/resetProToOpusDefault.ts +51 -0
  60. package/native-ts/color-diff/index.ts +999 -0
  61. package/native-ts/file-index/index.ts +370 -0
  62. package/native-ts/yoga-layout/enums.ts +134 -0
  63. package/native-ts/yoga-layout/index.ts +2578 -0
  64. package/outputStyles/loadOutputStylesDir.ts +98 -0
  65. package/package.json +22 -5
  66. package/plugins/builtinPlugins.ts +159 -0
  67. package/plugins/bundled/index.ts +23 -0
  68. package/schemas/hooks.ts +222 -0
  69. package/screens/Doctor.tsx +575 -0
  70. package/screens/REPL.tsx +5006 -0
  71. package/screens/ResumeConversation.tsx +399 -0
  72. package/server/createDirectConnectSession.ts +88 -0
  73. package/server/directConnectManager.ts +213 -0
  74. package/server/types.ts +57 -0
  75. package/skills/bundled/batch.ts +124 -0
  76. package/skills/bundled/claudeApi.ts +196 -0
  77. package/skills/bundled/claudeApiContent.ts +75 -0
  78. package/skills/bundled/claudeInChrome.ts +34 -0
  79. package/skills/bundled/debug.ts +103 -0
  80. package/skills/bundled/index.ts +79 -0
  81. package/skills/bundled/keybindings.ts +339 -0
  82. package/skills/bundled/loop.ts +92 -0
  83. package/skills/bundled/loremIpsum.ts +282 -0
  84. package/skills/bundled/remember.ts +82 -0
  85. package/skills/bundled/scheduleRemoteAgents.ts +447 -0
  86. package/skills/bundled/simplify.ts +69 -0
  87. package/skills/bundled/skillify.ts +197 -0
  88. package/skills/bundled/stuck.ts +79 -0
  89. package/skills/bundled/updateConfig.ts +475 -0
  90. package/skills/bundled/verify/SKILL.md +3 -0
  91. package/skills/bundled/verify/examples/cli.md +3 -0
  92. package/skills/bundled/verify/examples/server.md +3 -0
  93. package/skills/bundled/verify.ts +30 -0
  94. package/skills/bundled/verifyContent.ts +13 -0
  95. package/skills/bundledSkills.ts +220 -0
  96. package/skills/loadSkillsDir.ts +1086 -0
  97. package/skills/mcpSkillBuilders.ts +44 -0
  98. package/tasks/DreamTask/DreamTask.ts +157 -0
  99. package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
  100. package/tasks/InProcessTeammateTask/types.ts +121 -0
  101. package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
  102. package/tasks/LocalMainSessionTask.ts +479 -0
  103. package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
  104. package/tasks/LocalShellTask/guards.ts +41 -0
  105. package/tasks/LocalShellTask/killShellTasks.ts +76 -0
  106. package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
  107. package/tasks/pillLabel.ts +82 -0
  108. package/tasks/stopTask.ts +100 -0
  109. package/tasks/types.ts +46 -0
  110. package/upstreamproxy/relay.ts +455 -0
  111. package/upstreamproxy/upstreamproxy.ts +285 -0
  112. package/vim/motions.ts +82 -0
  113. package/vim/operators.ts +556 -0
  114. package/vim/textObjects.ts +186 -0
  115. package/vim/transitions.ts +490 -0
  116. package/vim/types.ts +199 -0
  117. package/voice/voiceModeEnabled.ts +54 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * LocalMainSessionTask - Handles backgrounding the main session query.
3
+ *
4
+ * When user presses Ctrl+B twice during a query, the session is "backgrounded":
5
+ * - The query continues running in the background
6
+ * - The UI clears to a fresh prompt
7
+ * - A notification is sent when the query completes
8
+ *
9
+ * This reuses the LocalAgentTask state structure since the behavior is similar.
10
+ */
11
+
12
+ import type { UUID } from 'crypto'
13
+ import { randomBytes } from 'crypto'
14
+ import {
15
+ OUTPUT_FILE_TAG,
16
+ STATUS_TAG,
17
+ SUMMARY_TAG,
18
+ TASK_ID_TAG,
19
+ TASK_NOTIFICATION_TAG,
20
+ TOOL_USE_ID_TAG,
21
+ } from '../constants/xml.js'
22
+ import { type QueryParams, query } from '../query.js'
23
+ import { roughTokenCountEstimation } from '../services/tokenEstimation.js'
24
+ import type { SetAppState } from '../Task.js'
25
+ import { createTaskStateBase } from '../Task.js'
26
+ import type {
27
+ AgentDefinition,
28
+ CustomAgentDefinition,
29
+ } from '../tools/AgentTool/loadAgentsDir.js'
30
+ import { asAgentId } from '../types/ids.js'
31
+ import type { Message } from '../types/message.js'
32
+ import { createAbortController } from '../utils/abortController.js'
33
+ import {
34
+ runWithAgentContext,
35
+ type SubagentContext,
36
+ } from '../utils/agentContext.js'
37
+ import { registerCleanup } from '../utils/cleanupRegistry.js'
38
+ import { logForDebugging } from '../utils/debug.js'
39
+ import { logError } from '../utils/log.js'
40
+ import { enqueuePendingNotification } from '../utils/messageQueueManager.js'
41
+ import { emitTaskTerminatedSdk } from '../utils/sdkEventQueue.js'
42
+ import {
43
+ getAgentTranscriptPath,
44
+ recordSidechainTranscript,
45
+ } from '../utils/sessionStorage.js'
46
+ import {
47
+ evictTaskOutput,
48
+ getTaskOutputPath,
49
+ initTaskOutputAsSymlink,
50
+ } from '../utils/task/diskOutput.js'
51
+ import { registerTask, updateTaskState } from '../utils/task/framework.js'
52
+ import type { LocalAgentTaskState } from './LocalAgentTask/LocalAgentTask.js'
53
+
54
+ // Main session tasks use LocalAgentTaskState with agentType='main-session'
55
+ export type LocalMainSessionTaskState = LocalAgentTaskState & {
56
+ agentType: 'main-session'
57
+ }
58
+
59
+ /**
60
+ * Default agent definition for main session tasks when no agent is specified.
61
+ */
62
+ const DEFAULT_MAIN_SESSION_AGENT: CustomAgentDefinition = {
63
+ agentType: 'main-session',
64
+ whenToUse: 'Main session query',
65
+ source: 'userSettings',
66
+ getSystemPrompt: () => '',
67
+ }
68
+
69
+ /**
70
+ * Generate a unique task ID for main session tasks.
71
+ * Uses 's' prefix to distinguish from agent tasks ('a' prefix).
72
+ */
73
+ const TASK_ID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyz'
74
+
75
+ function generateMainSessionTaskId(): string {
76
+ const bytes = randomBytes(8)
77
+ let id = 's'
78
+ for (let i = 0; i < 8; i++) {
79
+ id += TASK_ID_ALPHABET[bytes[i]! % TASK_ID_ALPHABET.length]
80
+ }
81
+ return id
82
+ }
83
+
84
+ /**
85
+ * Register a backgrounded main session task.
86
+ * Called when the user backgrounds the current session query.
87
+ *
88
+ * @param description - Description of the task
89
+ * @param setAppState - State setter function
90
+ * @param mainThreadAgentDefinition - Optional agent definition if running with --agent
91
+ * @param existingAbortController - Optional abort controller to reuse (for backgrounding an active query)
92
+ * @returns Object with task ID and abort signal for stopping the background query
93
+ */
94
+ export function registerMainSessionTask(
95
+ description: string,
96
+ setAppState: SetAppState,
97
+ mainThreadAgentDefinition?: AgentDefinition,
98
+ existingAbortController?: AbortController,
99
+ ): { taskId: string; abortSignal: AbortSignal } {
100
+ const taskId = generateMainSessionTaskId()
101
+
102
+ // Link output to an isolated per-task transcript file (same layout as
103
+ // sub-agents). Do NOT use getTranscriptPath() — that's the main session's
104
+ // file, and writing there from a background query after /clear would corrupt
105
+ // the post-clear conversation. The isolated path lets this task survive
106
+ // /clear: the symlink re-link in clearConversation handles session ID changes.
107
+ void initTaskOutputAsSymlink(
108
+ taskId,
109
+ getAgentTranscriptPath(asAgentId(taskId)),
110
+ )
111
+
112
+ // Use the existing abort controller if provided (important for backgrounding an active query)
113
+ // This ensures that aborting the task will abort the actual query
114
+ const abortController = existingAbortController ?? createAbortController()
115
+
116
+ const unregisterCleanup = registerCleanup(async () => {
117
+ // Clean up on process exit
118
+ setAppState(prev => {
119
+ const { [taskId]: removed, ...rest } = prev.tasks
120
+ return { ...prev, tasks: rest }
121
+ })
122
+ })
123
+
124
+ // Use provided agent definition or default
125
+ const selectedAgent = mainThreadAgentDefinition ?? DEFAULT_MAIN_SESSION_AGENT
126
+
127
+ // Create task state - already backgrounded since this is called when user backgrounds
128
+ const taskState: LocalMainSessionTaskState = {
129
+ ...createTaskStateBase(taskId, 'local_agent', description),
130
+ type: 'local_agent',
131
+ status: 'running',
132
+ agentId: taskId,
133
+ prompt: description,
134
+ selectedAgent,
135
+ agentType: 'main-session',
136
+ abortController,
137
+ unregisterCleanup,
138
+ retrieved: false,
139
+ lastReportedToolCount: 0,
140
+ lastReportedTokenCount: 0,
141
+ isBackgrounded: true, // Already backgrounded
142
+ pendingMessages: [],
143
+ retain: false,
144
+ diskLoaded: false,
145
+ }
146
+
147
+ logForDebugging(
148
+ `[LocalMainSessionTask] Registering task ${taskId} with description: ${description}`,
149
+ )
150
+ registerTask(taskState, setAppState)
151
+
152
+ // Verify task was registered by checking state
153
+ setAppState(prev => {
154
+ const hasTask = taskId in prev.tasks
155
+ logForDebugging(
156
+ `[LocalMainSessionTask] After registration, task ${taskId} exists in state: ${hasTask}`,
157
+ )
158
+ return prev
159
+ })
160
+
161
+ return { taskId, abortSignal: abortController.signal }
162
+ }
163
+
164
+ /**
165
+ * Complete the main session task and send notification.
166
+ * Called when the backgrounded query finishes.
167
+ */
168
+ export function completeMainSessionTask(
169
+ taskId: string,
170
+ success: boolean,
171
+ setAppState: SetAppState,
172
+ ): void {
173
+ let wasBackgrounded = true
174
+ let toolUseId: string | undefined
175
+
176
+ updateTaskState<LocalMainSessionTaskState>(taskId, setAppState, task => {
177
+ if (task.status !== 'running') {
178
+ return task
179
+ }
180
+
181
+ // Track if task was backgrounded (for notification decision)
182
+ wasBackgrounded = task.isBackgrounded ?? true
183
+ toolUseId = task.toolUseId
184
+
185
+ task.unregisterCleanup?.()
186
+
187
+ return {
188
+ ...task,
189
+ status: success ? 'completed' : 'failed',
190
+ endTime: Date.now(),
191
+ messages: task.messages?.length ? [task.messages.at(-1)!] : undefined,
192
+ }
193
+ })
194
+
195
+ void evictTaskOutput(taskId)
196
+
197
+ // Only send notification if task is still backgrounded (not foregrounded)
198
+ // If foregrounded, user is watching it directly - no notification needed
199
+ if (wasBackgrounded) {
200
+ enqueueMainSessionNotification(
201
+ taskId,
202
+ 'Background session',
203
+ success ? 'completed' : 'failed',
204
+ setAppState,
205
+ toolUseId,
206
+ )
207
+ } else {
208
+ // Foregrounded: no XML notification (TUI user is watching), but SDK
209
+ // consumers still need to see the task_started bookend close.
210
+ // Set notified so evictTerminalTask/generateTaskAttachments eviction
211
+ // guards pass; the backgrounded path sets this inside
212
+ // enqueueMainSessionNotification's check-and-set.
213
+ updateTaskState(taskId, setAppState, task => ({ ...task, notified: true }))
214
+ emitTaskTerminatedSdk(taskId, success ? 'completed' : 'failed', {
215
+ toolUseId,
216
+ summary: 'Background session',
217
+ })
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Enqueue a notification about the backgrounded session completing.
223
+ */
224
+ function enqueueMainSessionNotification(
225
+ taskId: string,
226
+ description: string,
227
+ status: 'completed' | 'failed',
228
+ setAppState: SetAppState,
229
+ toolUseId?: string,
230
+ ): void {
231
+ // Atomically check and set notified flag to prevent duplicate notifications.
232
+ let shouldEnqueue = false
233
+ updateTaskState(taskId, setAppState, task => {
234
+ if (task.notified) {
235
+ return task
236
+ }
237
+ shouldEnqueue = true
238
+ return { ...task, notified: true }
239
+ })
240
+
241
+ if (!shouldEnqueue) {
242
+ return
243
+ }
244
+
245
+ const summary =
246
+ status === 'completed'
247
+ ? `Background session "${description}" completed`
248
+ : `Background session "${description}" failed`
249
+
250
+ const toolUseIdLine = toolUseId
251
+ ? `\n<${TOOL_USE_ID_TAG}>${toolUseId}</${TOOL_USE_ID_TAG}>`
252
+ : ''
253
+
254
+ const outputPath = getTaskOutputPath(taskId)
255
+ const message = `<${TASK_NOTIFICATION_TAG}>
256
+ <${TASK_ID_TAG}>${taskId}</${TASK_ID_TAG}>${toolUseIdLine}
257
+ <${OUTPUT_FILE_TAG}>${outputPath}</${OUTPUT_FILE_TAG}>
258
+ <${STATUS_TAG}>${status}</${STATUS_TAG}>
259
+ <${SUMMARY_TAG}>${summary}</${SUMMARY_TAG}>
260
+ </${TASK_NOTIFICATION_TAG}>`
261
+
262
+ enqueuePendingNotification({ value: message, mode: 'task-notification' })
263
+ }
264
+
265
+ /**
266
+ * Foreground a main session task - mark it as foregrounded so its output
267
+ * appears in the main view. The background query keeps running.
268
+ * Returns the task's accumulated messages, or undefined if task not found.
269
+ */
270
+ export function foregroundMainSessionTask(
271
+ taskId: string,
272
+ setAppState: SetAppState,
273
+ ): Message[] | undefined {
274
+ let taskMessages: Message[] | undefined
275
+
276
+ setAppState(prev => {
277
+ const task = prev.tasks[taskId]
278
+ if (!task || task.type !== 'local_agent') {
279
+ return prev
280
+ }
281
+
282
+ taskMessages = (task as LocalMainSessionTaskState).messages
283
+
284
+ // Restore previous foregrounded task to background if it exists
285
+ const prevId = prev.foregroundedTaskId
286
+ const prevTask = prevId ? prev.tasks[prevId] : undefined
287
+ const restorePrev =
288
+ prevId && prevId !== taskId && prevTask?.type === 'local_agent'
289
+
290
+ return {
291
+ ...prev,
292
+ foregroundedTaskId: taskId,
293
+ tasks: {
294
+ ...prev.tasks,
295
+ ...(restorePrev && { [prevId]: { ...prevTask, isBackgrounded: true } }),
296
+ [taskId]: { ...task, isBackgrounded: false },
297
+ },
298
+ }
299
+ })
300
+
301
+ return taskMessages
302
+ }
303
+
304
+ /**
305
+ * Check if a task is a main session task (vs a regular agent task).
306
+ */
307
+ export function isMainSessionTask(
308
+ task: unknown,
309
+ ): task is LocalMainSessionTaskState {
310
+ if (
311
+ typeof task !== 'object' ||
312
+ task === null ||
313
+ !('type' in task) ||
314
+ !('agentType' in task)
315
+ ) {
316
+ return false
317
+ }
318
+ return (
319
+ task.type === 'local_agent' &&
320
+ (task as LocalMainSessionTaskState).agentType === 'main-session'
321
+ )
322
+ }
323
+
324
+ // Max recent activities to keep for display
325
+ const MAX_RECENT_ACTIVITIES = 5
326
+
327
+ type ToolActivity = {
328
+ toolName: string
329
+ input: Record<string, unknown>
330
+ }
331
+
332
+ /**
333
+ * Start a fresh background session with the given messages.
334
+ *
335
+ * Spawns an independent query() call with the current messages and registers it
336
+ * as a background task. The caller's foreground query continues running normally.
337
+ */
338
+ export function startBackgroundSession({
339
+ messages,
340
+ queryParams,
341
+ description,
342
+ setAppState,
343
+ agentDefinition,
344
+ }: {
345
+ messages: Message[]
346
+ queryParams: Omit<QueryParams, 'messages'>
347
+ description: string
348
+ setAppState: SetAppState
349
+ agentDefinition?: AgentDefinition
350
+ }): string {
351
+ const { taskId, abortSignal } = registerMainSessionTask(
352
+ description,
353
+ setAppState,
354
+ agentDefinition,
355
+ )
356
+
357
+ // Persist the pre-backgrounding conversation to the task's isolated
358
+ // transcript so TaskOutput shows context immediately. Subsequent messages
359
+ // are written incrementally below.
360
+ void recordSidechainTranscript(messages, taskId).catch(err =>
361
+ logForDebugging(`bg-session initial transcript write failed: ${err}`),
362
+ )
363
+
364
+ // Wrap in agent context so skill invocations scope to this task's agentId
365
+ // (not null). This lets clearInvokedSkills(preservedAgentIds) selectively
366
+ // preserve this task's skills across /clear. AsyncLocalStorage isolates
367
+ // concurrent async chains — this wrapper doesn't affect the foreground.
368
+ const agentContext: SubagentContext = {
369
+ agentId: taskId,
370
+ agentType: 'subagent',
371
+ subagentName: 'main-session',
372
+ isBuiltIn: true,
373
+ }
374
+
375
+ void runWithAgentContext(agentContext, async () => {
376
+ try {
377
+ const bgMessages: Message[] = [...messages]
378
+ const recentActivities: ToolActivity[] = []
379
+ let toolCount = 0
380
+ let tokenCount = 0
381
+ let lastRecordedUuid: UUID | null = messages.at(-1)?.uuid ?? null
382
+
383
+ for await (const event of query({
384
+ messages: bgMessages,
385
+ ...queryParams,
386
+ })) {
387
+ if (abortSignal.aborted) {
388
+ // Aborted mid-stream — completeMainSessionTask won't be reached.
389
+ // chat:killAgents path already marked notified + emitted; stopTask path did not.
390
+ let alreadyNotified = false
391
+ updateTaskState(taskId, setAppState, task => {
392
+ alreadyNotified = task.notified === true
393
+ return alreadyNotified ? task : { ...task, notified: true }
394
+ })
395
+ if (!alreadyNotified) {
396
+ emitTaskTerminatedSdk(taskId, 'stopped', {
397
+ summary: description,
398
+ })
399
+ }
400
+ return
401
+ }
402
+
403
+ if (
404
+ event.type !== 'user' &&
405
+ event.type !== 'assistant' &&
406
+ event.type !== 'system'
407
+ ) {
408
+ continue
409
+ }
410
+
411
+ bgMessages.push(event)
412
+
413
+ // Per-message write (matches runAgent.ts pattern) — gives live
414
+ // TaskOutput progress and keeps the transcript file current even if
415
+ // /clear re-links the symlink mid-run.
416
+ void recordSidechainTranscript([event], taskId, lastRecordedUuid).catch(
417
+ err => logForDebugging(`bg-session transcript write failed: ${err}`),
418
+ )
419
+ lastRecordedUuid = event.uuid
420
+
421
+ if (event.type === 'assistant') {
422
+ for (const block of event.message.content) {
423
+ if (block.type === 'text') {
424
+ tokenCount += roughTokenCountEstimation(block.text)
425
+ } else if (block.type === 'tool_use') {
426
+ toolCount++
427
+ const activity: ToolActivity = {
428
+ toolName: block.name,
429
+ input: block.input as Record<string, unknown>,
430
+ }
431
+ recentActivities.push(activity)
432
+ if (recentActivities.length > MAX_RECENT_ACTIVITIES) {
433
+ recentActivities.shift()
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+ setAppState(prev => {
440
+ const task = prev.tasks[taskId]
441
+ if (!task || task.type !== 'local_agent') return prev
442
+ const prevProgress = task.progress
443
+ if (
444
+ prevProgress?.tokenCount === tokenCount &&
445
+ prevProgress.toolUseCount === toolCount &&
446
+ task.messages === bgMessages
447
+ ) {
448
+ return prev
449
+ }
450
+ return {
451
+ ...prev,
452
+ tasks: {
453
+ ...prev.tasks,
454
+ [taskId]: {
455
+ ...task,
456
+ progress: {
457
+ tokenCount,
458
+ toolUseCount: toolCount,
459
+ recentActivities:
460
+ prevProgress?.toolUseCount === toolCount
461
+ ? prevProgress.recentActivities
462
+ : [...recentActivities],
463
+ },
464
+ messages: bgMessages,
465
+ },
466
+ },
467
+ }
468
+ })
469
+ }
470
+
471
+ completeMainSessionTask(taskId, true, setAppState)
472
+ } catch (error) {
473
+ logError(error)
474
+ completeMainSessionTask(taskId, false, setAppState)
475
+ }
476
+ })
477
+
478
+ return taskId
479
+ }