cliclaw 1.0.33 → 1.0.35

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/index.ts CHANGED
@@ -25,37 +25,40 @@ registerCommands(bot, storage, config)
25
25
  registerMessageHandler(bot, storage, config)
26
26
 
27
27
  // ── Codex exec approval callbacks ──────────────────────────────────────────
28
+ const APPROVAL_PREFIXES: Record<string, { decision: string; label: string }> = {
29
+ capprove: { decision: 'approved', label: '✅ Aprovado' },
30
+ csession: { decision: 'approved_for_session', label: '🔁 Sessão' },
31
+ cdeny: { decision: 'denied', label: '❌ Negado' },
32
+ cabort: { decision: 'abort', label: '🛑 Abortado' },
33
+ }
34
+
28
35
  bot.on('callback_query:data', async (ctx) => {
29
36
  const data = ctx.callbackQuery.data
30
37
  console.log(`[callback] recebido: ${data.slice(0, 60)}`)
31
38
 
32
- if (!data.startsWith('capprove:') && !data.startsWith('cdeny:')) return
33
-
34
- const colonIdx = data.indexOf(':')
35
- const prefix = data.slice(0, colonIdx)
39
+ const colonIdx = data.indexOf(':')
40
+ const prefix = data.slice(0, colonIdx)
36
41
  const approvalId = data.slice(colonIdx + 1)
37
- const approved = prefix === 'capprove'
42
+ const entry = APPROVAL_PREFIXES[prefix]
43
+ if (!entry) return
38
44
 
39
- console.log(`[callback] clicou ${approved ? 'APROVAR' : 'NEGAR'} id=${approvalId.slice(0, 8)}...`)
45
+ console.log(`[callback] clicou ${prefix} id=${approvalId.slice(0, 8)}...`)
40
46
 
41
- const found = respondApproval(approvalId, approved)
42
- console.log(`[callback] respondApproval found=${found}`)
47
+ const found = respondApproval(approvalId, entry.decision)
48
+ console.log(`[callback] respondApproval found=${found} decision=${entry.decision}`)
43
49
 
44
- const label = approved ? '✅ Aprovado' : '❌ Negado'
45
50
  try {
46
- await ctx.answerCallbackQuery({ text: label })
51
+ await ctx.answerCallbackQuery({ text: entry.label })
47
52
  } catch (e: any) {
48
53
  console.error(`[callback] answerCallbackQuery falhou: ${e.message}`)
49
54
  }
50
55
  try {
51
56
  if (found) {
52
- // Get the command text from the original message caption (plain text)
53
57
  const origText = ctx.callbackQuery.message?.text ?? ''
54
- // Replace keyboard with result label — rebuild safe HTML
55
- const cmdLine = origText.split('\n').slice(1).join('\n').trim()
56
- const escaped = cmdLine.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
58
+ const cmdLine = origText.split('\n').slice(1).join('\n').trim()
59
+ const escaped = cmdLine.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
57
60
  await ctx.editMessageText(
58
- `🔧 <b>Codex quer executar:</b>\n<code>${escaped}</code>\n\n${label}`,
61
+ `🔧 <b>Codex quer executar:</b>\n<code>${escaped}</code>\n\n${entry.label}`,
59
62
  { parse_mode: 'HTML' }
60
63
  )
61
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cliclaw",
3
- "version": "1.0.33",
3
+ "version": "1.0.35",
4
4
  "description": "Telegram bot bridging AI CLIs (Claude Code, Codex) to Forum Topics",
5
5
  "main": "index.ts",
6
6
  "scripts": {
@@ -31,7 +31,7 @@ interface ProtoSession {
31
31
  lastUsed: number
32
32
  pending: Map<string, PendingReq>
33
33
  buf: string // incomplete stdout line buffer
34
- approvalHandler?: (callId: string, commandStr: string) => Promise<boolean>
34
+ approvalHandler?: (callId: string, commandStr: string) => Promise<string>
35
35
  }
36
36
 
37
37
  // Map from cliclaw session.id → ProtoSession
@@ -126,15 +126,15 @@ function handleProtoEvent(ps: ProtoSession, obj: any) {
126
126
 
127
127
  // ── exec approval request ───────────────────────────────────────────────────
128
128
  if (msg?.type === 'exec_approval_request') {
129
- const callId = msg.call_id ?? msgId
129
+ const subId = msg.sub_id ?? msg.call_id ?? msgId
130
130
  const commandStr = formatApprovalCommand(msg.command ?? msg.cmd)
131
- console.log(`[Codex approval] request recebido call_id=${callId} cmd="${commandStr.slice(0, 80)}"`)
132
- const respond = (approved: boolean) => {
133
- console.log(`[Codex approval] enviando exec_approval call_id=${callId} approved=${approved}`)
131
+ console.log(`[Codex approval] request recebido sub_id=${subId} cmd="${commandStr.slice(0, 80)}"`)
132
+ const respond = (decision: string) => {
133
+ console.log(`[Codex approval] enviando exec_approval sub_id=${subId} decision=${decision}`)
134
134
  try {
135
135
  ps.proc.stdin!.write(JSON.stringify({
136
136
  id: randomUUID(),
137
- op: { type: 'exec_approval', id: callId, decision: approved ? 'approve' : 'deny' },
137
+ op: { type: 'exec_approval', sub_id: subId, decision },
138
138
  }) + '\n')
139
139
  console.log(`[Codex approval] enviado ok`)
140
140
  } catch (e: any) {
@@ -143,15 +143,15 @@ function handleProtoEvent(ps: ProtoSession, obj: any) {
143
143
  }
144
144
  if (ps.approvalHandler) {
145
145
  console.log(`[Codex approval] aguardando usuário via Telegram...`)
146
- ps.approvalHandler(callId, commandStr)
146
+ ps.approvalHandler(subId, commandStr)
147
147
  .then(respond)
148
148
  .catch((e: any) => {
149
149
  console.error(`[Codex approval] handler error: ${e?.message} — negando`)
150
- respond(false)
150
+ respond('denied')
151
151
  })
152
152
  } else {
153
153
  console.log(`[Codex approval] sem handler — auto-aprovando`)
154
- respond(true)
154
+ respond('approved')
155
155
  }
156
156
  return
157
157
  }
@@ -205,7 +205,7 @@ export async function askCodex(
205
205
  session: { id: string; codexThreadId?: string },
206
206
  userMessage: string,
207
207
  _onNewThreadId?: (id: string) => void,
208
- approvalHandler?: (callId: string, commandStr: string) => Promise<boolean>
208
+ approvalHandler?: (callId: string, commandStr: string) => Promise<string>
209
209
  ): Promise<{ text: string; usage?: TokenUsage }> {
210
210
  const ps = getOrStart(session.id)
211
211
  // Update handler every call (chat context may have changed)
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Shared state for pending Codex exec approval requests.
3
3
  * When codex wants to run a shell command, the bot sends a Telegram message
4
- * with Approve/Deny buttons. The user's answer is routed back here.
4
+ * with Approve/Deny/Session/Abort buttons. The user's answer is routed back here.
5
5
  */
6
6
 
7
7
  interface PendingApproval {
8
- resolve: (approved: boolean) => void
8
+ resolve: (decision: string) => void
9
9
  timeout: ReturnType<typeof setTimeout>
10
10
  }
11
11
 
@@ -14,25 +14,25 @@ const pending = new Map<string, PendingApproval>()
14
14
  /** Store a pending approval that will resolve when the user responds. */
15
15
  export function registerApproval(
16
16
  id: string,
17
- resolve: (approved: boolean) => void,
17
+ resolve: (decision: string) => void,
18
18
  ttlMs = 5 * 60 * 1000 // auto-deny after 5 min
19
19
  ): void {
20
20
  console.log(`[approvals] registrado id=${id.slice(0, 8)}... total pendentes=${pending.size + 1}`)
21
21
  const timeout = setTimeout(() => {
22
22
  console.log(`[approvals] timeout id=${id.slice(0, 8)}... auto-negando`)
23
- respondApproval(id, false)
23
+ respondApproval(id, 'denied')
24
24
  }, ttlMs)
25
25
  pending.set(id, { resolve, timeout })
26
26
  }
27
27
 
28
- /** Called by the callback-query handler when the user clicks Approve/Deny. */
29
- export function respondApproval(id: string, approved: boolean): boolean {
28
+ /** Called by the callback-query handler when the user clicks a decision button. */
29
+ export function respondApproval(id: string, decision: string): boolean {
30
30
  const found = pending.has(id)
31
- console.log(`[approvals] respondApproval id=${id.slice(0, 8)}... approved=${approved} found=${found} total=${pending.size}`)
31
+ console.log(`[approvals] respondApproval id=${id.slice(0, 8)}... decision=${decision} found=${found} total=${pending.size}`)
32
32
  const p = pending.get(id)
33
33
  if (!p) return false
34
34
  clearTimeout(p.timeout)
35
35
  pending.delete(id)
36
- p.resolve(approved)
36
+ p.resolve(decision)
37
37
  return true
38
38
  }
@@ -113,13 +113,19 @@ async function processMessage(ctx: Context, storage: Storage, config: Config) {
113
113
  {
114
114
  ...replyOpts,
115
115
  parse_mode: 'HTML',
116
- reply_markup: { inline_keyboard: [[
117
- { text: '✅ Aprovar', callback_data: `capprove:${approvalId}` },
118
- { text: ' Negar', callback_data: `cdeny:${approvalId}` },
119
- ]] },
116
+ reply_markup: { inline_keyboard: [
117
+ [
118
+ { text: ' Aprovar', callback_data: `capprove:${approvalId}` },
119
+ { text: '🔁 Sessão', callback_data: `csession:${approvalId}` },
120
+ ],
121
+ [
122
+ { text: '❌ Negar', callback_data: `cdeny:${approvalId}` },
123
+ { text: '🛑 Abortar', callback_data: `cabort:${approvalId}` },
124
+ ],
125
+ ] },
120
126
  }
121
127
  ).catch(() => {})
122
- return new Promise<boolean>((resolve) => registerApproval(approvalId, resolve))
128
+ return new Promise<string>((resolve) => registerApproval(approvalId, resolve))
123
129
  }
124
130
  : undefined
125
131