Skip to content

Commit a5f5993

Browse files
committed
move terminal.ts to directory
1 parent 05009f4 commit a5f5993

File tree

9 files changed

+218
-201
lines changed

9 files changed

+218
-201
lines changed

common/src/util/string.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,29 @@ export function includesMatch(
284284
return p.test(value)
285285
})
286286
}
287+
288+
/**
289+
* Finds the longest substring that is **both** a suffix of `source`
290+
* **and** a prefix of `next`.
291+
* Useful when concatenating strings while avoiding duplicate overlap.
292+
*
293+
* @example
294+
* ```ts
295+
* suffixPrefixOverlap('foobar', 'barbaz'); // ➜ 'bar'
296+
* suffixPrefixOverlap('abc', 'def'); // ➜ ''
297+
* ```
298+
*
299+
* @param source The string whose **suffix** is inspected.
300+
* @param next The string whose **prefix** is inspected.
301+
* @returns The longest overlapping edge, or an empty string if none exists.
302+
*/
303+
export function suffixPrefixOverlap(source: string, next: string): string {
304+
for (let len = next.length; len > 0; len--) {
305+
const prefix = next.slice(0, len)
306+
if (source.endsWith(prefix)) {
307+
return prefix
308+
}
309+
}
310+
311+
return ''
312+
}

npm-app/src/cli.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ import {
5353
initProjectFileContextWithWorker,
5454
isDir,
5555
} from './project-files'
56-
import { CliOptions, GitCommand } from './types'
57-
import { flushAnalytics, trackEvent } from './utils/analytics'
58-
import { Spinner } from './utils/spinner'
5956
import {
6057
clearScreen,
6158
isCommandRunning,
6259
killAndResetPersistentProcess,
6360
persistentProcess,
6461
resetShell,
65-
} from './utils/terminal'
62+
} from './terminal/base'
63+
import { CliOptions, GitCommand } from './types'
64+
import { flushAnalytics, trackEvent } from './utils/analytics'
65+
import { Spinner } from './utils/spinner'
6666

6767
import { CONFIG_DIR } from './credentials'
6868
import { loadCodebuffConfig } from './json-config/parser'

npm-app/src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,13 @@ import {
7575
getWorkingDirectory,
7676
startNewChat,
7777
} from './project-files'
78+
import { readNewTerminalOutput } from './terminal/base'
7879
import { handleToolCall } from './tool-handlers'
7980
import { GitCommand, MakeNullable } from './types'
8081
import { identifyUser, trackEvent } from './utils/analytics'
8182
import { getRepoMetrics, gitCommandIsAvailable } from './utils/git'
8283
import { logger, loggerContext } from './utils/logger'
8384
import { Spinner } from './utils/spinner'
84-
import { readNewTerminalOutput } from './utils/terminal'
8585
import { toolRenderers } from './utils/tool-renderers'
8686
import { createXMLStreamParser } from './utils/xml-stream-parser'
8787
import { getScrapedContentBlocks, parseUrlsFromContent } from './web-scraper'

npm-app/src/dev-process-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { generateCompactId } from 'common/util/string'
88
import { yellow } from 'picocolors'
99

10-
import { runBackgroundCommand } from './utils/terminal'
10+
import { runBackgroundCommand } from './terminal/background'
1111

