cliclaw 1.0.34 → 1.0.36
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 +18 -15
- package/package.json +1 -1
- package/src/agents/codex.ts +33 -16
- package/src/handlers/approvals.ts +8 -8
- package/src/handlers/messages.ts +11 -5
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 aprovada!' },
|
|
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
|
-
|
|
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
|
|
42
|
+
const entry = APPROVAL_PREFIXES[prefix]
|
|
43
|
+
if (!entry) return
|
|
38
44
|
|
|
39
|
-
console.log(`[callback] clicou ${
|
|
45
|
+
console.log(`[callback] clicou ${prefix} id=${approvalId.slice(0, 8)}...`)
|
|
40
46
|
|
|
41
|
-
const found = respondApproval(approvalId,
|
|
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
|
-
|
|
55
|
-
const
|
|
56
|
-
const escaped = cmdLine.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
58
|
+
const cmdLine = origText.split('\n').slice(1).join('\n').trim()
|
|
59
|
+
const escaped = cmdLine.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
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
package/src/agents/codex.ts
CHANGED
|
@@ -26,12 +26,13 @@ interface PendingReq {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
interface ProtoSession {
|
|
29
|
-
proc:
|
|
30
|
-
sessionId:
|
|
31
|
-
lastUsed:
|
|
32
|
-
pending:
|
|
33
|
-
buf:
|
|
34
|
-
approvalHandler?: (callId: string, commandStr: string) => Promise<
|
|
29
|
+
proc: ChildProcess
|
|
30
|
+
sessionId: string
|
|
31
|
+
lastUsed: number
|
|
32
|
+
pending: Map<string, PendingReq>
|
|
33
|
+
buf: string // incomplete stdout line buffer
|
|
34
|
+
approvalHandler?: (callId: string, commandStr: string) => Promise<string>
|
|
35
|
+
sessionApproved: boolean // true after user clicks "Aprovar Sessão"
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// Map from cliclaw session.id → ProtoSession
|
|
@@ -74,6 +75,7 @@ function startProto(appSessionId: string): ProtoSession {
|
|
|
74
75
|
lastUsed: Date.now(),
|
|
75
76
|
pending: new Map(),
|
|
76
77
|
buf: '',
|
|
78
|
+
sessionApproved: false,
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
proc.stdout!.on('data', (d: Buffer) => {
|
|
@@ -126,32 +128,47 @@ function handleProtoEvent(ps: ProtoSession, obj: any) {
|
|
|
126
128
|
|
|
127
129
|
// ── exec approval request ───────────────────────────────────────────────────
|
|
128
130
|
if (msg?.type === 'exec_approval_request') {
|
|
129
|
-
const
|
|
131
|
+
const subId = msg.sub_id ?? msg.call_id ?? msgId
|
|
130
132
|
const commandStr = formatApprovalCommand(msg.command ?? msg.cmd)
|
|
131
|
-
console.log(`[Codex approval] request recebido
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
console.log(`[Codex approval] request recebido sub_id=${subId} cmd="${commandStr.slice(0, 80)}"`)
|
|
134
|
+
|
|
135
|
+
// Respond using the original message id so Codex can correlate the reply
|
|
136
|
+
const respond = (decision: string) => {
|
|
137
|
+
// Handle app-level session approval — Codex only understands 'approved'/'denied'/'abort'
|
|
138
|
+
if (decision === 'approved_for_session') {
|
|
139
|
+
ps.sessionApproved = true
|
|
140
|
+
decision = 'approved'
|
|
141
|
+
}
|
|
142
|
+
console.log(`[Codex approval] enviando exec_approval sub_id=${subId} decision=${decision}`)
|
|
134
143
|
try {
|
|
135
144
|
ps.proc.stdin!.write(JSON.stringify({
|
|
136
|
-
id:
|
|
137
|
-
op: { type: 'exec_approval',
|
|
145
|
+
id: msgId, // use original event id, not a new UUID
|
|
146
|
+
op: { type: 'exec_approval', sub_id: subId, decision },
|
|
138
147
|
}) + '\n')
|
|
139
148
|
console.log(`[Codex approval] enviado ok`)
|
|
140
149
|
} catch (e: any) {
|
|
141
150
|
console.error(`[Codex approval] erro ao enviar: ${e.message}`)
|
|
142
151
|
}
|
|
143
152
|
}
|
|
153
|
+
|
|
154
|
+
// If the user already approved the whole session, skip the prompt
|
|
155
|
+
if (ps.sessionApproved) {
|
|
156
|
+
console.log(`[Codex approval] sessão aprovada — auto-aprovando`)
|
|
157
|
+
respond('approved')
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
|
|
144
161
|
if (ps.approvalHandler) {
|
|
145
162
|
console.log(`[Codex approval] aguardando usuário via Telegram...`)
|
|
146
|
-
ps.approvalHandler(
|
|
163
|
+
ps.approvalHandler(subId, commandStr)
|
|
147
164
|
.then(respond)
|
|
148
165
|
.catch((e: any) => {
|
|
149
166
|
console.error(`[Codex approval] handler error: ${e?.message} — negando`)
|
|
150
|
-
respond(
|
|
167
|
+
respond('denied')
|
|
151
168
|
})
|
|
152
169
|
} else {
|
|
153
170
|
console.log(`[Codex approval] sem handler — auto-aprovando`)
|
|
154
|
-
respond(
|
|
171
|
+
respond('approved')
|
|
155
172
|
}
|
|
156
173
|
return
|
|
157
174
|
}
|
|
@@ -205,7 +222,7 @@ export async function askCodex(
|
|
|
205
222
|
session: { id: string; codexThreadId?: string },
|
|
206
223
|
userMessage: string,
|
|
207
224
|
_onNewThreadId?: (id: string) => void,
|
|
208
|
-
approvalHandler?: (callId: string, commandStr: string) => Promise<
|
|
225
|
+
approvalHandler?: (callId: string, commandStr: string) => Promise<string>
|
|
209
226
|
): Promise<{ text: string; usage?: TokenUsage }> {
|
|
210
227
|
const ps = getOrStart(session.id)
|
|
211
228
|
// 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: (
|
|
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: (
|
|
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,
|
|
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
|
|
29
|
-
export function respondApproval(id: string,
|
|
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)}...
|
|
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(
|
|
36
|
+
p.resolve(decision)
|
|
37
37
|
return true
|
|
38
38
|
}
|
package/src/handlers/messages.ts
CHANGED
|
@@ -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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
reply_markup: { inline_keyboard: [
|
|
117
|
+
[
|
|
118
|
+
{ text: '✅ Aprovar', callback_data: `capprove:${approvalId}` },
|
|
119
|
+
{ text: '🔓 Aprovar 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<
|
|
128
|
+
return new Promise<string>((resolve) => registerApproval(approvalId, resolve))
|
|
123
129
|
}
|
|
124
130
|
: undefined
|
|
125
131
|
|