cliclaw 1.0.14 → 1.0.16
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/package.json +1 -1
- package/src/agents/codex.ts +39 -11
package/package.json
CHANGED
package/src/agents/codex.ts
CHANGED
|
@@ -15,9 +15,29 @@ const BASE_ENV = {
|
|
|
15
15
|
LANG: 'en_US.UTF-8',
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function extractTextFromObj(obj: any): string | null {
|
|
19
|
+
// New codex format: {"id":"...","msg":{"type":"agent_message","text":"..."}}
|
|
20
|
+
if (obj.msg?.type === 'agent_message' && obj.msg?.text) return obj.msg.text
|
|
21
|
+
// Old format: item.completed with agent_message
|
|
22
|
+
if (obj.type === 'item.completed' && obj.item?.type === 'agent_message' && obj.item?.text)
|
|
23
|
+
return obj.item.text
|
|
24
|
+
// OpenAI Responses API: response.output_item.done with message content array
|
|
25
|
+
if (obj.item?.type === 'message' && Array.isArray(obj.item?.content)) {
|
|
26
|
+
const parts = obj.item.content
|
|
27
|
+
.filter((c: any) => c.type === 'output_text' && c.text)
|
|
28
|
+
.map((c: any) => c.text)
|
|
29
|
+
if (parts.length > 0) return parts.join('')
|
|
30
|
+
}
|
|
31
|
+
// Fallback: top-level result/output
|
|
32
|
+
if (typeof obj.result === 'string' && obj.result) return obj.result
|
|
33
|
+
if (typeof obj.output === 'string' && obj.output) return obj.output
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
function spawnCodex(args: string[]): Promise<{ text: string; threadId: string | null; usage?: TokenUsage }> {
|
|
19
38
|
return new Promise((resolve, reject) => {
|
|
20
39
|
let stdout = ''
|
|
40
|
+
let stderr = ''
|
|
21
41
|
const isWin = process.platform === 'win32'
|
|
22
42
|
const proc = spawn('codex', args, {
|
|
23
43
|
env: { ...process.env, ...BASE_ENV },
|
|
@@ -29,7 +49,7 @@ function spawnCodex(args: string[]): Promise<{ text: string; threadId: string |
|
|
|
29
49
|
proc.stdout.on('data', (d: Buffer) => { stdout += d.toString() })
|
|
30
50
|
proc.stderr.on('data', (d: Buffer) => {
|
|
31
51
|
const s = d.toString().trim()
|
|
32
|
-
if (s) console.error('[Codex stderr]', s)
|
|
52
|
+
if (s) { stderr += s + '\n'; console.error('[Codex stderr]', s) }
|
|
33
53
|
})
|
|
34
54
|
proc.on('close', (code) => {
|
|
35
55
|
const lines = stdout.split('\n').filter(l => l.trim())
|
|
@@ -42,8 +62,8 @@ function spawnCodex(args: string[]): Promise<{ text: string; threadId: string |
|
|
|
42
62
|
const obj = JSON.parse(line)
|
|
43
63
|
if (obj.type === 'thread.started' && obj.thread_id)
|
|
44
64
|
threadId = obj.thread_id
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
const extracted = extractTextFromObj(obj)
|
|
66
|
+
if (extracted) texts.push(extracted)
|
|
47
67
|
// Codex/OpenAI usage events (response.completed carries usage)
|
|
48
68
|
const u = obj.usage ?? obj.response?.usage ?? obj.item?.usage
|
|
49
69
|
if (u && (u.input_tokens != null || u.prompt_tokens != null)) {
|
|
@@ -57,9 +77,19 @@ function spawnCodex(args: string[]): Promise<{ text: string; threadId: string |
|
|
|
57
77
|
} catch {}
|
|
58
78
|
}
|
|
59
79
|
|
|
60
|
-
if (texts.length > 0)
|
|
61
|
-
|
|
62
|
-
else
|
|
80
|
+
if (texts.length > 0) {
|
|
81
|
+
resolve({ text: texts.join('\n'), threadId, usage })
|
|
82
|
+
} else if (code !== 0) {
|
|
83
|
+
reject(new Error(`Codex exited with code ${code}${stderr ? ': ' + stderr.trim() : ''}`))
|
|
84
|
+
} else {
|
|
85
|
+
// Log raw output to help diagnose format issues
|
|
86
|
+
if (stdout.trim()) console.error('[Codex no-parse] raw stdout:', stdout.trim().slice(0, 500))
|
|
87
|
+
if (stderr.trim()) console.error('[Codex no-parse] stderr:', stderr.trim().slice(0, 500))
|
|
88
|
+
// Fall back to raw stdout if it's plain text (not JSON)
|
|
89
|
+
const raw = stdout.trim()
|
|
90
|
+
const looksLikeJson = raw.startsWith('{') || raw.startsWith('[')
|
|
91
|
+
resolve({ text: raw && !looksLikeJson ? raw : '[no response]', threadId, usage })
|
|
92
|
+
}
|
|
63
93
|
})
|
|
64
94
|
proc.on('error', reject)
|
|
65
95
|
})
|
|
@@ -71,12 +101,10 @@ export async function askCodex(
|
|
|
71
101
|
onNewThreadId?: (id: string) => void
|
|
72
102
|
): Promise<{ text: string; usage?: TokenUsage }> {
|
|
73
103
|
try {
|
|
74
|
-
|
|
75
|
-
const args =
|
|
76
|
-
? ['exec', 'resume', codexThreadId, '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--json', userMessage]
|
|
77
|
-
: ['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--json', userMessage]
|
|
104
|
+
// Always start fresh — codex exec resume <id> <message> rejects the message as unexpected arg
|
|
105
|
+
const args = ['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--json', userMessage]
|
|
78
106
|
const { text, threadId, usage } = await spawnCodex(args)
|
|
79
|
-
if (threadId && !codexThreadId) onNewThreadId?.(threadId)
|
|
107
|
+
if (threadId && !session.codexThreadId) onNewThreadId?.(threadId)
|
|
80
108
|
return { text, usage }
|
|
81
109
|
} catch (err: any) {
|
|
82
110
|
return { text: `❌ Codex error: ${err.message}` }
|