Skip to content

Commit 179ee39

Browse files
committed
add print mode event for agent headers
1 parent 564c094 commit 179ee39

File tree

5 files changed

+104
-34
lines changed

5 files changed

+104
-34
lines changed

backend/src/tools/handlers/tool/spawn-agent-utils.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,12 @@ export async function executeAgent({
328328
isOnlyChild?: boolean
329329
clearUserPromptMessagesAfterResponse?: boolean
330330
}) {
331-
const width = 60
332-
const fullAgentName = `${agentTemplate.displayName} (${agentTemplate.id})`
333-
const dashesLength = Math.max(
334-
0,
335-
Math.floor((width - fullAgentName.length - 2) / 2),
336-
)
337-
const dashes = '-'.repeat(dashesLength)
338-
339-
// Send agent start notification if this is the only child
340-
if (isOnlyChild) {
341-
onResponseChunk(`\n\n${dashes} ${fullAgentName} ${dashes}\n\n`)
342-
}
331+
onResponseChunk({
332+
type: 'subagent_start',
333+
agentId: agentTemplate.id,
334+
displayName: agentTemplate.displayName,
335+
onlyChild: isOnlyChild,
336+
})
343337

344338
// Import loopAgentSteps dynamically to avoid circular dependency
345339
const { loopAgentSteps } = await import('../../../run-agent-step')
@@ -360,18 +354,12 @@ export async function executeAgent({
360354
clearUserPromptMessagesAfterResponse,
361355
})
362356

363-
// Send agent end notification if this is the only child
364-
if (isOnlyChild) {
365-
const endedFullAgentName = `Completed: ${fullAgentName}`
366-
const dashesLength = Math.max(
367-
0,
368-
Math.floor((width - endedFullAgentName.length - 2) / 2),
369-
)
370-
const dashesForEndedAgent = '-'.repeat(dashesLength)
371-
onResponseChunk(
372-
`\n\n${dashesForEndedAgent} ${endedFullAgentName} ${dashesForEndedAgent}\n\n`,
373-
)
374-
}
357+
onResponseChunk({
358+
type: 'subagent_finish',
359+
agentId: agentTemplate.id,
360+
displayName: agentTemplate.displayName,
361+
onlyChild: isOnlyChild,
362+
})
375363

376364
if (result.agentState.runId) {
377365
parentAgentState.childRunIds.push(result.agentState.runId)

common/src/types/print-mode.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,33 @@ export const printModeFinishSchema = z.object({
5151
})
5252
export type PrintModeFinish = z.infer<typeof printModeFinishSchema>
5353

54+
export const printModeSubagentStartSchema = z.object({
55+
type: z.literal('subagent_start'),
56+
agentId: z.string(),
57+
displayName: z.string(),
58+
onlyChild: z.boolean(),
59+
})
60+
export type PrintModeSubagentStart = z.infer<
61+
typeof printModeSubagentStartSchema
62+
>
63+
64+
export const printModeSubagentFinishSchema = z.object({
65+
type: z.literal('subagent_finish'),
66+
agentId: z.string(),
67+
displayName: z.string(),
68+
onlyChild: z.boolean(),
69+
})
70+
export type PrintModeSubagentFinish = z.infer<
71+
typeof printModeSubagentFinishSchema
72+
>
73+
5474
export const printModeEventSchema = z.discriminatedUnion('type', [
55-
printModeErrorSchema,
5675
printModeDownloadStatusSchema,
76+
printModeErrorSchema,
5777
printModeFinishSchema,
5878
printModeStartSchema,
79+
printModeSubagentFinishSchema,
80+
printModeSubagentStartSchema,
5981
printModeTextSchema,
6082
printModeToolCallSchema,
6183
printModeToolResultSchema,

npm-app/src/client.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
startNewChat,
7575
} from './project-files'
7676
import { logAndHandleStartup } from './startup-process-handler'
77+
import { printSubagentHeader } from './subagent-headers'
7778
import {
7879
clearSubagentStorage,
7980
getAllSubagentIds,
@@ -1018,11 +1019,8 @@ export class Client {
10181019
DiffManager.receivedResponse()
10191020
process.stdout.write(chunk)
10201021
} else {
1021-
if (chunk.type === 'error') {
1022-
printModeLog(chunk)
1023-
} else {
1024-
printModeLog(chunk)
1025-
}
1022+
printModeLog(chunk)
1023+
printSubagentHeader(chunk)
10261024
}
10271025
},
10281026
userInputId,

npm-app/src/subagent-headers.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Spinner } from './utils/spinner'
2+
3+
import type {
4+
PrintModeEvent,
5+
PrintModeSubagentFinish,
6+
} from '@codebuff/common/types/print-mode'
7+
8+
export function printSubagentHeader(event: PrintModeEvent) {
9+
if (event.type !== 'subagent_start' && event.type !== 'subagent_finish') {
10+
return
11+
}
12+
13+
if (!event.onlyChild) {
14+
return
15+
}
16+
17+
const width = 60
18+
const fullAgentName = `${event.displayName} (${event.agentId})`
19+
const dashesLength = Math.max(
20+
0,
21+
Math.floor((width - fullAgentName.length - 2) / 2),
22+
)
23+
const dashes = '-'.repeat(dashesLength)
24+
25+
const shouldResumeSpinner = Spinner.get().stop()
26+
if (event.type === 'subagent_start') {
27+
console.log(`\n\n${dashes} ${fullAgentName} ${dashes}\n`)
28+
} else {
29+
event satisfies PrintModeSubagentFinish
30+
31+
const endedFullAgentName = `Completed: ${fullAgentName}`
32+
const dashesLength = Math.max(
33+
0,
34+
Math.floor((width - endedFullAgentName.length - 2) / 2),
35+
)
36+
const dashesForEndedAgent = '-'.repeat(dashesLength)
37+
console.log(
38+
`\n\n${dashesForEndedAgent} ${endedFullAgentName} ${dashesForEndedAgent}\n`,
39+
)
40+
console.log(``)
41+
}
42+
if (shouldResumeSpinner) {
43+
return Spinner.get().start(null)
44+
}
45+
}

npm-app/src/utils/spinner.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,15 @@ export class Spinner {
2727
return Spinner.instance
2828
}
2929

30-
start(text: string) {
31-
this.text = text
30+
/**
31+
* Start the spinner with the given text.
32+
*
33+
* @param text The text to display in the spinner. If this is `null`, the spinner will resume with the previous text.
34+
*/
35+
start(text: string | null): void {
36+
if (text !== null) {
37+
this.text = text
38+
}
3239
if (this.loadingInterval) {
3340
return
3441
}
@@ -47,12 +54,21 @@ export class Spinner {
4754
}, 100)
4855
}
4956

50-
stop() {
57+
isActive(): boolean {
58+
return !!this.loadingInterval
59+
}
60+
61+
/**
62+
* Stop the spinner and restore the cursor.
63+
*
64+
* @returns `true` if the spinner was active before calling this method, `false` otherwise.
65+
*/
66+
stop(): boolean {
5167
// Clear hang detection
5268
this.hangDetector.stop()
5369

5470
if (!this.loadingInterval) {
55-
return
71+
return false
5672
}
5773

5874
clearInterval(this.loadingInterval)
@@ -64,6 +80,7 @@ export class Spinner {
6480
setPrevious(this.previous)
6581
}
6682
this.previous = null
83+
return true
6784
}
6885

6986
restoreCursor() {

0 commit comments

Comments
 (0)