chatcc-agent 0.5.1 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatcc-agent",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "CCLink Agent - bridges Claude Code CLI with instant messaging",
5
5
  "bin": {
6
6
  "chatcc": "src/cli.js"
@@ -7,6 +7,8 @@ const path = require('path');
7
7
  const { StreamBuffer } = require('./stream-buffer');
8
8
  const { ProcessQueue } = require('./process-queue');
9
9
  const { PERMISSION_MODES, PERMISSION_ACTIONS } = require('./constants');
10
+ const { startTrace, getTraceId } = require('./trace');
11
+ const logUploader = require('./log-uploader');
10
12
 
11
13
  class ClaudeBridge {
12
14
 
@@ -219,6 +221,7 @@ class ClaudeBridge {
219
221
  });
220
222
  this.imClient.sendCustomMessage(replyTo, 'agent_tool', {
221
223
  msg_id: msgID, session_id: sessionID,
224
+ request_id: entry.requestID || msgID,
222
225
  tool: toolName, input: toolInput,
223
226
  tool_use_id: requestId,
224
227
  state: 'denied', workspace_violation: true,
@@ -251,6 +254,7 @@ class ClaudeBridge {
251
254
 
252
255
  this.imClient.sendCustomMessage(replyTo, 'agent_tool', {
253
256
  msg_id: msgID, session_id: sessionID,
257
+ request_id: entry.requestID || msgID,
254
258
  tool: toolName, input: toolInput,
255
259
  tool_use_id: clientKey,
256
260
  state: 'pending', requires_approval: true,
@@ -306,13 +310,36 @@ class ClaudeBridge {
306
310
  const msgID = crypto.randomUUID();
307
311
  const replyTo = from;
308
312
  const images = imagePaths || [];
313
+ const requestID = options.requestId || msgID;
314
+ const traceID = options.traceId || getTraceId() || requestID;
315
+ logUploader.collect('INFO', '[ChatTrace] queued user_text', {
316
+ tag: 'ChatTrace',
317
+ request_id: requestID,
318
+ session_id: sessionID,
319
+ msg_id: msgID,
320
+ });
309
321
 
310
322
  // Send stream_start when the task actually starts (not on message receipt),
311
323
  // so that rapid-fire messages don't have their stream_start ignored
312
324
  // by the client's StreamManager which keys on sessionID.
313
325
  const taskFn = async () => {
314
- await this.imClient.sendCustomMessage(replyTo, 'stream_start', { msg_id: msgID, session_id: sessionID });
315
- return this._spawnClaude(msgID, replyTo, content, sessionID, { cwd, claudeSessionId, images, onClaudeSessionId, onResumeFailed, onSessionTitle, workspaceRestricted, permissions });
326
+ return startTrace(traceID, async () => {
327
+ logUploader.collect('INFO', '[ChatTrace] started claude', {
328
+ tag: 'ChatTrace',
329
+ request_id: requestID,
330
+ session_id: sessionID,
331
+ msg_id: msgID,
332
+ });
333
+ await this.imClient.sendCustomMessage(replyTo, 'stream_start', {
334
+ msg_id: msgID,
335
+ session_id: sessionID,
336
+ request_id: requestID,
337
+ });
338
+ return this._spawnClaude(msgID, replyTo, content, sessionID, {
339
+ cwd, claudeSessionId, images, onClaudeSessionId, onResumeFailed, onSessionTitle,
340
+ workspaceRestricted, permissions, requestId: requestID,
341
+ });
342
+ });
316
343
  };
317
344
 
318
345
  try {
@@ -321,6 +348,7 @@ class ClaudeBridge {
321
348
  console.error('[Bridge] Queue error:', err.message, err.stack);
322
349
  await this.imClient.sendCustomMessage(replyTo, 'stream_end', {
323
350
  msg_id: msgID,
351
+ request_id: requestID,
324
352
  exit_code: 1,
325
353
  session_id: sessionID,
326
354
  error: err.message,
@@ -464,7 +492,7 @@ class ClaudeBridge {
464
492
 
465
493
  _spawnClaude(msgID, replyTo, content, sessionID, options = {}) {
466
494
  return new Promise((resolve, reject) => {
467
- const { cwd, claudeSessionId, images, onClaudeSessionId, onResumeFailed, onSessionTitle, workspaceRestricted = false, permissions = null } = options;
495
+ const { cwd, claudeSessionId, images, onClaudeSessionId, onResumeFailed, onSessionTitle, workspaceRestricted = false, permissions = null, requestId = msgID } = options;
468
496
  const usePermissionControl = permissions?.defaultMode !== PERMISSION_MODES.AUTO_ALL;
469
497
  const useStreamInput = usePermissionControl;
470
498
  const args = [
@@ -510,10 +538,10 @@ class ClaudeBridge {
510
538
  const sendStreamEnd = async (payload) => {
511
539
  if (streamEndSent) return;
512
540
  streamEndSent = true;
513
- await this.imClient.sendCustomMessage(replyTo, 'stream_end', { msg_id: msgID, session_id: sessionID, ...payload });
541
+ await this.imClient.sendCustomMessage(replyTo, 'stream_end', { msg_id: msgID, session_id: sessionID, request_id: requestId, ...payload });
514
542
  };
515
543
 
516
- this._activeProcesses.set(msgID, { proc, sessionID, cwd: cwd || process.cwd(), workspaceRestricted, usePermissionControl, sendStreamEnd });
544
+ this._activeProcesses.set(msgID, { proc, sessionID, requestID: requestId, cwd: cwd || process.cwd(), workspaceRestricted, usePermissionControl, sendStreamEnd });
517
545
 
518
546
  // In stream-json input mode, send the user message via stdin after spawn.
519
547
  // Build content blocks: text first, then images as image media blocks.
@@ -541,6 +569,7 @@ class ClaudeBridge {
541
569
  const streamBuffer = new StreamBuffer(async (delta) => {
542
570
  await this.imClient.sendCustomMessage(replyTo, 'stream_chunk', {
543
571
  msg_id: msgID,
572
+ request_id: requestId,
544
573
  delta,
545
574
  session_id: sessionID,
546
575
  });
@@ -552,7 +581,7 @@ class ClaudeBridge {
552
581
  if (!line.trim()) return;
553
582
  try {
554
583
  const event = JSON.parse(line);
555
- this._handleEvent(event, replyTo, msgID, streamBuffer, sessionID, onClaudeSessionId, onSessionTitle, cwd, workspaceRestricted, permissions, sendStreamEnd);
584
+ this._handleEvent(event, replyTo, msgID, streamBuffer, sessionID, onClaudeSessionId, onSessionTitle, cwd, workspaceRestricted, permissions, sendStreamEnd, requestId);
556
585
  } catch (e) {}
557
586
  });
558
587
 
@@ -617,7 +646,14 @@ class ClaudeBridge {
617
646
  } catch (e) {
618
647
  console.error('[Bridge] Failed to send stream_end:', e.message, e.stack);
619
648
  }
620
- console.log('[Bridge] Claude Code exited:', code);
649
+ logUploader.collect(code === 0 ? 'INFO' : 'WARN', '[ChatTrace] ended claude', {
650
+ tag: 'ChatTrace',
651
+ request_id: requestId,
652
+ session_id: sessionID,
653
+ msg_id: msgID,
654
+ exit_code: code,
655
+ });
656
+ console.log('[Bridge] Claude Code exited:', code, 'request=', requestId);
621
657
 
622
658
  for (const imgPath of images) {
623
659
  try { fs.unlinkSync(imgPath); } catch (e) {}
@@ -627,13 +663,13 @@ class ClaudeBridge {
627
663
  }); // end Promise wrapper
628
664
  }
629
665
 
630
- _handleEvent(event, replyTo, msgID, streamBuffer, sessionID, onClaudeSessionId, onSessionTitle, cwd, workspaceRestricted = false, permissions = null, sendStreamEnd = null) {
666
+ _handleEvent(event, replyTo, msgID, streamBuffer, sessionID, onClaudeSessionId, onSessionTitle, cwd, workspaceRestricted = false, permissions = null, sendStreamEnd = null, requestId = msgID) {
631
667
  switch (event.type) {
632
668
  case 'system':
633
669
  this._handleSystemEvent(event, onClaudeSessionId);
634
670
  break;
635
671
  case 'assistant':
636
- this._handleAssistantEvent(event, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, sendStreamEnd);
672
+ this._handleAssistantEvent(event, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, sendStreamEnd, requestId);
637
673
  break;
638
674
  case 'sdk_control_request': {
639
675
  const req = event.request;
@@ -656,13 +692,13 @@ class ClaudeBridge {
656
692
  break;
657
693
  }
658
694
  case 'user':
659
- this._handleUserEvent(event, replyTo, msgID, streamBuffer, sessionID);
695
+ this._handleUserEvent(event, replyTo, msgID, streamBuffer, sessionID, requestId);
660
696
  break;
661
697
  case 'result':
662
698
  this._handleResultEvent(event, streamBuffer, onSessionTitle, msgID);
663
699
  break;
664
700
  case 'error':
665
- this._handleErrorEvent(event, replyTo, msgID, sessionID, streamBuffer, sendStreamEnd);
701
+ this._handleErrorEvent(event, replyTo, msgID, sessionID, streamBuffer, sendStreamEnd, requestId);
666
702
  break;
667
703
  default:
668
704
  console.warn(`[Bridge] Unknown Claude event type: ${event.type}`, JSON.stringify(event).slice(0, 200));
@@ -676,7 +712,7 @@ class ClaudeBridge {
676
712
  }
677
713
  }
678
714
 
679
- _handleAssistantEvent(event, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, sendStreamEnd = null) {
715
+ _handleAssistantEvent(event, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, sendStreamEnd = null, requestId = msgID) {
680
716
  const msg = event.message;
681
717
  if (!msg?.content) return;
682
718
  const entry = this._activeProcesses.get(msgID);
@@ -693,6 +729,7 @@ class ClaudeBridge {
693
729
  this.imClient.sendCustomMessage(replyTo, 'user_question', {
694
730
  msg_id: msgID,
695
731
  session_id: sessionID,
732
+ request_id: requestId,
696
733
  tool_use_id: block.id,
697
734
  questions: block.input?.questions || [],
698
735
  }, {
@@ -702,13 +739,13 @@ class ClaudeBridge {
702
739
  ignoreBadge: false,
703
740
  }).catch(e => console.error('[Bridge] user_question:', e.message, e.stack));
704
741
  } else {
705
- this._handleToolUseBlock(block, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, useControl, sendStreamEnd);
742
+ this._handleToolUseBlock(block, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, useControl, sendStreamEnd, requestId);
706
743
  }
707
744
  }
708
745
  }
709
746
  }
710
747
 
711
- _handleToolUseBlock(block, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, useControl, sendStreamEnd = null) {
748
+ _handleToolUseBlock(block, replyTo, msgID, streamBuffer, sessionID, cwd, workspaceRestricted, permissions, useControl, sendStreamEnd = null, requestId = msgID) {
712
749
  streamBuffer.flush();
713
750
 
714
751
  if (useControl) {
@@ -738,6 +775,7 @@ class ClaudeBridge {
738
775
  const payload = {
739
776
  msg_id: msgID,
740
777
  session_id: sessionID,
778
+ request_id: requestId,
741
779
  tool: block.name,
742
780
  input: block.input,
743
781
  tool_use_id: block.id,
@@ -800,7 +838,7 @@ class ClaudeBridge {
800
838
  return diff;
801
839
  }
802
840
 
803
- _handleUserEvent(event, replyTo, msgID, streamBuffer, sessionID) {
841
+ _handleUserEvent(event, replyTo, msgID, streamBuffer, sessionID, requestId = msgID) {
804
842
  const msg = event.message;
805
843
  if (!msg?.content) return;
806
844
  for (const block of msg.content) {
@@ -832,6 +870,7 @@ class ClaudeBridge {
832
870
  const payload = {
833
871
  msg_id: msgID,
834
872
  session_id: sessionID,
873
+ request_id: requestId,
835
874
  tool_use_id: block.tool_use_id,
836
875
  state: block.is_error ? 'failed' : 'completed',
837
876
  output: output.slice(0, 4000),
@@ -878,15 +917,22 @@ class ClaudeBridge {
878
917
  }
879
918
  }
880
919
 
881
- _handleErrorEvent(event, replyTo, msgID, sessionID, streamBuffer, sendStreamEnd = null) {
920
+ _handleErrorEvent(event, replyTo, msgID, sessionID, streamBuffer, sendStreamEnd = null, requestId = msgID) {
882
921
  streamBuffer.flush();
883
922
  streamBuffer.end();
884
923
  const payload = { exit_code: 1, error: event.error || event.message || 'Unknown error' };
885
924
  if (sendStreamEnd) {
886
925
  sendStreamEnd(payload).catch(() => {});
887
926
  } else {
888
- this.imClient.sendCustomMessage(replyTo, 'stream_end', { msg_id: msgID, session_id: sessionID, ...payload }).catch(() => {});
889
- }
927
+ this.imClient.sendCustomMessage(replyTo, 'stream_end', { msg_id: msgID, session_id: sessionID, request_id: requestId, ...payload }).catch(() => {});
928
+ }
929
+ logUploader.collect('ERROR', '[ChatTrace] claude error event', {
930
+ tag: 'ChatTrace',
931
+ request_id: requestId,
932
+ session_id: sessionID,
933
+ msg_id: msgID,
934
+ error: event.error || event.message || 'Unknown error',
935
+ });
890
936
  console.error('[Bridge] Claude error:', event.error || event.message);
891
937
  }
892
938
  }
package/src/index.js CHANGED
@@ -16,6 +16,7 @@ const path = require('path');
16
16
  const os = require('os');
17
17
  const crypto = require('crypto');
18
18
  const logUploader = require('./log-uploader');
19
+ const trace = require('./trace');
19
20
 
20
21
  const isDaemon = process.argv.includes('--daemon');
21
22
  const CHATCC_DIR = path.join(os.homedir(), '.chatcc');
@@ -295,9 +296,20 @@ async function main() {
295
296
  imClient.onMessage(async (from, data) => {
296
297
  // 建立请求级 traceId 上下文:沿用入站 trace_id(跨 hop 续链),没有则生成。
297
298
  // 后续所有出站 IM/HTTP 调用、日志都会自动带上。
298
- require('./trace').enterWithTrace(data && data.trace_id);
299
+ trace.enterWithTrace(data && (data.trace_id || data.request_id));
299
300
  const qs = bridge.getQueueStatus();
300
- console.log(`[Message] from=${from} cc_type=${data.cc_type} queue_active=${qs.activeCount} queue_waiting=${qs.queuedCount}`);
301
+ const requestID = data.request_id || '';
302
+ const traceID = trace.getTraceId() || '';
303
+ console.log(`[Message] from=${from} cc_type=${data.cc_type} request=${requestID} trace=${traceID} queue_active=${qs.activeCount} queue_waiting=${qs.queuedCount}`);
304
+ logUploader.collect('INFO', `[ChatTrace] received cc_type=${data.cc_type}`, {
305
+ tag: 'ChatTrace',
306
+ from,
307
+ cc_type: data.cc_type,
308
+ request_id: requestID,
309
+ session_id: data.session_id || '',
310
+ queue_active: qs.activeCount,
311
+ queue_waiting: qs.queuedCount,
312
+ });
301
313
 
302
314
  // All messages require authenticated sender
303
315
  if (pairedClientIDs.size === 0 || !pairedClientIDs.has(from)) {
@@ -380,7 +392,17 @@ async function main() {
380
392
  }).catch(e => console.error('[Session] Failed to send session_update:', e.message, e.stack));
381
393
  };
382
394
 
383
- const bridgeOpts = { cwd, claudeSessionId: claudeSid, onClaudeSessionId, onResumeFailed, onSessionTitle, workspaceRestricted: isRestricted, permissions: sessionPermissions };
395
+ const bridgeOpts = {
396
+ cwd,
397
+ claudeSessionId: claudeSid,
398
+ onClaudeSessionId,
399
+ onResumeFailed,
400
+ onSessionTitle,
401
+ workspaceRestricted: isRestricted,
402
+ permissions: sessionPermissions,
403
+ requestId: data.request_id,
404
+ traceId: traceID,
405
+ };
384
406
  if (imageUrls.length > 0) {
385
407
  Promise.all(imageUrls.map(u => downloadImage(u, tempFiles).catch(e => {
386
408
  console.error('[Image] Download failed for', u, ':', e.message, e.stack);