@muyichengshayu/promptx 0.2.13 → 0.2.15
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/CHANGELOG.md +11 -0
- package/apps/server/src/agentSessionDiscovery.js +180 -7
- package/apps/web/dist/assets/{CodexSessionManagerDialog-Dic9kMHK.js → CodexSessionManagerDialog-y7O-JTxP.js} +1 -1
- package/apps/web/dist/assets/{TaskDiffReviewDialog-CKiZdXqi.js → TaskDiffReviewDialog-CTr_zoAn.js} +1 -1
- package/apps/web/dist/assets/{WorkbenchSettingsDialog-CP0z90bm.js → WorkbenchSettingsDialog-Bf2DCuN_.js} +1 -1
- package/apps/web/dist/assets/{WorkbenchView-D1oxqNr4.css → WorkbenchView-CK1snPBz.css} +1 -1
- package/apps/web/dist/assets/WorkbenchView-Gq3mmtsK.js +60 -0
- package/apps/web/dist/assets/index-Co1Ssha9.js +2 -0
- package/apps/web/dist/index.html +1 -1
- package/package.json +21 -14
- package/apps/runner/src/engines/claudeCodeRunner.test.js +0 -467
- package/apps/runner/src/engines/kimiCodeRunner.test.js +0 -127
- package/apps/runner/src/engines/openCodeRunner.test.js +0 -236
- package/apps/runner/src/engines/runnerContract.test.js +0 -449
- package/apps/runner/src/engines/shellRunner.test.js +0 -46
- package/apps/runner/src/runManager.test.js +0 -913
- package/apps/runner/src/serverClient.test.js +0 -93
- package/apps/server/src/agentSessionDiscovery.test.js +0 -186
- package/apps/server/src/appPaths.test.js +0 -52
- package/apps/server/src/assetRoutes.test.js +0 -168
- package/apps/server/src/codex.test.js +0 -518
- package/apps/server/src/codexRoutes.test.js +0 -376
- package/apps/server/src/codexRuns.test.js +0 -160
- package/apps/server/src/codexSessions.test.js +0 -369
- package/apps/server/src/db.test.js +0 -182
- package/apps/server/src/gitDiff.test.js +0 -542
- package/apps/server/src/gitDiffClient.test.js +0 -140
- package/apps/server/src/internalRoutes.test.js +0 -134
- package/apps/server/src/maintenance.test.js +0 -154
- package/apps/server/src/processControl.test.js +0 -147
- package/apps/server/src/relayClient.test.js +0 -478
- package/apps/server/src/relayConfig.test.js +0 -73
- package/apps/server/src/relayProtocol.test.js +0 -49
- package/apps/server/src/relayServer.test.js +0 -798
- package/apps/server/src/relayTenants.test.js +0 -137
- package/apps/server/src/relayUsageStore.test.js +0 -65
- package/apps/server/src/repository.test.js +0 -150
- package/apps/server/src/runDispatchService.test.js +0 -563
- package/apps/server/src/runEventIngest.test.js +0 -225
- package/apps/server/src/runRecovery.test.js +0 -73
- package/apps/server/src/runnerClient.test.js +0 -80
- package/apps/server/src/runnerDispatch.test.js +0 -136
- package/apps/server/src/systemConfig.test.js +0 -112
- package/apps/server/src/systemRoutes.test.js +0 -319
- package/apps/server/src/taskRoutes.test.js +0 -775
- package/apps/server/src/upload.test.js +0 -30
- package/apps/server/src/webAppRoutes.test.js +0 -67
- package/apps/server/src/workspaceFiles.test.js +0 -279
- package/apps/web/dist/assets/WorkbenchView-noayQwj4.js +0 -60
- package/apps/web/dist/assets/index-HLkdzIYF.js +0 -2
- package/packages/shared/src/dailyLogStream.test.js +0 -29
- package/packages/shared/src/shellCommands.test.js +0 -45
|
@@ -1,518 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { createRequire } from 'node:module'
|
|
5
|
-
import test from 'node:test'
|
|
6
|
-
import assert from 'node:assert/strict'
|
|
7
|
-
import Database from 'better-sqlite3'
|
|
8
|
-
import initSqlJs from 'sql.js'
|
|
9
|
-
|
|
10
|
-
const require = createRequire(import.meta.url)
|
|
11
|
-
const wasmPath = require.resolve('sql.js/dist/sql-wasm.wasm')
|
|
12
|
-
const SQL = await initSqlJs({
|
|
13
|
-
locateFile: () => wasmPath,
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
function withEnv(overrides, fn) {
|
|
17
|
-
const previous = new Map()
|
|
18
|
-
|
|
19
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
20
|
-
previous.set(key, process.env[key])
|
|
21
|
-
if (typeof value === 'undefined') {
|
|
22
|
-
delete process.env[key]
|
|
23
|
-
} else {
|
|
24
|
-
process.env[key] = value
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return Promise.resolve()
|
|
29
|
-
.then(fn)
|
|
30
|
-
.finally(() => {
|
|
31
|
-
for (const [key, value] of previous.entries()) {
|
|
32
|
-
if (typeof value === 'undefined') {
|
|
33
|
-
delete process.env[key]
|
|
34
|
-
} else {
|
|
35
|
-
process.env[key] = value
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function importFreshCodexModule() {
|
|
42
|
-
return import(`./codex.js?test=${Date.now()}-${Math.random().toString(16).slice(2)}`)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function createFakeCodexBinary(tempDir) {
|
|
46
|
-
const scriptPath = path.join(tempDir, process.platform === 'win32' ? 'fake-codex.js' : 'fake-codex')
|
|
47
|
-
const script = `#!/usr/bin/env node
|
|
48
|
-
const fs = require('node:fs')
|
|
49
|
-
|
|
50
|
-
const args = process.argv.slice(2)
|
|
51
|
-
const outputIndex = args.indexOf('--output-last-message')
|
|
52
|
-
const outputFile = outputIndex >= 0 ? args[outputIndex + 1] : ''
|
|
53
|
-
const resumeIndex = args.indexOf('resume')
|
|
54
|
-
const resumeTarget = resumeIndex >= 0 ? args[resumeIndex + 1] || '' : ''
|
|
55
|
-
const threadId = resumeTarget || 'thread-new-123'
|
|
56
|
-
|
|
57
|
-
let prompt = ''
|
|
58
|
-
process.stdin.setEncoding('utf8')
|
|
59
|
-
process.stdin.on('data', (chunk) => {
|
|
60
|
-
prompt += chunk
|
|
61
|
-
})
|
|
62
|
-
process.stdin.on('end', () => {
|
|
63
|
-
if (prompt.includes('fail-case')) {
|
|
64
|
-
process.stderr.write('mocked codex failure\\n')
|
|
65
|
-
process.exit(2)
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (prompt.includes('disconnect-case')) {
|
|
70
|
-
process.stdout.write(JSON.stringify({
|
|
71
|
-
type: 'error',
|
|
72
|
-
message: 'Reconnecting... 1/5 (stream disconnected before completion: error sending request for url (https://api.codexzh.com/v1/responses))',
|
|
73
|
-
}) + '\\n')
|
|
74
|
-
process.stdout.write(JSON.stringify({
|
|
75
|
-
type: 'turn.failed',
|
|
76
|
-
error: {
|
|
77
|
-
message: 'stream disconnected before completion: error sending request for url (https://api.codexzh.com/v1/responses)',
|
|
78
|
-
},
|
|
79
|
-
}) + '\\n')
|
|
80
|
-
process.stderr.write('Warning: no last agent message; wrote empty content to tmp-file\\n')
|
|
81
|
-
process.exit(1)
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (prompt.includes('mojibake-case')) {
|
|
86
|
-
const garbled = '鑾峰彇娴嬭瘯鏁版嵁'
|
|
87
|
-
if (outputFile) {
|
|
88
|
-
fs.writeFileSync(outputFile, garbled)
|
|
89
|
-
}
|
|
90
|
-
process.stdout.write(JSON.stringify({ type: 'thread.started', thread_id: threadId }) + '\\n')
|
|
91
|
-
process.stdout.write(JSON.stringify({
|
|
92
|
-
type: 'item.completed',
|
|
93
|
-
item: {
|
|
94
|
-
type: 'command_execution',
|
|
95
|
-
command: 'Get-Content demo.txt',
|
|
96
|
-
aggregated_output: garbled,
|
|
97
|
-
exit_code: 0,
|
|
98
|
-
status: 'completed',
|
|
99
|
-
},
|
|
100
|
-
}) + '\\n')
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (prompt.includes('args-case')) {
|
|
105
|
-
if (outputFile) {
|
|
106
|
-
fs.writeFileSync(outputFile, JSON.stringify(args))
|
|
107
|
-
}
|
|
108
|
-
process.stdout.write(JSON.stringify({ type: 'thread.started', thread_id: threadId }) + '\\n')
|
|
109
|
-
process.stdout.write(JSON.stringify({
|
|
110
|
-
type: 'item.completed',
|
|
111
|
-
item: {
|
|
112
|
-
type: 'agent_message',
|
|
113
|
-
text: 'args-ok',
|
|
114
|
-
},
|
|
115
|
-
}) + '\\n')
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
process.stdout.write(JSON.stringify({ type: 'thread.started', thread_id: threadId }) + '\\n')
|
|
120
|
-
|
|
121
|
-
if (prompt.includes('stream-tail-case')) {
|
|
122
|
-
process.stdout.write(JSON.stringify({
|
|
123
|
-
type: 'item.completed',
|
|
124
|
-
item: {
|
|
125
|
-
type: 'agent_message',
|
|
126
|
-
text: 'stream tail message',
|
|
127
|
-
},
|
|
128
|
-
}))
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (outputFile) {
|
|
133
|
-
fs.writeFileSync(outputFile, \`thread:\${threadId}\\nprompt:\${prompt.trim()}\\ncwd:\${process.cwd()}\\n\`)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
process.stdout.write(JSON.stringify({
|
|
137
|
-
type: 'item.completed',
|
|
138
|
-
item: {
|
|
139
|
-
type: 'agent_message',
|
|
140
|
-
text: 'ok',
|
|
141
|
-
},
|
|
142
|
-
}) + '\\n')
|
|
143
|
-
})
|
|
144
|
-
`
|
|
145
|
-
|
|
146
|
-
fs.writeFileSync(scriptPath, script, { mode: 0o755 })
|
|
147
|
-
|
|
148
|
-
if (process.platform !== 'win32') {
|
|
149
|
-
return scriptPath
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const cmdPath = path.join(tempDir, 'fake-codex.cmd')
|
|
153
|
-
fs.writeFileSync(cmdPath, '@echo off\r\nnode "%~dp0fake-codex.js" %*\r\n')
|
|
154
|
-
return cmdPath
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function createWindowsCodexCommand(tempDir) {
|
|
158
|
-
const scriptPath = path.join(tempDir, 'fake-codex.js')
|
|
159
|
-
const cmdPath = path.join(tempDir, 'codex.cmd')
|
|
160
|
-
const script = `const fs = require('node:fs')
|
|
161
|
-
const args = process.argv.slice(2)
|
|
162
|
-
const outputIndex = args.indexOf('--output-last-message')
|
|
163
|
-
const outputFile = outputIndex >= 0 ? args[outputIndex + 1] : ''
|
|
164
|
-
let prompt = ''
|
|
165
|
-
process.stdin.setEncoding('utf8')
|
|
166
|
-
process.stdin.on('data', (chunk) => {
|
|
167
|
-
prompt += chunk
|
|
168
|
-
})
|
|
169
|
-
process.stdin.on('end', () => {
|
|
170
|
-
process.stdout.write(JSON.stringify({ type: 'thread.started', thread_id: 'thread-win' }) + '\\n')
|
|
171
|
-
if (outputFile) {
|
|
172
|
-
fs.writeFileSync(outputFile, \`prompt:\${prompt.trim()}\\n\`)
|
|
173
|
-
}
|
|
174
|
-
process.stdout.write(JSON.stringify({ type: 'item.completed', item: { type: 'agent_message', text: 'ok' } }) + '\\n')
|
|
175
|
-
})
|
|
176
|
-
`
|
|
177
|
-
|
|
178
|
-
fs.writeFileSync(scriptPath, script)
|
|
179
|
-
fs.writeFileSync(cmdPath, '@echo off\r\nnode "%~dp0fake-codex.js" %*\r\n')
|
|
180
|
-
return cmdPath
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function writeThreadsDb(tempHome, rows = []) {
|
|
184
|
-
const dbPath = path.join(tempHome, 'state_5.sqlite')
|
|
185
|
-
const db = new SQL.Database()
|
|
186
|
-
|
|
187
|
-
try {
|
|
188
|
-
db.run(`
|
|
189
|
-
CREATE TABLE threads (
|
|
190
|
-
id TEXT NOT NULL,
|
|
191
|
-
cwd TEXT,
|
|
192
|
-
title TEXT,
|
|
193
|
-
updated_at INTEGER
|
|
194
|
-
);
|
|
195
|
-
`)
|
|
196
|
-
|
|
197
|
-
const statement = db.prepare('INSERT INTO threads (id, cwd, title, updated_at) VALUES (?, ?, ?, ?)')
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
for (const row of rows) {
|
|
201
|
-
statement.run([
|
|
202
|
-
row.id,
|
|
203
|
-
row.cwd || '',
|
|
204
|
-
row.title || '',
|
|
205
|
-
Number(row.updated_at || 0),
|
|
206
|
-
])
|
|
207
|
-
}
|
|
208
|
-
} finally {
|
|
209
|
-
statement.free()
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
fs.writeFileSync(dbPath, Buffer.from(db.export()))
|
|
213
|
-
} finally {
|
|
214
|
-
db.close()
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function getSessionCwd() {
|
|
219
|
-
return process.cwd()
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
test('listKnownCodexWorkspaces dedupes cwd values', async () => {
|
|
223
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-home-'))
|
|
224
|
-
writeThreadsDb(tempHome, [
|
|
225
|
-
{ id: 'thread-1', cwd: 'D:\\code\\yuyang-web', title: 'A', updated_at: 10 },
|
|
226
|
-
{ id: 'thread-2', cwd: 'D:\\code\\promptx', title: 'B', updated_at: 9 },
|
|
227
|
-
{ id: 'thread-3', cwd: 'D:\\code\\yuyang-web', title: 'C', updated_at: 8 },
|
|
228
|
-
])
|
|
229
|
-
|
|
230
|
-
await withEnv(
|
|
231
|
-
{
|
|
232
|
-
CODEX_HOME: tempHome,
|
|
233
|
-
CODEX_BIN: undefined,
|
|
234
|
-
},
|
|
235
|
-
async () => {
|
|
236
|
-
const { listKnownCodexWorkspaces } = await importFreshCodexModule()
|
|
237
|
-
assert.deepEqual(listKnownCodexWorkspaces(), ['D:\\code\\yuyang-web', 'D:\\code\\promptx'])
|
|
238
|
-
}
|
|
239
|
-
)
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
test('listKnownCodexSessions reads latest rows from WAL-backed sqlite', async () => {
|
|
243
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-wal-'))
|
|
244
|
-
const dbPath = path.join(tempHome, 'state_5.sqlite')
|
|
245
|
-
const writer = new Database(dbPath)
|
|
246
|
-
|
|
247
|
-
try {
|
|
248
|
-
writer.pragma('journal_mode = WAL')
|
|
249
|
-
writer.exec(`
|
|
250
|
-
CREATE TABLE threads (
|
|
251
|
-
id TEXT NOT NULL,
|
|
252
|
-
cwd TEXT,
|
|
253
|
-
title TEXT,
|
|
254
|
-
updated_at INTEGER
|
|
255
|
-
);
|
|
256
|
-
`)
|
|
257
|
-
|
|
258
|
-
const insert = writer.prepare('INSERT INTO threads (id, cwd, title, updated_at) VALUES (?, ?, ?, ?)')
|
|
259
|
-
insert.run('thread-old', '/tmp/demo', 'old', 100)
|
|
260
|
-
insert.run('thread-new-1', '/tmp/demo', 'new-1', 300)
|
|
261
|
-
insert.run('thread-new-2', '/tmp/demo', 'new-2', 200)
|
|
262
|
-
|
|
263
|
-
await withEnv(
|
|
264
|
-
{
|
|
265
|
-
CODEX_HOME: tempHome,
|
|
266
|
-
CODEX_BIN: undefined,
|
|
267
|
-
},
|
|
268
|
-
async () => {
|
|
269
|
-
const { listKnownCodexSessions } = await importFreshCodexModule()
|
|
270
|
-
const items = listKnownCodexSessions({ cwd: '/tmp/demo', limit: 3 })
|
|
271
|
-
assert.deepEqual(
|
|
272
|
-
items.map((item) => item.id),
|
|
273
|
-
['thread-new-1', 'thread-new-2', 'thread-old']
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
)
|
|
277
|
-
} finally {
|
|
278
|
-
writer.close()
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
test('streamPromptToCodexSession handles a tail event without newline', async () => {
|
|
283
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-stream-'))
|
|
284
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
285
|
-
|
|
286
|
-
await withEnv(
|
|
287
|
-
{
|
|
288
|
-
CODEX_HOME: tempHome,
|
|
289
|
-
CODEX_BIN: fakeBin,
|
|
290
|
-
},
|
|
291
|
-
async () => {
|
|
292
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
293
|
-
const events = []
|
|
294
|
-
const seenThreadIds = []
|
|
295
|
-
const stream = streamPromptToCodexSession(
|
|
296
|
-
{ id: 'session-xyz', cwd: getSessionCwd() },
|
|
297
|
-
'stream-tail-case',
|
|
298
|
-
{
|
|
299
|
-
onEvent(event) {
|
|
300
|
-
events.push(event)
|
|
301
|
-
},
|
|
302
|
-
onThreadStarted(threadId) {
|
|
303
|
-
seenThreadIds.push(threadId)
|
|
304
|
-
},
|
|
305
|
-
}
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
const result = await stream.result
|
|
309
|
-
|
|
310
|
-
assert.equal(result.sessionId, 'session-xyz')
|
|
311
|
-
assert.equal(result.message, '')
|
|
312
|
-
assert.equal(result.threadId, 'thread-new-123')
|
|
313
|
-
assert.deepEqual(seenThreadIds, ['thread-new-123'])
|
|
314
|
-
assert.deepEqual(
|
|
315
|
-
events.filter((event) => event.type === 'agent_event').map((event) => event.event.type),
|
|
316
|
-
['thread.started', 'item.completed']
|
|
317
|
-
)
|
|
318
|
-
assert.equal(
|
|
319
|
-
events.find((event) => event.type === 'agent_event' && event.event.type === 'item.completed')?.event?.item?.text,
|
|
320
|
-
'stream tail message'
|
|
321
|
-
)
|
|
322
|
-
assert.equal(events.at(-1)?.type, 'completed')
|
|
323
|
-
}
|
|
324
|
-
)
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
test('streamPromptToCodexSession emits starting status for new sessions and resuming status for existing threads', async () => {
|
|
328
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-status-'))
|
|
329
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
330
|
-
|
|
331
|
-
await withEnv(
|
|
332
|
-
{
|
|
333
|
-
CODEX_HOME: tempHome,
|
|
334
|
-
CODEX_BIN: fakeBin,
|
|
335
|
-
},
|
|
336
|
-
async () => {
|
|
337
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
338
|
-
|
|
339
|
-
const startingEvents = []
|
|
340
|
-
const startingStream = streamPromptToCodexSession(
|
|
341
|
-
{ id: 'session-new', cwd: getSessionCwd(), codexThreadId: '' },
|
|
342
|
-
'hello start',
|
|
343
|
-
{
|
|
344
|
-
onEvent(event) {
|
|
345
|
-
startingEvents.push(event)
|
|
346
|
-
},
|
|
347
|
-
}
|
|
348
|
-
)
|
|
349
|
-
await startingStream.result
|
|
350
|
-
|
|
351
|
-
const resumingEvents = []
|
|
352
|
-
const resumingStream = streamPromptToCodexSession(
|
|
353
|
-
{ id: 'session-old', cwd: getSessionCwd(), codexThreadId: 'thread-existing-1' },
|
|
354
|
-
'hello resume',
|
|
355
|
-
{
|
|
356
|
-
onEvent(event) {
|
|
357
|
-
resumingEvents.push(event)
|
|
358
|
-
},
|
|
359
|
-
}
|
|
360
|
-
)
|
|
361
|
-
await resumingStream.result
|
|
362
|
-
|
|
363
|
-
assert.deepEqual(
|
|
364
|
-
startingEvents.find((event) => event.type === 'status'),
|
|
365
|
-
{
|
|
366
|
-
type: 'status',
|
|
367
|
-
stage: 'starting',
|
|
368
|
-
message: '已创建 PromptX 项目,正在启动第一轮执行。',
|
|
369
|
-
}
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
assert.deepEqual(
|
|
373
|
-
resumingEvents.find((event) => event.type === 'status'),
|
|
374
|
-
{
|
|
375
|
-
type: 'status',
|
|
376
|
-
stage: 'resuming',
|
|
377
|
-
message: '已连接 PromptX 项目,正在继续这轮执行。',
|
|
378
|
-
}
|
|
379
|
-
)
|
|
380
|
-
}
|
|
381
|
-
)
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
test('streamPromptToCodexSession includes full-access and repo-check bypass args by default', async () => {
|
|
385
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-args-'))
|
|
386
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
387
|
-
|
|
388
|
-
await withEnv(
|
|
389
|
-
{
|
|
390
|
-
CODEX_HOME: tempHome,
|
|
391
|
-
CODEX_BIN: fakeBin,
|
|
392
|
-
},
|
|
393
|
-
async () => {
|
|
394
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
395
|
-
const stream = streamPromptToCodexSession(
|
|
396
|
-
{ id: 'session-args', cwd: getSessionCwd(), codexThreadId: '' },
|
|
397
|
-
'args-case'
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
const result = await stream.result
|
|
401
|
-
const args = JSON.parse(result.message)
|
|
402
|
-
|
|
403
|
-
assert.deepEqual(
|
|
404
|
-
args.slice(0, 5),
|
|
405
|
-
['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '-C', getSessionCwd()]
|
|
406
|
-
)
|
|
407
|
-
assert.equal(args.at(-2), '--output-last-message')
|
|
408
|
-
}
|
|
409
|
-
)
|
|
410
|
-
})
|
|
411
|
-
|
|
412
|
-
test('streamPromptToCodexSession surfaces stderr failures', async () => {
|
|
413
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-fail-'))
|
|
414
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
415
|
-
|
|
416
|
-
await withEnv(
|
|
417
|
-
{
|
|
418
|
-
CODEX_HOME: tempHome,
|
|
419
|
-
CODEX_BIN: fakeBin,
|
|
420
|
-
},
|
|
421
|
-
async () => {
|
|
422
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
423
|
-
const stream = streamPromptToCodexSession(
|
|
424
|
-
{ id: 'session-xyz', cwd: getSessionCwd() },
|
|
425
|
-
'fail-case'
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
await assert.rejects(() => stream.result, /mocked codex failure/)
|
|
429
|
-
}
|
|
430
|
-
)
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
test('streamPromptToCodexSession prefers structured codex errors over trailing stderr warnings', async () => {
|
|
434
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-disconnect-'))
|
|
435
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
436
|
-
|
|
437
|
-
await withEnv(
|
|
438
|
-
{
|
|
439
|
-
CODEX_HOME: tempHome,
|
|
440
|
-
CODEX_BIN: fakeBin,
|
|
441
|
-
},
|
|
442
|
-
async () => {
|
|
443
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
444
|
-
const stream = streamPromptToCodexSession(
|
|
445
|
-
{ id: 'session-xyz', cwd: getSessionCwd() },
|
|
446
|
-
'disconnect-case'
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
await assert.rejects(
|
|
450
|
-
() => stream.result,
|
|
451
|
-
/stream disconnected before completion: error sending request/
|
|
452
|
-
)
|
|
453
|
-
}
|
|
454
|
-
)
|
|
455
|
-
})
|
|
456
|
-
|
|
457
|
-
test('streamPromptToCodexSession repairs garbled command output', async () => {
|
|
458
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-mojibake-'))
|
|
459
|
-
const fakeBin = createFakeCodexBinary(tempHome)
|
|
460
|
-
|
|
461
|
-
await withEnv(
|
|
462
|
-
{
|
|
463
|
-
CODEX_HOME: tempHome,
|
|
464
|
-
CODEX_BIN: fakeBin,
|
|
465
|
-
},
|
|
466
|
-
async () => {
|
|
467
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
468
|
-
const events = []
|
|
469
|
-
const stream = streamPromptToCodexSession(
|
|
470
|
-
{ id: 'session-xyz', cwd: getSessionCwd() },
|
|
471
|
-
'mojibake-case',
|
|
472
|
-
{
|
|
473
|
-
onEvent(event) {
|
|
474
|
-
events.push(event)
|
|
475
|
-
},
|
|
476
|
-
}
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
const streamResult = await stream.result
|
|
480
|
-
|
|
481
|
-
assert.equal(streamResult.message, '获取测试数据')
|
|
482
|
-
assert.equal(
|
|
483
|
-
events.find((event) => event.type === 'agent_event' && event.event.type === 'item.completed')?.event?.item?.aggregated_output,
|
|
484
|
-
'获取测试数据'
|
|
485
|
-
)
|
|
486
|
-
}
|
|
487
|
-
)
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
test('Windows resolves codex.cmd when CODEX_BIN is omitted', async (t) => {
|
|
491
|
-
if (process.platform !== 'win32') {
|
|
492
|
-
t.skip('Windows-only validation')
|
|
493
|
-
return
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'promptx-codex-win-'))
|
|
497
|
-
createWindowsCodexCommand(tempHome)
|
|
498
|
-
|
|
499
|
-
await withEnv(
|
|
500
|
-
{
|
|
501
|
-
CODEX_HOME: tempHome,
|
|
502
|
-
CODEX_BIN: undefined,
|
|
503
|
-
PATH: `${tempHome};${process.env.PATH || ''}`,
|
|
504
|
-
},
|
|
505
|
-
async () => {
|
|
506
|
-
const { streamPromptToCodexSession } = await importFreshCodexModule()
|
|
507
|
-
const stream = streamPromptToCodexSession(
|
|
508
|
-
{ id: 'session-win', cwd: 'D:\\code\\promptx' },
|
|
509
|
-
'hello from windows'
|
|
510
|
-
)
|
|
511
|
-
const result = await stream.result
|
|
512
|
-
|
|
513
|
-
assert.equal(result.sessionId, 'session-win')
|
|
514
|
-
assert.equal(result.threadId, 'thread-win')
|
|
515
|
-
assert.match(result.message, /prompt:hello from windows/)
|
|
516
|
-
}
|
|
517
|
-
)
|
|
518
|
-
})
|