1212
/**
1313
* Starts background development processes defined in the config file.

npm-app/src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@ import {
1616
setWorkingDirectory,
1717
} from './project-files'
1818
import { logAndHandleStartup } from './startup-process-handler'
19+
import { recreateShell } from './terminal/base'
1920
import { CliOptions } from './types'
2021
import { updateCodebuff } from './update-codebuff'
2122
import { initAnalytics } from './utils/analytics'
2223
import { findGitRoot } from './utils/git'
2324
import { logger } from './utils/logger'
24-
import { recreateShell } from './utils/terminal'
2525

2626
async function codebuff(
2727
projectDir: string | undefined,
@@ -117,7 +117,8 @@ For all commands and options, run 'codebuff' and then type 'help'.
117117
)
118118
logger.error(
119119
{
120-
errorMessage: 'The --pro flag is deprecated. Please restart codebuff and use the --max option instead.',
120+
errorMessage:
121+
'The --pro flag is deprecated. Please restart codebuff and use the --max option instead.',
121122
},
122123
'Deprecated --pro flag used'
123124
)

npm-app/src/json-config/hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { generateCompactId } from 'common/util/string'
33
import micromatch from 'micromatch'
44

55
import { getProjectRoot } from '../project-files'
6+
import { runTerminalCommand } from '../terminal/base'
67
import { logger } from '../utils/logger'
7-
import { runTerminalCommand } from '../utils/terminal'
88
import { loadCodebuffConfig } from './parser'
99

1010
/**

npm-app/src/terminal/background.ts

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import assert from 'assert'
2+
import { createWriteStream, mkdirSync, WriteStream } from 'fs'
3+
import * as os from 'os'
4+
import path, { dirname } from 'path'
5+
6+
import { stripColors } from 'common/util/string'
7+
import { green } from 'picocolors'
8+
9+
import {
10+
backgroundProcesses,
11+
BackgroundProcessInfo,
12+
spawnAndTrack,
13+
} from '../background-process-manager'
14+
15+
export function runBackgroundCommand(
16+
options: {
17+
toolCallId: string
18+
command: string
19+
mode: 'user' | 'assistant' | 'manager'
20+
cwd: string
21+
stdoutFile?: string
22+
stderrFile?: string
23+
},
24+
resolveCommand: (value: {
25+
result: string
26+
stdout: string
27+
exitCode: number | null
28+
}) => void
29+
): void {
30+
const { toolCallId, command, mode, cwd, stdoutFile, stderrFile } = options
31+
const isWindows = os.platform() === 'win32'
32+
const shell = isWindows ? 'cmd.exe' : 'bash'
33+
const shellArgs = isWindows ? ['/c'] : ['-c']
34+
35+
if (mode === 'assistant') {
36+
console.log(green(`Running background process...\n> ${command}`))
37+
}
38+
39+
const initialStdout = ''
40+
const initialStderr = ''
41+
42+
try {
43+
const childProcess = spawnAndTrack(shell, [...shellArgs, command], {
44+
cwd,
45+
env: { ...process.env, FORCE_COLOR: '1' },
46+
// Ensure detached is always false to link child lifetime to parent
47+
detached: false,
48+
stdio: 'pipe',
49+
})
50+
51+
// An error should have been thrown when we called `spawn`
52+
assert(
53+
childProcess.pid !== undefined,
54+
'Failed to spawn process: no PID assigned.'
55+
)
56+
57+
const processId = childProcess.pid
58+
const processInfo: BackgroundProcessInfo = {
59+
pid: processId,
60+
toolCallId,
61+
command,
62+
process: childProcess,
63+
stdoutBuffer: [],
64+
stderrBuffer: [],
65+
status: 'running',
66+
startTime: Date.now(),
67+
endTime: null,
68+
lastReportedStdoutLength: 0,
69+
lastReportedStderrLength: 0,
70+
lastReportedStatus: null,
71+
stdoutFile,
72+
stderrFile,
73+
}
74+
backgroundProcesses.set(processId, processInfo)
75+
76+
// Set up file streams if paths are provided
77+
let stdoutStream: WriteStream | undefined
78+
let stderrStream: WriteStream | undefined
79+
80+
if (stdoutFile) {
81+
const stdoutAbs = path.isAbsolute(stdoutFile)
82+
? stdoutFile
83+
: path.join(cwd, stdoutFile)
84+
mkdirSync(dirname(stdoutAbs), { recursive: true })
85+
stdoutStream = createWriteStream(stdoutAbs)
86+
}
87+
88+
const realStderrFile = stderrFile || stdoutFile
89+
if (realStderrFile) {
90+
const stderrAbs = path.isAbsolute(realStderrFile)
91+
? realStderrFile
92+
: path.join(cwd, realStderrFile)
93+
mkdirSync(dirname(stderrAbs), { recursive: true })
94+
stderrStream = createWriteStream(stderrAbs)
95+
}
96+
97+
childProcess.stdout.on('data', (data: Buffer) => {
98+
const output = stripColors(data.toString())
99+
processInfo.stdoutBuffer.push(output)
100+
101+
// Write to file if stream exists
102+
if (stdoutStream) {
103+
stdoutStream.write(output)
104+
}
105+
})
106+
107+
childProcess.stderr.on('data', (data: Buffer) => {
108+
const output = stripColors(data.toString())
109+
processInfo.stderrBuffer.push(output)
110+
111+
// Write to file if stream exists
112+
if (stderrStream) {
113+
stderrStream.write(output)
114+
}
115+
})
116+
117+
childProcess.on('error', (error) => {
118+
processInfo.status = 'error'
119+
processInfo.stderrBuffer.push(
120+
`\nError spawning command: ${error.message}`
121+
)
122+
processInfo.endTime = Date.now()
123+
124+
// Close file streams
125+
stdoutStream?.end()
126+
stderrStream?.end()
127+
})
128+
129+
let exitCode = null
130+
131+
childProcess.on('close', (code) => {
132+
exitCode = code
133+
processInfo.status = code === 0 ? 'completed' : 'error'
134+
processInfo.endTime = Date.now()
135+
136+
// Close file streams
137+
stdoutStream?.end()
138+
stderrStream?.end()
139+
})
140+
141+
// Unreference the process so the parent can exit independently IF the child is the only thing keeping it alive.
142+
childProcess.unref()
143+
144+
const resultMessage = `<background_process>
145+
<process_id>${processId}</process_id>
146+
<command>${command}</command>
147+
<status>${processInfo.status}</status>
148+
</background_process>`
149+
resolveCommand({
150+
result: resultMessage,
151+
stdout: initialStdout + initialStderr,
152+
exitCode,
153+
})
154+
} catch (error: any) {
155+
const errorMessage = `<background_process>\n<command>${command}</command>\n<error>${error.message}</error>\n</background_process>`
156+
resolveCommand({
157+
result: errorMessage,
158+
stdout: error.message,
159+
exitCode: null,
160+
})
161+
}
162+
}

0 commit comments

Comments
 (0)