happy-coder 0.9.0-1 → 0.9.0-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/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chalk = require('chalk');
4
- var types$1 = require('./types-D8nxJTr8.cjs');
4
+ var types$1 = require('./types-a-nJyP-e.cjs');
5
5
  var node_crypto = require('node:crypto');
6
6
  var node_child_process = require('node:child_process');
7
7
  var node_path = require('node:path');
@@ -1626,10 +1626,17 @@ class PermissionHandler {
1626
1626
  allowedBashLiterals = /* @__PURE__ */ new Set();
1627
1627
  allowedBashPrefixes = /* @__PURE__ */ new Set();
1628
1628
  permissionMode = "default";
1629
+ onPermissionRequestCallback;
1629
1630
  constructor(session) {
1630
1631
  this.session = session;
1631
1632
  this.setupClientHandler();
1632
1633
  }
1634
+ /**
1635
+ * Set callback to trigger when permission request is made
1636
+ */
1637
+ setOnPermissionRequest(callback) {
1638
+ this.onPermissionRequestCallback = callback;
1639
+ }
1633
1640
  handleModeChange(mode) {
1634
1641
  this.permissionMode = mode;
1635
1642
  }
@@ -1725,6 +1732,9 @@ class PermissionHandler {
1725
1732
  toolName,
1726
1733
  input
1727
1734
  });
1735
+ if (this.onPermissionRequestCallback) {
1736
+ this.onPermissionRequestCallback(id);
1737
+ }
1728
1738
  this.session.api.push().sendToAllDevices(
1729
1739
  "Permission Request",
1730
1740
  `Claude wants to ${getToolName(toolName)}`,
@@ -2246,6 +2256,136 @@ class SDKToLogConverter {
2246
2256
  }
2247
2257
  }
2248
2258
 
