Skip to content

Commit c9ed1ab

Browse files
committed
sdk: inject child_process spawn, compute git changes for initial sessionState
1 parent 2a08b3c commit c9ed1ab

File tree

3 files changed

+131
-6
lines changed

3 files changed

+131
-6
lines changed

common/src/types/spawn.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { ChildProcess, SpawnOptions } from 'child_process'
2+
3+
/**
4+
* Spawn function for running shell commands.
5+
*
6+
* Compatible with `child_process.spawn` from Node.js.
7+
* Returns ChildProcess to support full streaming capabilities (stdin, stdout, stderr).
8+
*/
9+
export type CodebuffSpawn = (
10+
command: string,
11+
args?: readonly string[],
12+
options?: SpawnOptions,
13+
) => ChildProcess

sdk/src/run-state.ts

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { CustomToolDefinition } from './custom-tool'
1414
import type { AgentDefinition } from '@codebuff/common/templates/initial-agents-dir/types/agent-definition'
1515
import type { Logger } from '@codebuff/common/types/contracts/logger'
1616
import type { CodebuffFileSystem } from '@codebuff/common/types/filesystem'
17+
import type { CodebuffSpawn } from '@codebuff/common/types/spawn'
1718
import type { Message } from '@codebuff/common/types/messages/codebuff-message'
1819
import type {
1920
AgentOutput,
@@ -38,6 +39,7 @@ export type InitialSessionStateOptions = {
3839
customToolDefinitions?: CustomToolDefinition[]
3940
maxAgentSteps?: number
4041
fs?: CodebuffFileSystem
42+
spawn?: CodebuffSpawn
4143
logger?: Logger
4244
}
4345

@@ -119,6 +121,99 @@ async function computeProjectIndex(
119121
return { fileTree, fileTokenScores, tokenCallers }
120122
}
121123

124+
/**
125+
* Helper to convert ChildProcess to Promise with stdout/stderr
126+
*/
127+
function childProcessToPromise(
128+
proc: ReturnType<CodebuffSpawn>,
129+
): Promise<{ stdout: string; stderr: string }> {
130+
return new Promise((resolve, reject) => {
131+
let stdout = ''
132+
let stderr = ''
133+
134+
proc.stdout?.on('data', (data: Buffer) => {
135+
stdout += data.toString()
136+
})
137+
138+
proc.stderr?.on('data', (data: Buffer) => {
139+
stderr += data.toString()
140+
})
141+
142+
proc.on('close', (code: number | null) => {
143+
if (code === 0) {
144+
resolve({ stdout, stderr })
145+
} else {
146+
reject(new Error(`Command exited with code ${code}`))
147+
}
148+
})
149+
150+
proc.on('error', reject)
151+
})
152+
}
153+
154+
/**
155+
* Retrieves git changes for the project using the provided spawn function
156+
*/
157+
async function getGitChanges(params: {
158+
cwd: string
159+
spawn: CodebuffSpawn
160+
logger: Logger
161+
}): Promise<{
162+
status: string
163+
diff: string
164+
diffCached: string
165+
lastCommitMessages: string
166+
}> {
167+
const { cwd, spawn, logger } = params
168+
169+
const status = childProcessToPromise(spawn('git', ['status'], { cwd }))
170+
.then(({ stdout }) => stdout)
171+
.catch((error) => {
172+
logger.debug?.({ error }, 'Failed to get git status')
173+
return ''
174+
})
175+
176+
const diff = childProcessToPromise(spawn('git', ['diff'], { cwd }))
177+
.then(({ stdout }) => stdout)
178+
.catch((error) => {
179+
logger.debug?.({ error }, 'Failed to get git diff')
180+
return ''
181+
})
182+
183+
const diffCached = childProcessToPromise(
184+
spawn('git', ['diff', '--cached'], { cwd }),
185+
)
186+
.then(({ stdout }) => stdout)
187+
.catch((error) => {
188+
logger.debug?.({ error }, 'Failed to get git diff --cached')
189+
return ''
190+
})
191+
192+
const lastCommitMessages = childProcessToPromise(
193+
spawn('git', ['shortlog', 'HEAD~10..HEAD'], { cwd }),
194+
)
195+
.then(({ stdout }) =>
196+
stdout
197+
.trim()
198+
.split('\n')
199+
.slice(1)
200+
.reverse()
201+
.map((line) => line.trim())
202+
.join('\n'),
203+
)
204+
.catch((error) => {
205+
logger.debug?.({ error }, 'Failed to get lastCommitMessages')
206+
return ''
207+
})
208+
209+
return {
210+
status: await status,
211+
diff: await diff,
212+
diffCached: await diffCached,
213+
lastCommitMessages: await lastCommitMessages,
214+
}
215+
}
216+
122217
/**
123218
* Discovers project files using .gitignore patterns when projectFiles is undefined
124219
*/
@@ -231,6 +326,7 @@ export async function initialSessionState(
231326
projectFiles,
232327
knowledgeFiles,
233328
fs,
329+
spawn,
234330
logger,
235331
} = params
236332
if (!agentDefinitions) {
@@ -242,6 +338,10 @@ export async function initialSessionState(
242338
if (!fs) {
243339
fs = (require('fs') as typeof fsType).promises
244340
}
341+
if (!spawn) {
342+
const { spawn: nodeSpawn } = require('child_process')
343+
spawn = nodeSpawn as CodebuffSpawn
344+
}
245345
if (!logger) {
246346
logger = {
247347
debug: () => {},
@@ -276,6 +376,16 @@ export async function initialSessionState(
276376
tokenCallers = result.tokenCallers
277377
}
278378

379+
// Gather git changes if cwd is available
380+
const gitChanges = cwd
381+
? await getGitChanges({ cwd, spawn, logger })
382+
: {
383+
status: '',
384+
diff: '',
385+
diffCached: '',
386+
lastCommitMessages: '',
387+
}
388+
279389
const initialState = getInitialSessionState({
280390
projectRoot: cwd ?? process.cwd(),
281391
cwd: cwd ?? process.cwd(),
@@ -286,12 +396,7 @@ export async function initialSessionState(
286396
userKnowledgeFiles: {},
287397
agentTemplates: processedAgentTemplates,
288398
customToolDefinitions: processedCustomToolDefinitions,
289-
gitChanges: {
290-
status: '',
291-
diff: '',
292-
diffCached: '',
293-
lastCommitMessages: '',
294-
},
399+
gitChanges,
295400
changesSinceLastChat: {},
296401
shellConfigFiles: {},
297402
systemInfo: {

sdk/src/run.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import type {
3838
} from '@codebuff/common/tools/list'
3939
import type { Logger } from '@codebuff/common/types/contracts/logger'
4040
import type { CodebuffFileSystem } from '@codebuff/common/types/filesystem'
41+
import type { CodebuffSpawn } from '@codebuff/common/types/spawn'
4142
import type {
4243
ToolResultOutput,
4344
ToolResultPart,
@@ -89,6 +90,7 @@ export type CodebuffClientOptions = {
8990
customToolDefinitions?: CustomToolDefinition[]
9091

9192
fsSource?: Source<CodebuffFileSystem>
93+
spawnSource?: Source<CodebuffSpawn>
9294
logger?: Logger
9395
}
9496

@@ -120,6 +122,7 @@ export async function run({
120122
customToolDefinitions,
121123

122124
fsSource = () => require('fs').promises,
125+
spawnSource,
123126
logger,
124127

125128
agent,
@@ -134,6 +137,9 @@ export async function run({
134137
fingerprintId: string
135138
}): Promise<RunState> {
136139
const fs = await (typeof fsSource === 'function' ? fsSource() : fsSource)
140+
const spawn: CodebuffSpawn = (
141+
spawnSource ? await spawnSource : require('child_process').spawn
142+
) as CodebuffSpawn
137143

138144
// Init session state
139145
let agentId
@@ -167,6 +173,7 @@ export async function run({
167173
projectFiles,
168174
maxAgentSteps,
169175
fs,
176+
spawn,
170177
logger,
171178
})
172179
}

0 commit comments

Comments
 (0)