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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/agents/codex.ts +39 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cliclaw",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Telegram bot bridging AI CLIs (Claude Code, Codex) to Forum Topics",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -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
- if (obj.type === 'item.completed' && obj.item?.type === 'agent_message' && obj.item?.text)
46
- texts.push(obj.item.text)
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) resolve({ text: texts.join('\n'), threadId, usage })
61
- else if (code !== 0) reject(new Error(`Codex exited with code ${code}`))
62
- else resolve({ text: '[no response]', threadId, usage })
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
- const codexThreadId = session.codexThreadId
75
- const args = codexThreadId
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}` }