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.
- package/assistant/sessionHistory.ts +87 -0
- package/bootstrap/state.ts +1769 -0
- package/bridge/bridgeApi.ts +539 -0
- package/bridge/bridgeConfig.ts +48 -0
- package/bridge/bridgeDebug.ts +135 -0
- package/bridge/bridgeEnabled.ts +202 -0
- package/bridge/bridgeMain.ts +2999 -0
- package/bridge/bridgeMessaging.ts +461 -0
- package/bridge/bridgePermissionCallbacks.ts +43 -0
- package/bridge/bridgePointer.ts +210 -0
- package/bridge/bridgeStatusUtil.ts +163 -0
- package/bridge/bridgeUI.ts +530 -0
- package/bridge/capacityWake.ts +56 -0
- package/bridge/codeSessionApi.ts +168 -0
- package/bridge/createSession.ts +384 -0
- package/bridge/debugUtils.ts +141 -0
- package/bridge/envLessBridgeConfig.ts +165 -0
- package/bridge/flushGate.ts +71 -0
- package/bridge/inboundAttachments.ts +175 -0
- package/bridge/inboundMessages.ts +80 -0
- package/bridge/initReplBridge.ts +569 -0
- package/bridge/jwtUtils.ts +256 -0
- package/bridge/pollConfig.ts +110 -0
- package/bridge/pollConfigDefaults.ts +82 -0
- package/bridge/remoteBridgeCore.ts +1008 -0
- package/bridge/replBridge.ts +2406 -0
- package/bridge/replBridgeHandle.ts +36 -0
- package/bridge/replBridgeTransport.ts +370 -0
- package/bridge/sessionIdCompat.ts +57 -0
- package/bridge/sessionRunner.ts +550 -0
- package/bridge/trustedDevice.ts +210 -0
- package/bridge/types.ts +262 -0
- package/bridge/workSecret.ts +127 -0
- package/buddy/CompanionSprite.tsx +371 -0
- package/buddy/companion.ts +133 -0
- package/buddy/prompt.ts +36 -0
- package/buddy/sprites.ts +514 -0
- package/buddy/types.ts +148 -0
- package/buddy/useBuddyNotification.tsx +98 -0
- package/coordinator/coordinatorMode.ts +369 -0
- package/memdir/findRelevantMemories.ts +141 -0
- package/memdir/memdir.ts +507 -0
- package/memdir/memoryAge.ts +53 -0
- package/memdir/memoryScan.ts +94 -0
- package/memdir/memoryTypes.ts +271 -0
- package/memdir/paths.ts +278 -0
- package/memdir/teamMemPaths.ts +292 -0
- package/memdir/teamMemPrompts.ts +100 -0
- package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
- package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
- package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
- package/migrations/migrateFennecToOpus.ts +45 -0
- package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
- package/migrations/migrateOpusToOpus1m.ts +43 -0
- package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
- package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
- package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
- package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
- package/migrations/resetProToOpusDefault.ts +51 -0
- package/native-ts/color-diff/index.ts +999 -0
- package/native-ts/file-index/index.ts +370 -0
- package/native-ts/yoga-layout/enums.ts +134 -0
- package/native-ts/yoga-layout/index.ts +2578 -0
- package/outputStyles/loadOutputStylesDir.ts +98 -0
- package/package.json +22 -5
- package/plugins/builtinPlugins.ts +159 -0
- package/plugins/bundled/index.ts +23 -0
- package/schemas/hooks.ts +222 -0
- package/screens/Doctor.tsx +575 -0
- package/screens/REPL.tsx +5006 -0
- package/screens/ResumeConversation.tsx +399 -0
- package/server/createDirectConnectSession.ts +88 -0
- package/server/directConnectManager.ts +213 -0
- package/server/types.ts +57 -0
- package/skills/bundled/batch.ts +124 -0
- package/skills/bundled/claudeApi.ts +196 -0
- package/skills/bundled/claudeApiContent.ts +75 -0
- package/skills/bundled/claudeInChrome.ts +34 -0
- package/skills/bundled/debug.ts +103 -0
- package/skills/bundled/index.ts +79 -0
- package/skills/bundled/keybindings.ts +339 -0
- package/skills/bundled/loop.ts +92 -0
- package/skills/bundled/loremIpsum.ts +282 -0
- package/skills/bundled/remember.ts +82 -0
- package/skills/bundled/scheduleRemoteAgents.ts +447 -0
- package/skills/bundled/simplify.ts +69 -0
- package/skills/bundled/skillify.ts +197 -0
- package/skills/bundled/stuck.ts +79 -0
- package/skills/bundled/updateConfig.ts +475 -0
- package/skills/bundled/verify/SKILL.md +3 -0
- package/skills/bundled/verify/examples/cli.md +3 -0
- package/skills/bundled/verify/examples/server.md +3 -0
- package/skills/bundled/verify.ts +30 -0
- package/skills/bundled/verifyContent.ts +13 -0
- package/skills/bundledSkills.ts +220 -0
- package/skills/loadSkillsDir.ts +1086 -0
- package/skills/mcpSkillBuilders.ts +44 -0
- package/tasks/DreamTask/DreamTask.ts +157 -0
- package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
- package/tasks/InProcessTeammateTask/types.ts +121 -0
- package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
- package/tasks/LocalMainSessionTask.ts +479 -0
- package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
- package/tasks/LocalShellTask/guards.ts +41 -0
- package/tasks/LocalShellTask/killShellTasks.ts +76 -0
- package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
- package/tasks/pillLabel.ts +82 -0
- package/tasks/stopTask.ts +100 -0
- package/tasks/types.ts +46 -0
- package/upstreamproxy/relay.ts +455 -0
- package/upstreamproxy/upstreamproxy.ts +285 -0
- package/vim/motions.ts +82 -0
- package/vim/operators.ts +556 -0
- package/vim/textObjects.ts +186 -0
- package/vim/transitions.ts +490 -0
- package/vim/types.ts +199 -0
- 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
|
+
}
|