codeclaw 0.2.2 → 0.2.3

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.
@@ -410,7 +410,7 @@ export class TelegramBot extends Bot {
410
410
  tp.push(`out: ${fmtTokens(result.outputTokens)}`);
411
411
  tokenBlock = `\n<blockquote expandable>${tp.join(' ')}</blockquote>`;
412
412
  }
413
- const quickReplies = detectQuickReplies(result.message);
413
+ const quickReplies = result.incomplete ? [] : detectQuickReplies(result.message);
414
414
  let keyboard = undefined;
415
415
  if (quickReplies.length) {
416
416
  const rows = [];
@@ -442,8 +442,22 @@ export class TelegramBot extends Bot {
442
442
  display = '...\n' + display.slice(-800);
443
443
  thinkingHtml = `<blockquote><b>${label}</b>\n${escapeHtml(display)}</blockquote>\n\n`;
444
444
  }
445
+ let statusHtml = '';
446
+ if (result.incomplete) {
447
+ const statusLines = [];
448
+ if (result.stopReason === 'max_tokens')
449
+ statusLines.push('Output limit reached. Response may be truncated.');
450
+ if (!result.ok) {
451
+ const detail = result.error?.trim();
452
+ if (detail && detail !== result.message.trim())
453
+ statusLines.push(detail);
454
+ else
455
+ statusLines.push('Agent exited before reporting completion.');
456
+ }
457
+ statusHtml = `<blockquote expandable><b>Incomplete Response</b>\n${statusLines.map(escapeHtml).join('\n')}</blockquote>\n\n`;
458
+ }
445
459
  const bodyHtml = mdToTgHtml(result.message);
446
- const fullHtml = `${thinkingHtml}${bodyHtml}\n\n${meta}${tokenBlock}`;
460
+ const fullHtml = `${statusHtml}${thinkingHtml}${bodyHtml}\n\n${meta}${tokenBlock}`;
447
461
  if (fullHtml.length <= 3900) {
448
462
  try {
449
463
  await this.channel.editMessage(ctx.chatId, phId, fullHtml, { parseMode: 'HTML', keyboard });
@@ -456,7 +470,7 @@ export class TelegramBot extends Bot {
456
470
  let preview = bodyHtml.slice(0, 3200);
457
471
  if (bodyHtml.length > 3200)
458
472
  preview += '\n<i>... (see full response below)</i>';
459
- const previewHtml = `${thinkingHtml}${preview}\n\n${meta}${tokenBlock}`;
473
+ const previewHtml = `${statusHtml}${thinkingHtml}${preview}\n\n${meta}${tokenBlock}`;
460
474
  try {
461
475
  await this.channel.editMessage(ctx.chatId, phId, previewHtml, { parseMode: 'HTML', keyboard });
462
476
  }
package/dist/bot.js CHANGED
@@ -8,7 +8,7 @@ import fs from 'node:fs';
8
8
  import path from 'node:path';
9
9
  import { execSync, spawn } from 'node:child_process';
10
10
  import { doStream, getSessions, listAgents, } from './code-agent.js';
11
- export const VERSION = '0.2.2';
11
+ export const VERSION = '0.2.3';
12
12
  // ---------------------------------------------------------------------------
13
13
  // Helpers
14
14
  // ---------------------------------------------------------------------------
@@ -16,6 +16,7 @@ async function run(cmd, opts, parseLine) {
16
16
  sessionId: opts.sessionId, text: '', thinking: '', msgs: [], thinkParts: [],
17
17
  model: opts.model, thinkingEffort: opts.thinkingEffort, errors: null,
18
18
  inputTokens: null, outputTokens: null, cachedInputTokens: null,
19
+ stopReason: null,
19
20
  };
20
21
  const shellCmd = cmd.map(Q).join(' ');
21
22
  agentLog(`[spawn] cmd: ${shellCmd}`);
@@ -63,7 +64,7 @@ async function run(cmd, opts, parseLine) {
63
64
  }
64
65
  catch { }
65
66
  });
66
- const [ok, code] = await new Promise(resolve => {
67
+ const [procOk, code] = await new Promise(resolve => {
67
68
  proc.on('close', code => { agentLog(`[exit] code=${code} lines_parsed=${lineCount}`); resolve([code === 0, code]); });
68
69
  proc.on('error', e => { agentLog(`[error] ${e.message}`); stderr += e.message; resolve([false, -1]); });
69
70
  });
@@ -71,18 +72,26 @@ async function run(cmd, opts, parseLine) {
71
72
  s.text = s.msgs.join('\n\n');
72
73
  if (!s.thinking.trim() && s.thinkParts.length)
73
74
  s.thinking = s.thinkParts.join('\n\n');
75
+ const ok = procOk && !s.errors;
76
+ const error = s.errors?.map(e => e.trim()).filter(Boolean).join('; ') || (!procOk ? (stderr.trim() || `Failed (exit=${code}).`) : null);
77
+ const incomplete = !ok || s.stopReason === 'max_tokens';
74
78
  const elapsed = ((Date.now() - start) / 1000).toFixed(1);
75
79
  agentLog(`[result] ok=${ok && !s.errors} elapsed=${elapsed}s text=${s.text.length}chars thinking=${s.thinking.length}chars session=${s.sessionId || '?'}`);
76
80
  if (s.errors)
77
81
  agentLog(`[result] errors: ${s.errors.join('; ')}`);
78
- if (stderr.trim() && !ok)
82
+ if (s.stopReason)
83
+ agentLog(`[result] stop_reason=${s.stopReason}`);
84
+ if (stderr.trim() && !procOk)
79
85
  agentLog(`[result] stderr: ${stderr.trim().slice(0, 300)}`);
80
86
  return {
81
- ok: ok && !s.errors, sessionId: s.sessionId, model: s.model, thinkingEffort: s.thinkingEffort,
82
- message: s.errors?.join('; ') || s.text.trim() || (ok ? '(no textual response)' : `Failed (exit=${code}).\n\n${stderr.trim() || '(no output)'}`),
87
+ ok, sessionId: s.sessionId, model: s.model, thinkingEffort: s.thinkingEffort,
88
+ message: s.text.trim() || s.errors?.join('; ') || (procOk ? '(no textual response)' : `Failed (exit=${code}).\n\n${stderr.trim() || '(no output)'}`),
83
89
  thinking: s.thinking.trim() || null,
84
90
  elapsedS: (Date.now() - start) / 1000,
85
91
  inputTokens: s.inputTokens, outputTokens: s.outputTokens, cachedInputTokens: s.cachedInputTokens,
92
+ error,
93
+ stopReason: s.stopReason,
94
+ incomplete,
86
95
  };
87
96
  }
88
97
  // --- codex ---
@@ -171,6 +180,8 @@ function claudeParse(ev, s) {
171
180
  s.text += d.text || '';
172
181
  }
173
182
  if (inner.type === 'message_delta') {
183
+ const d = inner.delta || {};
184
+ s.stopReason = d.stop_reason ?? s.stopReason;
174
185
  const u = inner.usage;
175
186
  if (u) {
176
187
  s.inputTokens = u.input_tokens ?? s.inputTokens;
@@ -182,13 +193,15 @@ function claudeParse(ev, s) {
182
193
  s.model = ev.model ?? s.model;
183
194
  }
184
195
  if (t === 'assistant') {
185
- const contents = (ev.message || {}).content || [];
196
+ const msg = ev.message || {};
197
+ const contents = msg.content || [];
186
198
  const th = contents.filter((b) => b?.type === 'thinking').map((b) => b.thinking || '').join('');
187
199
  const tx = contents.filter((b) => b?.type === 'text').map((b) => b.text || '').join('');
188
200
  if (th && !s.thinking.trim())
189
201
  s.thinking = th;
190
202
  if (tx && !s.text.trim())
191
203
  s.text = tx;
204
+ s.stopReason = msg.stop_reason ?? s.stopReason;
192
205
  }
193
206
  if (t === 'result') {
194
207
  s.sessionId = ev.session_id ?? s.sessionId;
@@ -197,6 +210,7 @@ function claudeParse(ev, s) {
197
210
  s.errors = ev.errors;
198
211
  if (ev.result && !s.text.trim())
199
212
  s.text = ev.result;
213
+ s.stopReason = ev.stop_reason ?? s.stopReason;
200
214
  const u = ev.usage;
201
215
  if (u) {
202
216
  s.inputTokens = u.input_tokens ?? s.inputTokens;
@@ -208,7 +222,8 @@ function claudeParse(ev, s) {
208
222
  export async function doClaudeStream(opts) {
209
223
  const result = await run(claudeCmd(opts), opts, claudeParse);
210
224
  // session not found → retry as new conversation
211
- if (!result.ok && opts.sessionId && /no conversation found/i.test(result.message)) {
225
+ const retryText = `${result.error || ''}\n${result.message}`;
226
+ if (!result.ok && opts.sessionId && /no conversation found/i.test(retryText)) {
212
227
  return run(claudeCmd({ ...opts, sessionId: null }), { ...opts, sessionId: null }, claudeParse);
213
228
  }
214
229
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeclaw",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "The best IM-driven remote coding experience. Bridge AI coding agents to any IM.",
5
5
  "type": "module",
6
6
  "bin": {