@naarang/ccc 1.2.0-beta.9 → 1.2.0

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.
@@ -326,10 +326,13 @@ async function requestPermission(hookInput, projectId) {
326
326
  const client = mqtt.connect(connectUrl, connectOptions);
327
327
  let resolved = false;
328
328
  let timeoutHandle = null;
329
+ let ackTimeoutHandle = null;
330
+ let ackReceived = false;
329
331
 
330
332
  // Topics for communication
331
333
  const requestTopic = `permissions/${projectId}/request`;
332
334
  const responseTopic = `permissions/${projectId}/response`;
335
+ const ackTopic = `permissions/${projectId}/ack`;
333
336
 
334
337
  /**
335
338
  * Cleanup and resolve/reject helper
@@ -342,6 +345,10 @@ async function requestPermission(hookInput, projectId) {
342
345
  clearTimeout(timeoutHandle);
343
346
  }
344
347
 
348
+ if (ackTimeoutHandle) {
349
+ clearTimeout(ackTimeoutHandle);
350
+ }
351
+
345
352
  // Clean disconnect
346
353
  client.end(true, {}, () => {
347
354
  callback();
@@ -363,46 +370,108 @@ async function requestPermission(hookInput, projectId) {
363
370
  client.on('connect', () => {
364
371
  log('Connected to MQTT broker');
365
372
 
366
- // Subscribe to response topic
367
- client.subscribe(responseTopic, { qos: MQTT_CONFIG.qos }, (err) => {
373
+ // Subscribe to ACK topic first
374
+ client.subscribe(ackTopic, { qos: MQTT_CONFIG.qos }, (err) => {
368
375
  if (err) {
369
- log('Failed to subscribe to response topic', { error: err.message });
376
+ log('Failed to subscribe to ACK topic', { error: err.message });
370
377
  cleanup(() => {
371
- reject(new Error(`Failed to subscribe: ${err.message}`));
378
+ reject(new Error(`Failed to subscribe to ACK: ${err.message}`));
372
379
  });
373
380
  return;
374
381
  }
375
382
 
376
- log('Subscribed to response topic', { topic: responseTopic });
377
-
378
- // Publish permission request
379
- const permissionRequest = {
380
- tool_name,
381
- tool_input,
382
- session_id,
383
- cwd,
384
- timestamp: Date.now(),
385
- project_id: projectId,
386
- };
383
+ log('Subscribed to ACK topic', { topic: ackTopic });
387
384
 
388
- const payload = JSON.stringify(permissionRequest);
389
-
390
- client.publish(requestTopic, payload, { qos: MQTT_CONFIG.qos, retain: false }, (err) => {
385
+ // Subscribe to response topic
386
+ client.subscribe(responseTopic, { qos: MQTT_CONFIG.qos }, (err) => {
391
387
  if (err) {
392
- log('Failed to publish permission request', { error: err.message });
388
+ log('Failed to subscribe to response topic', { error: err.message });
393
389
  cleanup(() => {
394
- reject(new Error(`Failed to publish: ${err.message}`));
390
+ reject(new Error(`Failed to subscribe: ${err.message}`));
395
391
  });
396
392
  return;
397
393
  }
398
394
 
399
- log('Published permission request', { topic: requestTopic });
395
+ log('Subscribed to response topic', { topic: responseTopic });
396
+
397
+ // Publish permission request
398
+ const permissionRequest = {
399
+ tool_name,
400
+ tool_input,
401
+ session_id,
402
+ cwd,
403
+ timestamp: Date.now(),
404
+ project_id: projectId,
405
+ };
406
+
407
+ const payload = JSON.stringify(permissionRequest);
408
+
409
+ client.publish(requestTopic, payload, { qos: MQTT_CONFIG.qos, retain: false }, (err) => {
410
+ if (err) {
411
+ log('Failed to publish permission request', { error: err.message });
412
+ cleanup(() => {
413
+ reject(new Error(`Failed to publish: ${err.message}`));
414
+ });
415
+ return;
416
+ }
417
+
418
+ log('Published permission request', { topic: requestTopic });
419
+
420
+ // Send notification to all devices
421
+ const notificationTopic = `notifications/${projectId}/permission`;
422
+ const notificationPayload = JSON.stringify({
423
+ type: 'permission_request',
424
+ tool_name,
425
+ tool_input,
426
+ timestamp: Date.now(),
427
+ project_id: projectId,
428
+ });
429
+
430
+ client.publish(notificationTopic, notificationPayload, { qos: MQTT_CONFIG.qos }, (notifErr) => {
431
+ if (notifErr) {
432
+ log('Failed to publish notification', { error: notifErr.message });
433
+ // Don't fail the whole request if notification fails
434
+ } else {
435
+ log('Published permission notification', { topic: notificationTopic });
436
+ }
437
+ });
438
+
439
+ // Set ACK timeout (2 seconds)
440
+ ackTimeoutHandle = setTimeout(() => {
441
+ if (!ackReceived) {
442
+ log('ACK not received within 2 seconds - user likely not on mobile app');
443
+ cleanup(() => {
444
+ // Exit without response - Claude will continue without permission
445
+ process.exit(0);
446
+ });
447
+ }
448
+ }, 5000);
449
+ });
400
450
  });
401
451
  });
402
452
  });
403
453
 
404
- // Handle incoming messages (response from frontend)
454
+ // Handle incoming messages (ACK and response from frontend)
405
455
  client.on('message', (topic, payload) => {
456
+ // Handle ACK
457
+ if (topic === ackTopic) {
458
+ try {
459
+ const ack = JSON.parse(payload.toString());
460
+ log('Received ACK from mobile app', ack);
461
+ ackReceived = true;
462
+
463
+ // Clear ACK timeout since we received it
464
+ if (ackTimeoutHandle) {
465
+ clearTimeout(ackTimeoutHandle);
466
+ ackTimeoutHandle = null;
467
+ }
468
+ } catch (error) {
469
+ log('Failed to parse ACK', { error: error.message });
470
+ }
471
+ return;
472
+ }
473
+
474
+ // Handle response
406
475
  if (topic !== responseTopic) return;
407
476
 
408
477
  try {
@@ -552,11 +621,11 @@ async function main() {
552
621
  // Get project ID from session mapping file
553
622
  let projectId = getProjectIdFromSession(hookInput.session_id);
554
623
 
555
- // CRITICAL: If session is not in sessions.json, this is a native Claude Code session
556
- // (not managed by CCC backend). Pass through without MQTT check to avoid hanging.
624
+ // If no project ID found, we can't route the permission request via MQTT
625
+ // This could happen if the session was started from CLI without backend
626
+ // In this case, exit without blocking (allow the operation)
557
627
  if (!projectId) {
558
- log('Session not found in sessions.json - this is a native Claude Code session');
559
- log('Passing through to use Claude Code\'s native permission system');
628
+ log('Session not found in sessions.json - allowing operation without permission check');
560
629
  process.exit(0);
561
630
  }
562
631