2259
+ class OutgoingMessageQueue {
2260
+ constructor(sendFunction) {
2261
+ this.sendFunction = sendFunction;
2262
+ }
2263
+ queue = [];
2264
+ nextId = 1;
2265
+ lock = new types$1.AsyncLock();
2266
+ processTimer;
2267
+ delayTimers = /* @__PURE__ */ new Map();
2268
+ /**
2269
+ * Add message to queue
2270
+ */
2271
+ enqueue(logMessage, options) {
2272
+ this.lock.inLock(async () => {
2273
+ const item = {
2274
+ id: this.nextId++,
2275
+ logMessage,
2276
+ delayed: !!options?.delay,
2277
+ delayMs: options?.delay || 0,
2278
+ toolCallIds: options?.toolCallIds,
2279
+ released: !options?.delay,
2280
+ // Not delayed = already released
2281
+ sent: false
2282
+ };
2283
+ this.queue.push(item);
2284
+ if (item.delayed) {
2285
+ const timer = setTimeout(() => {
2286
+ this.releaseItem(item.id);
2287
+ }, item.delayMs);
2288
+ this.delayTimers.set(item.id, timer);
2289
+ }
2290
+ });
2291
+ this.scheduleProcessing();
2292
+ }
2293
+ /**
2294
+ * Release specific item by ID
2295
+ */
2296
+ async releaseItem(itemId) {
2297
+ await this.lock.inLock(async () => {
2298
+ const item = this.queue.find((i) => i.id === itemId);
2299
+ if (item && !item.released) {
2300
+ item.released = true;
2301
+ const timer = this.delayTimers.get(itemId);
2302
+ if (timer) {
2303
+ clearTimeout(timer);
2304
+ this.delayTimers.delete(itemId);
2305
+ }
2306
+ }
2307
+ });
2308
+ this.scheduleProcessing();
2309
+ }
2310
+ /**
2311
+ * Release all messages with specific tool call ID
2312
+ */
2313
+ async releaseToolCall(toolCallId) {
2314
+ await this.lock.inLock(async () => {
2315
+ for (const item of this.queue) {
2316
+ if (item.toolCallIds?.includes(toolCallId) && !item.released) {
2317
+ item.released = true;
2318
+ const timer = this.delayTimers.get(item.id);
2319
+ if (timer) {
2320
+ clearTimeout(timer);
2321
+ this.delayTimers.delete(item.id);
2322
+ }
2323
+ }
2324
+ }
2325
+ });
2326
+ this.scheduleProcessing();
2327
+ }
2328
+ /**
2329
+ * Process queue - send messages in ID order that are released
2330
+ */
2331
+ async processQueue() {
2332
+ await this.lock.inLock(async () => {
2333
+ this.queue.sort((a, b) => a.id - b.id);
2334
+ while (this.queue.length > 0) {
2335
+ const item = this.queue[0];
2336
+ if (!item.released) {
2337
+ break;
2338
+ }
2339
+ if (!item.sent) {
2340
+ if (item.logMessage.type !== "system") {
2341
+ this.sendFunction(item.logMessage);
2342
+ }
2343
+ item.sent = true;
2344
+ }
2345
+ this.queue.shift();
2346
+ }
2347
+ });
2348
+ }
2349
+ /**
2350
+ * Flush all messages immediately (for cleanup)
2351
+ */
2352
+ async flush() {
2353
+ await this.lock.inLock(async () => {
2354
+ for (const timer of this.delayTimers.values()) {
2355
+ clearTimeout(timer);
2356
+ }
2357
+ this.delayTimers.clear();
2358
+ for (const item of this.queue) {
2359
+ item.released = true;
2360
+ }
2361
+ await this.processQueue();
2362
+ });
2363
+ }
2364
+ /**
2365
+ * Schedule processing on next tick
2366
+ */
2367
+ scheduleProcessing() {
2368
+ if (this.processTimer) {
2369
+ clearTimeout(this.processTimer);
2370
+ }
2371
+ this.processTimer = setTimeout(() => {
2372
+ this.processQueue();
2373
+ }, 0);
2374
+ }
2375
+ /**
2376
+ * Cleanup timers and resources
2377
+ */
2378
+ destroy() {
2379
+ if (this.processTimer) {
2380
+ clearTimeout(this.processTimer);
2381
+ }
2382
+ for (const timer of this.delayTimers.values()) {
2383
+ clearTimeout(timer);
2384
+ }
2385
+ this.delayTimers.clear();
2386
+ }
2387
+ }
2388
+
2249
2389
  async function claudeRemoteLauncher(session) {
2250
2390
  types$1.logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2251
2391
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
@@ -2303,6 +2443,12 @@ async function claudeRemoteLauncher(session) {
2303
2443
  session.client.setHandler("abort", doAbort);
2304
2444
  session.client.setHandler("switch", doSwitch);
2305
2445
  const permissionHandler = new PermissionHandler(session);
2446
+ const messageQueue = new OutgoingMessageQueue(
2447
+ (logMessage) => session.client.sendClaudeSessionMessage(logMessage)
2448
+ );
2449
+ permissionHandler.setOnPermissionRequest((toolCallId) => {
2450
+ messageQueue.releaseToolCall(toolCallId);
2451
+ });
2306
2452
  const sdkToLogConverter = new SDKToLogConverter({
2307
2453
  sessionId: session.sessionId || "unknown",
2308
2454
  cwd: session.path,
@@ -2341,6 +2487,7 @@ async function claudeRemoteLauncher(session) {
2341
2487
  for (let c of umessage.message.content) {
2342
2488
  if (c.type === "tool_result" && c.tool_use_id) {
2343
2489
  ongoingToolCalls.delete(c.tool_use_id);
2490
+ messageQueue.releaseToolCall(c.tool_use_id);
2344
2491
  }
2345
2492
  }
2346
2493
  }
@@ -2402,9 +2549,28 @@ async function claudeRemoteLauncher(session) {
2402
2549
  }
2403
2550
  }
2404
2551
  }
2405
- if (logMessage.type !== "system") {
2406
- session.client.sendClaudeSessionMessage(logMessage);
2552
+ if (logMessage.type === "assistant" && message.type === "assistant") {
2553
+ const assistantMsg = message;
2554
+ const toolCallIds = [];
2555
+ if (assistantMsg.message.content && Array.isArray(assistantMsg.message.content)) {
2556
+ for (const block of assistantMsg.message.content) {
2557
+ if (block.type === "tool_use" && block.id) {
2558
+ toolCallIds.push(block.id);
2559
+ }
2560
+ }
2561
+ }
2562
+ if (toolCallIds.length > 0) {
2563
+ const isSidechain = assistantMsg.parent_tool_use_id !== void 0;
2564
+ if (!isSidechain) {
2565
+ messageQueue.enqueue(logMessage, {
2566
+ delay: 250,
2567
+ toolCallIds
2568
+ });
2569
+ return;
2570
+ }
2571
+ }
2407
2572
  }
2573
+ messageQueue.enqueue(logMessage);
2408
2574
  }
2409
2575
  if (message.type === "assistant") {
2410
2576
  let umessage = message;
@@ -2413,7 +2579,7 @@ async function claudeRemoteLauncher(session) {
2413
2579
  if (c.type === "tool_use" && c.name === "Task" && c.input && typeof c.input.prompt === "string") {
2414
2580
  const logMessage2 = sdkToLogConverter.convertSidechainUserMessage(c.id, c.input.prompt);
2415
2581
  if (logMessage2) {
2416
- session.client.sendClaudeSessionMessage(logMessage2);
2582
+ messageQueue.enqueue(logMessage2);
2417
2583
  }
2418
2584
  }
2419
2585
  }
@@ -2504,6 +2670,10 @@ async function claudeRemoteLauncher(session) {
2504
2670
  }
2505
2671
  }
2506
2672
  ongoingToolCalls.clear();
2673
+ types$1.logger.debug("[remote]: flushing message queue");
2674
+ await messageQueue.flush();
2675
+ messageQueue.destroy();
2676
+ types$1.logger.debug("[remote]: message queue flushed");
2507
2677
  abortController = null;
2508
2678
  abortFuture?.resolve(void 0);
2509
2679
  abortFuture = null;
@@ -2577,7 +2747,7 @@ async function loop(opts) {
2577
2747
  }
2578
2748
 
2579
2749
  var name = "happy-coder";
2580
- var version = "0.9.0-1";
2750
+ var version = "0.9.0-3";
2581
2751
  var description = "Claude Code session sharing CLI";
2582
2752
  var author = "Kirill Dubovitskiy";
2583
2753
  var license = "MIT";
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- import { l as logger, b as backoff, d as delay, R as RawJSONLinesSchema, c as configuration, e as encodeBase64, f as encodeBase64Url, g as decodeBase64, A as ApiClient } from './types-CJU8o8xd.mjs';
2
+ import { l as logger, b as backoff, d as delay, R as RawJSONLinesSchema, e as AsyncLock, c as configuration, f as encodeBase64, g as encodeBase64Url, h as decodeBase64, A as ApiClient } from './types-DJOX-XG-.mjs';
3
3
  import { randomUUID, randomBytes } from 'node:crypto';
4
4
  import { spawn, execSync } from 'node:child_process';
5
5
  import { resolve, join, dirname as dirname$1 } from 'node:path';
@@ -1605,10 +1605,17 @@ class PermissionHandler {
1605
1605
  allowedBashLiterals = /* @__PURE__ */ new Set();
1606
1606
  allowedBashPrefixes = /* @__PURE__ */ new Set();
1607
1607
  permissionMode = "default";
1608
+ onPermissionRequestCallback;
1608
1609
  constructor(session) {
1609
1610
  this.session = session;
1610
1611
  this.setupClientHandler();
1611
1612
  }
1613
+ /**
1614
+ * Set callback to trigger when permission request is made
1615
+ */
1616
+ setOnPermissionRequest(callback) {
1617
+ this.onPermissionRequestCallback = callback;
1618
+ }
1612
1619
  handleModeChange(mode) {
1613
1620
  this.permissionMode = mode;
1614
1621
  }
@@ -1704,6 +1711,9 @@ class PermissionHandler {
1704
1711
  toolName,
1705
1712
  input
1706
1713
  });
1714
+ if (this.onPermissionRequestCallback) {
1715
+ this.onPermissionRequestCallback(id);
1716
+ }
1707
1717
  this.session.api.push().sendToAllDevices(
1708
1718
  "Permission Request",
1709
1719
  `Claude wants to ${getToolName(toolName)}`,
@@ -2225,6 +2235,136 @@ class SDKToLogConverter {
2225
2235
  }
2226
2236
  }
2227
2237
 
2238
+ class OutgoingMessageQueue {
2239
+ constructor(sendFunction) {
2240
+ this.sendFunction = sendFunction;
2241
+ }
2242
+ queue = [];
2243
+ nextId = 1;
2244
+ lock = new AsyncLock();
2245
+ processTimer;
2246
+ delayTimers = /* @__PURE__ */ new Map();
2247
+ /**
2248
+ * Add message to queue
2249
+ */
2250
+ enqueue(logMessage, options) {
2251
+ this.lock.inLock(async () => {
2252
+ const item = {
2253
+ id: this.nextId++,
2254
+ logMessage,
2255
+ delayed: !!options?.delay,
2256
+ delayMs: options?.delay || 0,
2257
+ toolCallIds: options?.toolCallIds,
2258
+ released: !options?.delay,
2259
+ // Not delayed = already released
2260
+ sent: false
2261
+ };
2262
+ this.queue.push(item);
2263
+ if (item.delayed) {
2264
+ const timer = setTimeout(() => {
2265
+ this.releaseItem(item.id);
2266
+ }, item.delayMs);
2267
+ this.delayTimers.set(item.id, timer);
2268
+ }
2269
+ });
2270
+ this.scheduleProcessing();
2271
+ }
2272
+ /**
2273
+ * Release specific item by ID
2274
+ */
2275
+ async releaseItem(itemId) {
2276
+ await this.lock.inLock(async () => {
2277
+ const item = this.queue.find((i) => i.id === itemId);
2278
+ if (item && !item.released) {
2279
+ item.released = true;
2280
+ const timer = this.delayTimers.get(itemId);
2281
+ if (timer) {
2282
+ clearTimeout(timer);
2283
+ this.delayTimers.delete(itemId);
2284
+ }
2285
+ }
2286
+ });
2287
+ this.scheduleProcessing();
2288
+ }
2289
+ /**
2290
+ * Release all messages with specific tool call ID
2291
+ */
2292
+ async releaseToolCall(toolCallId) {
2293
+ await this.lock.inLock(async () => {
2294
+ for (const item of this.queue) {
2295
+ if (item.toolCallIds?.includes(toolCallId) && !item.released) {
2296
+ item.released = true;
2297
+ const timer = this.delayTimers.get(item.id);
2298
+ if (timer) {
2299
+ clearTimeout(timer);
2300
+ this.delayTimers.delete(item.id);
2301
+ }
2302
+ }
2303
+ }
2304
+ });
2305
+ this.scheduleProcessing();
2306
+ }
2307
+ /**
2308
+ * Process queue - send messages in ID order that are released
2309
+ */
2310
+ async processQueue() {
2311
+ await this.lock.inLock(async () => {
2312
+ this.queue.sort((a, b) => a.id - b.id);
2313
+ while (this.queue.length > 0) {
2314
+ const item = this.queue[0];
2315
+ if (!item.released) {
2316
+ break;
2317
+ }
2318
+ if (!item.sent) {
2319
+ if (item.logMessage.type !== "system") {
2320
+ this.sendFunction(item.logMessage);
2321
+ }
2322
+ item.sent = true;
2323
+ }
2324
+ this.queue.shift();
2325
+ }
2326
+ });
2327
+ }
2328
+ /**
2329
+ * Flush all messages immediately (for cleanup)
2330
+ */
2331
+ async flush() {
2332
+ await this.lock.inLock(async () => {
2333
+ for (const timer of this.delayTimers.values()) {
2334
+ clearTimeout(timer);
2335
+ }
2336
+ this.delayTimers.clear();
2337
+ for (const item of this.queue) {
2338
+ item.released = true;
2339
+ }
2340
+ await this.processQueue();
2341
+ });
2342
+ }
2343
+ /**
2344
+ * Schedule processing on next tick
2345
+ */
2346
+ scheduleProcessing() {
2347
+ if (this.processTimer) {
2348
+ clearTimeout(this.processTimer);
2349
+ }
2350
+ this.processTimer = setTimeout(() => {
2351
+ this.processQueue();
2352
+ }, 0);
2353
+ }
2354
+ /**
2355
+ * Cleanup timers and resources
2356
+ */
2357
+ destroy() {
2358
+ if (this.processTimer) {
2359
+ clearTimeout(this.processTimer);
2360
+ }
2361
+ for (const timer of this.delayTimers.values()) {
2362
+ clearTimeout(timer);
2363
+ }
2364
+ this.delayTimers.clear();
2365
+ }
2366
+ }
2367
+
2228
2368
  async function claudeRemoteLauncher(session) {
2229
2369
  logger.debug("[claudeRemoteLauncher] Starting remote launcher");
2230
2370
  const hasTTY = process.stdout.isTTY && process.stdin.isTTY;
@@ -2282,6 +2422,12 @@ async function claudeRemoteLauncher(session) {
2282
2422
  session.client.setHandler("abort", doAbort);
2283
2423
  session.client.setHandler("switch", doSwitch);
2284
2424
  const permissionHandler = new PermissionHandler(session);
2425
+ const messageQueue = new OutgoingMessageQueue(
2426
+ (logMessage) => session.client.sendClaudeSessionMessage(logMessage)
2427
+ );
2428
+ permissionHandler.setOnPermissionRequest((toolCallId) => {
2429
+ messageQueue.releaseToolCall(toolCallId);
2430
+ });
2285
2431
  const sdkToLogConverter = new SDKToLogConverter({
2286
2432
  sessionId: session.sessionId || "unknown",
2287
2433
  cwd: session.path,
@@ -2320,6 +2466,7 @@ async function claudeRemoteLauncher(session) {
2320
2466
  for (let c of umessage.message.content) {
2321
2467
  if (c.type === "tool_result" && c.tool_use_id) {
2322
2468
  ongoingToolCalls.delete(c.tool_use_id);
2469
+ messageQueue.releaseToolCall(c.tool_use_id);
2323
2470
  }
2324
2471
  }
2325
2472
  }
@@ -2381,9 +2528,28 @@ async function claudeRemoteLauncher(session) {
2381
2528
  }
2382
2529
  }
2383
2530
  }
2384
- if (logMessage.type !== "system") {
2385
- session.client.sendClaudeSessionMessage(logMessage);
2531
+ if (logMessage.type === "assistant" && message.type === "assistant") {
2532
+ const assistantMsg = message;
2533
+ const toolCallIds = [];
2534
+ if (assistantMsg.message.content && Array.isArray(assistantMsg.message.content)) {
2535
+ for (const block of assistantMsg.message.content) {
2536
+ if (block.type === "tool_use" && block.id) {
2537
+ toolCallIds.push(block.id);
2538
+ }
2539
+ }
2540
+ }
2541
+ if (toolCallIds.length > 0) {
2542
+ const isSidechain = assistantMsg.parent_tool_use_id !== void 0;
2543
+ if (!isSidechain) {
2544
+ messageQueue.enqueue(logMessage, {
2545
+ delay: 250,
2546
+ toolCallIds
2547
+ });
2548
+ return;
2549
+ }
2550
+ }
2386
2551
  }
2552
+ messageQueue.enqueue(logMessage);
2387
2553
  }
2388
2554
  if (message.type === "assistant") {
2389
2555
  let umessage = message;
@@ -2392,7 +2558,7 @@ async function claudeRemoteLauncher(session) {
2392
2558
  if (c.type === "tool_use" && c.name === "Task" && c.input && typeof c.input.prompt === "string") {
2393
2559
  const logMessage2 = sdkToLogConverter.convertSidechainUserMessage(c.id, c.input.prompt);
2394
2560
  if (logMessage2) {
2395
- session.client.sendClaudeSessionMessage(logMessage2);
2561
+ messageQueue.enqueue(logMessage2);
2396
2562
  }
2397
2563
  }
2398
2564
  }
@@ -2483,6 +2649,10 @@ async function claudeRemoteLauncher(session) {
2483
2649
  }
2484
2650
  }
2485
2651
  ongoingToolCalls.clear();
2652
+ logger.debug("[remote]: flushing message queue");
2653
+ await messageQueue.flush();
2654
+ messageQueue.destroy();
2655
+ logger.debug("[remote]: message queue flushed");
2486
2656
  abortController = null;
2487
2657
  abortFuture?.resolve(void 0);
2488
2658
  abortFuture = null;
@@ -2556,7 +2726,7 @@ async function loop(opts) {
2556
2726
  }
2557
2727
 
2558
2728
  var name = "happy-coder";
2559
- var version = "0.9.0-1";
2729
+ var version = "0.9.0-3";
2560
2730
  var description = "Claude Code session sharing CLI";
2561
2731
  var author = "Kirill Dubovitskiy";
2562
2732
  var license = "MIT";
package/dist/lib.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var types = require('./types-D8nxJTr8.cjs');
3
+ var types = require('./types-a-nJyP-e.cjs');
4
4
  require('axios');
5
5
  require('chalk');
6
6
  require('fs');
package/dist/lib.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-CJU8o8xd.mjs';
1
+ export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, l as logger } from './types-DJOX-XG-.mjs';
2
2
  import 'axios';
3
3
  import 'chalk';
4
4
  import 'fs';
@@ -1317,4 +1317,4 @@ const RawJSONLinesSchema = z.discriminatedUnion("type", [
1317
1317
  }).passthrough()
1318
1318
  ]);
1319
1319
 
1320
- export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, encodeBase64 as e, encodeBase64Url as f, decodeBase64 as g, logger as l };
1320
+ export { ApiClient as A, RawJSONLinesSchema as R, ApiSessionClient as a, backoff as b, configuration as c, delay as d, AsyncLock as e, encodeBase64 as f, encodeBase64Url as g, decodeBase64 as h, logger as l };
@@ -1321,6 +1321,7 @@ const RawJSONLinesSchema = z.z.discriminatedUnion("type", [
1321
1321
 
1322
1322
  exports.ApiClient = ApiClient;
1323
1323
  exports.ApiSessionClient = ApiSessionClient;
1324
+ exports.AsyncLock = AsyncLock;
1324
1325
  exports.RawJSONLinesSchema = RawJSONLinesSchema;
1325
1326
  exports.backoff = backoff;
1326
1327
  exports.configuration = configuration;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.9.0-1",
3
+ "version": "0.9.0-3",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",