cliclaw 1.0.15 → 1.0.17
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 +33 -15
package/package.json
CHANGED
package/src/agents/codex.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'child_process'
|
|
2
|
-
import type { Session } from '../storage'
|
|
2
|
+
import type { Session, Message } from '../storage'
|
|
3
3
|
import type { TokenUsage } from './claude'
|
|
4
4
|
|
|
5
5
|
const HOME = process.env.HOME || process.env.USERPROFILE || '/root'
|
|
@@ -16,26 +16,40 @@ const BASE_ENV = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
function extractTextFromObj(obj: any): string | null {
|
|
19
|
-
//
|
|
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
|
|
20
22
|
if (obj.type === 'item.completed' && obj.item?.type === 'agent_message' && obj.item?.text)
|
|
21
23
|
return obj.item.text
|
|
22
|
-
//
|
|
24
|
+
// OpenAI Responses API: response.output_item.done with message content array
|
|
23
25
|
if (obj.item?.type === 'message' && Array.isArray(obj.item?.content)) {
|
|
24
26
|
const parts = obj.item.content
|
|
25
27
|
.filter((c: any) => c.type === 'output_text' && c.text)
|
|
26
28
|
.map((c: any) => c.text)
|
|
27
29
|
if (parts.length > 0) return parts.join('')
|
|
28
30
|
}
|
|
29
|
-
//
|
|
31
|
+
// Fallback: top-level result/output
|
|
30
32
|
if (typeof obj.result === 'string' && obj.result) return obj.result
|
|
31
33
|
if (typeof obj.output === 'string' && obj.output) return obj.output
|
|
32
|
-
if (typeof obj.text === 'string' && obj.text && obj.type !== 'thread.started') return obj.text
|
|
33
|
-
// Format 4: message with content string
|
|
34
|
-
if (obj.type === 'message' && typeof obj.content === 'string' && obj.content) return obj.content
|
|
35
34
|
return null
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
function
|
|
37
|
+
function buildPrompt(history: Message[], userMessage: string): string {
|
|
38
|
+
// history already contains the current user message as the last item (added before askCodex)
|
|
39
|
+
const prior = history.slice(0, -1).slice(-20) // up to last 20 prior messages
|
|
40
|
+
if (prior.length === 0) return userMessage
|
|
41
|
+
const lines = prior.map(m => {
|
|
42
|
+
let content = m.content
|
|
43
|
+
// Strip usage stats suffix (e.g. "\n`📊 ↑1234 ↓56`")
|
|
44
|
+
const usageIdx = content.lastIndexOf('\n`📊')
|
|
45
|
+
if (usageIdx > 0) content = content.slice(0, usageIdx)
|
|
46
|
+
if (content.length > 600) content = content.slice(0, 600) + '…'
|
|
47
|
+
return `${m.role === 'user' ? 'User' : 'Assistant'}: ${content}`
|
|
48
|
+
})
|
|
49
|
+
return `<conversation_history>\n${lines.join('\n')}\n</conversation_history>\n\nUser: ${userMessage}`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function spawnCodex(args: string[], stdinText?: string): Promise<{ text: string; threadId: string | null; usage?: TokenUsage }> {
|
|
39
53
|
return new Promise((resolve, reject) => {
|
|
40
54
|
let stdout = ''
|
|
41
55
|
let stderr = ''
|
|
@@ -45,8 +59,12 @@ function spawnCodex(args: string[]): Promise<{ text: string; threadId: string |
|
|
|
45
59
|
cwd: process.cwd(),
|
|
46
60
|
shell: isWin,
|
|
47
61
|
windowsHide: true,
|
|
48
|
-
|
|
62
|
+
// Use pipe for stdin when we have text to send; otherwise ignore to prevent PM2 hang
|
|
63
|
+
stdio: [stdinText != null ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
49
64
|
})
|
|
65
|
+
if (stdinText != null) {
|
|
66
|
+
proc.stdin!.end(stdinText, 'utf8')
|
|
67
|
+
}
|
|
50
68
|
proc.stdout.on('data', (d: Buffer) => { stdout += d.toString() })
|
|
51
69
|
proc.stderr.on('data', (d: Buffer) => {
|
|
52
70
|
const s = d.toString().trim()
|
|
@@ -102,12 +120,12 @@ export async function askCodex(
|
|
|
102
120
|
onNewThreadId?: (id: string) => void
|
|
103
121
|
): Promise<{ text: string; usage?: TokenUsage }> {
|
|
104
122
|
try {
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const { text, threadId, usage } = await spawnCodex(args)
|
|
110
|
-
if (threadId && !codexThreadId) onNewThreadId?.(threadId)
|
|
123
|
+
// Build full prompt with conversation history, send via stdin (avoids Windows arg escaping issues)
|
|
124
|
+
const prompt = buildPrompt(session.history, userMessage)
|
|
125
|
+
// `-` tells codex exec to read PROMPT from stdin
|
|
126
|
+
const args = ['exec', '--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check', '--json', '-']
|
|
127
|
+
const { text, threadId, usage } = await spawnCodex(args, prompt)
|
|
128
|
+
if (threadId && !session.codexThreadId) onNewThreadId?.(threadId)
|
|
111
129
|
return { text, usage }
|
|
112
130
|
} catch (err: any) {
|
|
113
131
|
return { text: `❌ Codex error: ${err.message}` }
|