happy-coder 0.6.3 → 0.6.4

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
@@ -93,6 +93,13 @@ class Session {
93
93
  onSessionFound = (sessionId) => {
94
94
  this.sessionId = sessionId;
95
95
  };
96
+ /**
97
+ * Clear the current session ID (used by /clear command)
98
+ */
99
+ clearSessionId = () => {
100
+ this.sessionId = null;
101
+ types$1.logger.debug("[Session] Session ID cleared");
102
+ };
96
103
  }
97
104
 
98
105
  function getProjectPath(workingDirectory) {
@@ -1237,6 +1244,50 @@ class PushableAsyncIterable {
1237
1244
  }
1238
1245
  }
1239
1246
 
1247
+ function parseCompact(message) {
1248
+ const trimmed = message.trim();
1249
+ if (trimmed === "/compact") {
1250
+ return {
1251
+ isCompact: true,
1252
+ originalMessage: trimmed
1253
+ };
1254
+ }
1255
+ if (trimmed.startsWith("/compact ")) {
1256
+ return {
1257
+ isCompact: true,
1258
+ originalMessage: trimmed
1259
+ };
1260
+ }
1261
+ return {
1262
+ isCompact: false,
1263
+ originalMessage: message
1264
+ };
1265
+ }
1266
+ function parseClear(message) {
1267
+ const trimmed = message.trim();
1268
+ return {
1269
+ isClear: trimmed === "/clear"
1270
+ };
1271
+ }
1272
+ function parseSpecialCommand(message) {
1273
+ const compactResult = parseCompact(message);
1274
+ if (compactResult.isCompact) {
1275
+ return {
1276
+ type: "compact",
1277
+ originalMessage: compactResult.originalMessage
1278
+ };
1279
+ }
1280
+ const clearResult = parseClear(message);
1281
+ if (clearResult.isClear) {
1282
+ return {
1283
+ type: "clear"
1284
+ };
1285
+ }
1286
+ return {
1287
+ type: null
1288
+ };
1289
+ }
1290
+
1240
1291
  async function claudeRemote(opts) {
1241
1292
  let startFrom = opts.sessionId;
1242
1293
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
@@ -1270,6 +1321,21 @@ async function claudeRemote(opts) {
1270
1321
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1271
1322
  }
1272
1323
  types$1.logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}, model: ${opts.model || "default"}, fallbackModel: ${opts.fallbackModel || "none"}, customSystemPrompt: ${opts.customSystemPrompt ? "set" : "none"}, appendSystemPrompt: ${opts.appendSystemPrompt ? "set" : "none"}, allowedTools: ${opts.allowedTools ? opts.allowedTools.join(",") : "none"}, disallowedTools: ${opts.disallowedTools ? opts.disallowedTools.join(",") : "none"}`);
1324
+ const specialCommand = parseSpecialCommand(opts.message);
1325
+ if (specialCommand.type === "clear") {
1326
+ types$1.logger.debug("[claudeRemote] /clear command detected - should not reach here, handled in start.ts");
1327
+ if (opts.onCompletionEvent) {
1328
+ opts.onCompletionEvent("Context was reset");
1329
+ }
1330
+ if (opts.onSessionReset) {
1331
+ opts.onSessionReset();
1332
+ }
1333
+ return;
1334
+ }
1335
+ if (specialCommand.type === "compact") {
1336
+ types$1.logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
1337
+ }
1338
+ const isCompactCommand = specialCommand.type === "compact";
1273
1339
  let message = new PushableAsyncIterable();
1274
1340
  message.push({
1275
1341
  type: "user",
@@ -1309,10 +1375,22 @@ async function claudeRemote(opts) {
1309
1375
  types$1.logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
1310
1376
  opts.onSessionFound(systemInit.session_id);
1311
1377
  }
1378
+ if (isCompactCommand) {
1379
+ types$1.logger.debug("[claudeRemote] Compaction started");
1380
+ if (opts.onCompletionEvent) {
1381
+ opts.onCompletionEvent("Compaction started");
1382
+ }
1383
+ }
1312
1384
  }
1313
1385
  if (message2.type === "result") {
1314
1386
  updateThinking(false);
1315
1387
  types$1.logger.debug("[claudeRemote] Result received, exiting claudeRemote");
1388
+ if (isCompactCommand) {
1389
+ types$1.logger.debug("[claudeRemote] Compaction completed");
1390
+ if (opts.onCompletionEvent) {
1391
+ opts.onCompletionEvent("Compaction completed");
1392
+ }
1393
+ }
1316
1394
  return;
1317
1395
  }
1318
1396
  if (message2.type === "user") {
@@ -2177,6 +2255,14 @@ async function claudeRemoteLauncher(session) {
2177
2255
  claudeEnvVars: session.claudeEnvVars,
2178
2256
  claudeArgs: session.claudeArgs,
2179
2257
  onMessage,
2258
+ onCompletionEvent: (message) => {
2259
+ types$1.logger.debug(`[remote]: Completion event: ${message}`);
2260
+ session.client.sendSessionEvent({ type: "message", message });
2261
+ },
2262
+ onSessionReset: () => {
2263
+ types$1.logger.debug("[remote]: Session reset");
2264
+ session.clearSessionId();
2265
+ },
2180
2266
  signal: abortController.signal
2181
2267
  });
2182
2268
  if (!exitReason && abortController.signal.aborted) {
@@ -2233,6 +2319,9 @@ async function loop(opts) {
2233
2319
  messageQueue: opts.messageQueue,
2234
2320
  onModeChange: opts.onModeChange
2235
2321
  });
2322
+ if (opts.onSessionReady) {
2323
+ opts.onSessionReady(session);
2324
+ }
2236
2325
  let mode = opts.startingMode ?? "local";
2237
2326
  while (true) {
2238
2327
  types$1.logger.debug(`[loop] Iteration with mode: ${mode}`);
@@ -2262,7 +2351,7 @@ async function loop(opts) {
2262
2351
  }
2263
2352
 
2264
2353
  var name = "happy-coder";
2265
- var version = "0.6.3";
2354
+ var version = "0.6.4";
2266
2355
  var description = "Claude Code session sharing CLI";
2267
2356
  var author = "Kirill Dubovitskiy";
2268
2357
  var license = "MIT";
@@ -2669,6 +2758,7 @@ class MessageQueue2 {
2669
2758
  types$1.logger.debug(`[MessageQueue2] Initialized`);
2670
2759
  }
2671
2760
  queue = [];
2761
+ // Made public for testing
2672
2762
  waiter = null;
2673
2763
  closed = false;
2674
2764
  onMessageHandler = null;
@@ -2690,7 +2780,8 @@ class MessageQueue2 {
2690
2780
  this.queue.push({
2691
2781
  message,
2692
2782
  mode,
2693
- modeHash
2783
+ modeHash,
2784
+ isolate: false
2694
2785
  });
2695
2786
  if (this.onMessageHandler) {
2696
2787
  this.onMessageHandler(message, mode);
@@ -2703,6 +2794,62 @@ class MessageQueue2 {
2703
2794
  }
2704
2795
  types$1.logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
2705
2796
  }
2797
+ /**
2798
+ * Push a message immediately without batching delay.
2799
+ * Does not clear the queue or enforce isolation.
2800
+ */
2801
+ pushImmediate(message, mode) {
2802
+ if (this.closed) {
2803
+ throw new Error("Cannot push to closed queue");
2804
+ }
2805
+ const modeHash = this.modeHasher(mode);
2806
+ types$1.logger.debug(`[MessageQueue2] pushImmediate() called with mode hash: ${modeHash}`);
2807
+ this.queue.push({
2808
+ message,
2809
+ mode,
2810
+ modeHash,
2811
+ isolate: false
2812
+ });
2813
+ if (this.onMessageHandler) {
2814
+ this.onMessageHandler(message, mode);
2815
+ }
2816
+ if (this.waiter) {
2817
+ types$1.logger.debug(`[MessageQueue2] Notifying waiter for immediate message`);
2818
+ const waiter = this.waiter;
2819
+ this.waiter = null;
2820
+ waiter(true);
2821
+ }
2822
+ types$1.logger.debug(`[MessageQueue2] pushImmediate() completed. Queue size: ${this.queue.length}`);
2823
+ }
2824
+ /**
2825
+ * Push a message that must be processed in complete isolation.
2826
+ * Clears any pending messages and ensures this message is never batched with others.
2827
+ * Used for special commands that require dedicated processing.
2828
+ */
2829
+ pushIsolateAndClear(message, mode) {
2830
+ if (this.closed) {
2831
+ throw new Error("Cannot push to closed queue");
2832
+ }
2833
+ const modeHash = this.modeHasher(mode);
2834
+ types$1.logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
2835
+ this.queue = [];
2836
+ this.queue.push({
2837
+ message,
2838
+ mode,
2839
+ modeHash,
2840
+ isolate: true
2841
+ });
2842
+ if (this.onMessageHandler) {
2843
+ this.onMessageHandler(message, mode);
2844
+ }
2845
+ if (this.waiter) {
2846
+ types$1.logger.debug(`[MessageQueue2] Notifying waiter for isolated message`);
2847
+ const waiter = this.waiter;
2848
+ this.waiter = null;
2849
+ waiter(true);
2850
+ }
2851
+ types$1.logger.debug(`[MessageQueue2] pushIsolateAndClear() completed. Queue size: ${this.queue.length}`);
2852
+ }
2706
2853
  /**
2707
2854
  * Push a message to the beginning of the queue with a mode.
2708
2855
  */
@@ -2715,7 +2862,8 @@ class MessageQueue2 {
2715
2862
  this.queue.unshift({
2716
2863
  message,
2717
2864
  mode,
2718
- modeHash
2865
+ modeHash,
2866
+ isolate: false
2719
2867
  });
2720
2868
  if (this.onMessageHandler) {
2721
2869
  this.onMessageHandler(message, mode);
@@ -2779,7 +2927,7 @@ class MessageQueue2 {
2779
2927
  return this.collectBatch();
2780
2928
  }
2781
2929
  /**
2782
- * Collect a batch of messages with the same mode
2930
+ * Collect a batch of messages with the same mode, respecting isolation requirements
2783
2931
  */
2784
2932
  collectBatch() {
2785
2933
  if (this.queue.length === 0) {
@@ -2789,12 +2937,18 @@ class MessageQueue2 {
2789
2937
  const sameModeMessages = [];
2790
2938
  let mode = firstItem.mode;
2791
2939
  const targetModeHash = firstItem.modeHash;
2792
- while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash) {
2940
+ if (firstItem.isolate) {
2793
2941
  const item = this.queue.shift();
2794
2942
  sameModeMessages.push(item.message);
2943
+ types$1.logger.debug(`[MessageQueue2] Collected isolated message with mode hash: ${targetModeHash}`);
2944
+ } else {
2945
+ while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash && !this.queue[0].isolate) {
2946
+ const item = this.queue.shift();
2947
+ sameModeMessages.push(item.message);
2948
+ }
2949
+ types$1.logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
2795
2950
  }
2796
2951
  const combinedMessage = sameModeMessages.join("\n");
2797
- types$1.logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
2798
2952
  return {
2799
2953
  message: combinedMessage,
2800
2954
  mode
@@ -3166,6 +3320,37 @@ async function start(credentials, options = {}) {
3166
3320
  } else {
3167
3321
  types$1.logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3168
3322
  }
3323
+ const specialCommand = parseSpecialCommand(message.content.text);
3324
+ if (specialCommand.type === "compact") {
3325
+ types$1.logger.debug("[start] Detected /compact command");
3326
+ const enhancedMode2 = {
3327
+ permissionMode: messagePermissionMode || "default",
3328
+ model: messageModel,
3329
+ fallbackModel: messageFallbackModel,
3330
+ customSystemPrompt: messageCustomSystemPrompt,
3331
+ appendSystemPrompt: messageAppendSystemPrompt,
3332
+ allowedTools: messageAllowedTools,
3333
+ disallowedTools: messageDisallowedTools
3334
+ };
3335
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3336
+ types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3337
+ return;
3338
+ }
3339
+ if (specialCommand.type === "clear") {
3340
+ types$1.logger.debug("[start] Detected /clear command");
3341
+ const enhancedMode2 = {
3342
+ permissionMode: messagePermissionMode || "default",
3343
+ model: messageModel,
3344
+ fallbackModel: messageFallbackModel,
3345
+ customSystemPrompt: messageCustomSystemPrompt,
3346
+ appendSystemPrompt: messageAppendSystemPrompt,
3347
+ allowedTools: messageAllowedTools,
3348
+ disallowedTools: messageDisallowedTools
3349
+ };
3350
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3351
+ types$1.logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3352
+ return;
3353
+ }
3169
3354
  const enhancedMode = {
3170
3355
  permissionMode: messagePermissionMode || "default",
3171
3356
  model: messageModel,
@@ -3192,6 +3377,8 @@ async function start(credentials, options = {}) {
3192
3377
  controlledByUser: newMode === "local"
3193
3378
  }));
3194
3379
  },
3380
+ onSessionReady: (sessionInstance) => {
3381
+ },
3195
3382
  mcpServers: {},
3196
3383
  session,
3197
3384
  claudeEnvVars: options.claudeEnvVars,
package/dist/index.mjs CHANGED
@@ -72,6 +72,13 @@ class Session {
72
72
  onSessionFound = (sessionId) => {
73
73
  this.sessionId = sessionId;
74
74
  };
75
+ /**
76
+ * Clear the current session ID (used by /clear command)
77
+ */
78
+ clearSessionId = () => {
79
+ this.sessionId = null;
80
+ logger.debug("[Session] Session ID cleared");
81
+ };
75
82
  }
76
83
 
77
84
  function getProjectPath(workingDirectory) {
@@ -1216,6 +1223,50 @@ class PushableAsyncIterable {
1216
1223
  }
1217
1224
  }
1218
1225
 
1226
+ function parseCompact(message) {
1227
+ const trimmed = message.trim();
1228
+ if (trimmed === "/compact") {
1229
+ return {
1230
+ isCompact: true,
1231
+ originalMessage: trimmed
1232
+ };
1233
+ }
1234
+ if (trimmed.startsWith("/compact ")) {
1235
+ return {
1236
+ isCompact: true,
1237
+ originalMessage: trimmed
1238
+ };
1239
+ }
1240
+ return {
1241
+ isCompact: false,
1242
+ originalMessage: message
1243
+ };
1244
+ }
1245
+ function parseClear(message) {
1246
+ const trimmed = message.trim();
1247
+ return {
1248
+ isClear: trimmed === "/clear"
1249
+ };
1250
+ }
1251
+ function parseSpecialCommand(message) {
1252
+ const compactResult = parseCompact(message);
1253
+ if (compactResult.isCompact) {
1254
+ return {
1255
+ type: "compact",
1256
+ originalMessage: compactResult.originalMessage
1257
+ };
1258
+ }
1259
+ const clearResult = parseClear(message);
1260
+ if (clearResult.isClear) {
1261
+ return {
1262
+ type: "clear"
1263
+ };
1264
+ }
1265
+ return {
1266
+ type: null
1267
+ };
1268
+ }
1269
+
1219
1270
  async function claudeRemote(opts) {
1220
1271
  let startFrom = opts.sessionId;
1221
1272
  if (opts.sessionId && !claudeCheckSession(opts.sessionId, opts.path)) {
@@ -1249,6 +1300,21 @@ async function claudeRemote(opts) {
1249
1300
  sdkOptions.executableArgs = [...sdkOptions.executableArgs || [], ...opts.claudeArgs];
1250
1301
  }
1251
1302
  logger.debug(`[claudeRemote] Starting query with permission mode: ${opts.permissionMode}, model: ${opts.model || "default"}, fallbackModel: ${opts.fallbackModel || "none"}, customSystemPrompt: ${opts.customSystemPrompt ? "set" : "none"}, appendSystemPrompt: ${opts.appendSystemPrompt ? "set" : "none"}, allowedTools: ${opts.allowedTools ? opts.allowedTools.join(",") : "none"}, disallowedTools: ${opts.disallowedTools ? opts.disallowedTools.join(",") : "none"}`);
1303
+ const specialCommand = parseSpecialCommand(opts.message);
1304
+ if (specialCommand.type === "clear") {
1305
+ logger.debug("[claudeRemote] /clear command detected - should not reach here, handled in start.ts");
1306
+ if (opts.onCompletionEvent) {
1307
+ opts.onCompletionEvent("Context was reset");
1308
+ }
1309
+ if (opts.onSessionReset) {
1310
+ opts.onSessionReset();
1311
+ }
1312
+ return;
1313
+ }
1314
+ if (specialCommand.type === "compact") {
1315
+ logger.debug("[claudeRemote] /compact command detected - will process as normal but with compaction behavior");
1316
+ }
1317
+ const isCompactCommand = specialCommand.type === "compact";
1252
1318
  let message = new PushableAsyncIterable();
1253
1319
  message.push({
1254
1320
  type: "user",
@@ -1288,10 +1354,22 @@ async function claudeRemote(opts) {
1288
1354
  logger.debug(`[claudeRemote] Session file found: ${systemInit.session_id} ${found}`);
1289
1355
  opts.onSessionFound(systemInit.session_id);
1290
1356
  }
1357
+ if (isCompactCommand) {
1358
+ logger.debug("[claudeRemote] Compaction started");
1359
+ if (opts.onCompletionEvent) {
1360
+ opts.onCompletionEvent("Compaction started");
1361
+ }
1362
+ }
1291
1363
  }
1292
1364
  if (message2.type === "result") {
1293
1365
  updateThinking(false);
1294
1366
  logger.debug("[claudeRemote] Result received, exiting claudeRemote");
1367
+ if (isCompactCommand) {
1368
+ logger.debug("[claudeRemote] Compaction completed");
1369
+ if (opts.onCompletionEvent) {
1370
+ opts.onCompletionEvent("Compaction completed");
1371
+ }
1372
+ }
1295
1373
  return;
1296
1374
  }
1297
1375
  if (message2.type === "user") {
@@ -2156,6 +2234,14 @@ async function claudeRemoteLauncher(session) {
2156
2234
  claudeEnvVars: session.claudeEnvVars,
2157
2235
  claudeArgs: session.claudeArgs,
2158
2236
  onMessage,
2237
+ onCompletionEvent: (message) => {
2238
+ logger.debug(`[remote]: Completion event: ${message}`);
2239
+ session.client.sendSessionEvent({ type: "message", message });
2240
+ },
2241
+ onSessionReset: () => {
2242
+ logger.debug("[remote]: Session reset");
2243
+ session.clearSessionId();
2244
+ },
2159
2245
  signal: abortController.signal
2160
2246
  });
2161
2247
  if (!exitReason && abortController.signal.aborted) {
@@ -2212,6 +2298,9 @@ async function loop(opts) {
2212
2298
  messageQueue: opts.messageQueue,
2213
2299
  onModeChange: opts.onModeChange
2214
2300
  });
2301
+ if (opts.onSessionReady) {
2302
+ opts.onSessionReady(session);
2303
+ }
2215
2304
  let mode = opts.startingMode ?? "local";
2216
2305
  while (true) {
2217
2306
  logger.debug(`[loop] Iteration with mode: ${mode}`);
@@ -2241,7 +2330,7 @@ async function loop(opts) {
2241
2330
  }
2242
2331
 
2243
2332
  var name = "happy-coder";
2244
- var version = "0.6.3";
2333
+ var version = "0.6.4";
2245
2334
  var description = "Claude Code session sharing CLI";
2246
2335
  var author = "Kirill Dubovitskiy";
2247
2336
  var license = "MIT";
@@ -2648,6 +2737,7 @@ class MessageQueue2 {
2648
2737
  logger.debug(`[MessageQueue2] Initialized`);
2649
2738
  }
2650
2739
  queue = [];
2740
+ // Made public for testing
2651
2741
  waiter = null;
2652
2742
  closed = false;
2653
2743
  onMessageHandler = null;
@@ -2669,7 +2759,8 @@ class MessageQueue2 {
2669
2759
  this.queue.push({
2670
2760
  message,
2671
2761
  mode,
2672
- modeHash
2762
+ modeHash,
2763
+ isolate: false
2673
2764
  });
2674
2765
  if (this.onMessageHandler) {
2675
2766
  this.onMessageHandler(message, mode);
@@ -2682,6 +2773,62 @@ class MessageQueue2 {
2682
2773
  }
2683
2774
  logger.debug(`[MessageQueue2] push() completed. Queue size: ${this.queue.length}`);
2684
2775
  }
2776
+ /**
2777
+ * Push a message immediately without batching delay.
2778
+ * Does not clear the queue or enforce isolation.
2779
+ */
2780
+ pushImmediate(message, mode) {
2781
+ if (this.closed) {
2782
+ throw new Error("Cannot push to closed queue");
2783
+ }
2784
+ const modeHash = this.modeHasher(mode);
2785
+ logger.debug(`[MessageQueue2] pushImmediate() called with mode hash: ${modeHash}`);
2786
+ this.queue.push({
2787
+ message,
2788
+ mode,
2789
+ modeHash,
2790
+ isolate: false
2791
+ });
2792
+ if (this.onMessageHandler) {
2793
+ this.onMessageHandler(message, mode);
2794
+ }
2795
+ if (this.waiter) {
2796
+ logger.debug(`[MessageQueue2] Notifying waiter for immediate message`);
2797
+ const waiter = this.waiter;
2798
+ this.waiter = null;
2799
+ waiter(true);
2800
+ }
2801
+ logger.debug(`[MessageQueue2] pushImmediate() completed. Queue size: ${this.queue.length}`);
2802
+ }
2803
+ /**
2804
+ * Push a message that must be processed in complete isolation.
2805
+ * Clears any pending messages and ensures this message is never batched with others.
2806
+ * Used for special commands that require dedicated processing.
2807
+ */
2808
+ pushIsolateAndClear(message, mode) {
2809
+ if (this.closed) {
2810
+ throw new Error("Cannot push to closed queue");
2811
+ }
2812
+ const modeHash = this.modeHasher(mode);
2813
+ logger.debug(`[MessageQueue2] pushIsolateAndClear() called with mode hash: ${modeHash} - clearing ${this.queue.length} pending messages`);
2814
+ this.queue = [];
2815
+ this.queue.push({
2816
+ message,
2817
+ mode,
2818
+ modeHash,
2819
+ isolate: true
2820
+ });
2821
+ if (this.onMessageHandler) {
2822
+ this.onMessageHandler(message, mode);
2823
+ }
2824
+ if (this.waiter) {
2825
+ logger.debug(`[MessageQueue2] Notifying waiter for isolated message`);
2826
+ const waiter = this.waiter;
2827
+ this.waiter = null;
2828
+ waiter(true);
2829
+ }
2830
+ logger.debug(`[MessageQueue2] pushIsolateAndClear() completed. Queue size: ${this.queue.length}`);
2831
+ }
2685
2832
  /**
2686
2833
  * Push a message to the beginning of the queue with a mode.
2687
2834
  */
@@ -2694,7 +2841,8 @@ class MessageQueue2 {
2694
2841
  this.queue.unshift({
2695
2842
  message,
2696
2843
  mode,
2697
- modeHash
2844
+ modeHash,
2845
+ isolate: false
2698
2846
  });
2699
2847
  if (this.onMessageHandler) {
2700
2848
  this.onMessageHandler(message, mode);
@@ -2758,7 +2906,7 @@ class MessageQueue2 {
2758
2906
  return this.collectBatch();
2759
2907
  }
2760
2908
  /**
2761
- * Collect a batch of messages with the same mode
2909
+ * Collect a batch of messages with the same mode, respecting isolation requirements
2762
2910
  */
2763
2911
  collectBatch() {
2764
2912
  if (this.queue.length === 0) {
@@ -2768,12 +2916,18 @@ class MessageQueue2 {
2768
2916
  const sameModeMessages = [];
2769
2917
  let mode = firstItem.mode;
2770
2918
  const targetModeHash = firstItem.modeHash;
2771
- while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash) {
2919
+ if (firstItem.isolate) {
2772
2920
  const item = this.queue.shift();
2773
2921
  sameModeMessages.push(item.message);
2922
+ logger.debug(`[MessageQueue2] Collected isolated message with mode hash: ${targetModeHash}`);
2923
+ } else {
2924
+ while (this.queue.length > 0 && this.queue[0].modeHash === targetModeHash && !this.queue[0].isolate) {
2925
+ const item = this.queue.shift();
2926
+ sameModeMessages.push(item.message);
2927
+ }
2928
+ logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
2774
2929
  }
2775
2930
  const combinedMessage = sameModeMessages.join("\n");
2776
- logger.debug(`[MessageQueue2] Collected batch of ${sameModeMessages.length} messages with mode hash: ${targetModeHash}`);
2777
2931
  return {
2778
2932
  message: combinedMessage,
2779
2933
  mode
@@ -3145,6 +3299,37 @@ async function start(credentials, options = {}) {
3145
3299
  } else {
3146
3300
  logger.debug(`[loop] User message received with no disallowed tools override, using current: ${currentDisallowedTools ? currentDisallowedTools.join(", ") : "none"}`);
3147
3301
  }
3302
+ const specialCommand = parseSpecialCommand(message.content.text);
3303
+ if (specialCommand.type === "compact") {
3304
+ logger.debug("[start] Detected /compact command");
3305
+ const enhancedMode2 = {
3306
+ permissionMode: messagePermissionMode || "default",
3307
+ model: messageModel,
3308
+ fallbackModel: messageFallbackModel,
3309
+ customSystemPrompt: messageCustomSystemPrompt,
3310
+ appendSystemPrompt: messageAppendSystemPrompt,
3311
+ allowedTools: messageAllowedTools,
3312
+ disallowedTools: messageDisallowedTools
3313
+ };
3314
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3315
+ logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3316
+ return;
3317
+ }
3318
+ if (specialCommand.type === "clear") {
3319
+ logger.debug("[start] Detected /clear command");
3320
+ const enhancedMode2 = {
3321
+ permissionMode: messagePermissionMode || "default",
3322
+ model: messageModel,
3323
+ fallbackModel: messageFallbackModel,
3324
+ customSystemPrompt: messageCustomSystemPrompt,
3325
+ appendSystemPrompt: messageAppendSystemPrompt,
3326
+ allowedTools: messageAllowedTools,
3327
+ disallowedTools: messageDisallowedTools
3328
+ };
3329
+ messageQueue.pushIsolateAndClear(specialCommand.originalMessage || message.content.text, enhancedMode2);
3330
+ logger.debugLargeJson("[start] /compact command pushed to queue:", message);
3331
+ return;
3332
+ }
3148
3333
  const enhancedMode = {
3149
3334
  permissionMode: messagePermissionMode || "default",
3150
3335
  model: messageModel,
@@ -3171,6 +3356,8 @@ async function start(credentials, options = {}) {
3171
3356
  controlledByUser: newMode === "local"
3172
3357
  }));
3173
3358
  },
3359
+ onSessionReady: (sessionInstance) => {
3360
+ },
3174
3361
  mcpServers: {},
3175
3362
  session,
3176
3363
  claudeEnvVars: options.claudeEnvVars,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "happy-coder",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "Claude Code session sharing CLI",
5
5
  "author": "Kirill Dubovitskiy",
6
6
  "license": "MIT",