happy-coder 0.6.2 → 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 +195 -8
- package/dist/index.mjs +195 -8
- package/dist/lib.cjs +1 -1
- package/dist/lib.d.cts +2 -0
- package/dist/lib.d.mts +2 -0
- package/dist/lib.mjs +1 -1
- package/dist/{types-iMUxaPkI.cjs → types-BDtHM1DY.cjs} +65 -25
- package/dist/{types-DKVMGtcN.mjs → types-Dz5kZrVh.mjs} +65 -25
- package/package.json +1 -1
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-
|
|
4
|
+
var types$1 = require('./types-BDtHM1DY.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');
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
@@ -3089,7 +3243,7 @@ async function start(credentials, options = {}) {
|
|
|
3089
3243
|
types$1.logger.infoDeveloper(`Logs: ${logPath}`);
|
|
3090
3244
|
session.updateAgentState((currentState) => ({
|
|
3091
3245
|
...currentState,
|
|
3092
|
-
controlledByUser: options.startingMode
|
|
3246
|
+
controlledByUser: options.startingMode !== "remote"
|
|
3093
3247
|
}));
|
|
3094
3248
|
const caffeinateStarted = startCaffeinate();
|
|
3095
3249
|
if (caffeinateStarted) {
|
|
@@ -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-
|
|
2
|
+
import { l as logger, d as backoff, e as delay, R as RawJSONLinesSchema, c as configuration, f as encodeBase64, A as ApiClient, g as encodeBase64Url, h as decodeBase64, j as encrypt, b as initializeConfiguration, i as initLoggerWithGlobalConfiguration } from './types-Dz5kZrVh.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';
|
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
@@ -3068,7 +3222,7 @@ async function start(credentials, options = {}) {
|
|
|
3068
3222
|
logger.infoDeveloper(`Logs: ${logPath}`);
|
|
3069
3223
|
session.updateAgentState((currentState) => ({
|
|
3070
3224
|
...currentState,
|
|
3071
|
-
controlledByUser: options.startingMode
|
|
3225
|
+
controlledByUser: options.startingMode !== "remote"
|
|
3072
3226
|
}));
|
|
3073
3227
|
const caffeinateStarted = startCaffeinate();
|
|
3074
3228
|
if (caffeinateStarted) {
|
|
@@ -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/dist/lib.cjs
CHANGED
package/dist/lib.d.cts
CHANGED
|
@@ -449,6 +449,8 @@ declare class ApiSessionClient extends EventEmitter {
|
|
|
449
449
|
private pendingMessages;
|
|
450
450
|
private pendingMessageCallback;
|
|
451
451
|
private rpcHandlers;
|
|
452
|
+
private agentStateLock;
|
|
453
|
+
private metadataLock;
|
|
452
454
|
constructor(token: string, secret: Uint8Array, session: Session);
|
|
453
455
|
onUserMessage(callback: (data: UserMessage) => void): void;
|
|
454
456
|
/**
|
package/dist/lib.d.mts
CHANGED
|
@@ -449,6 +449,8 @@ declare class ApiSessionClient extends EventEmitter {
|
|
|
449
449
|
private pendingMessages;
|
|
450
450
|
private pendingMessageCallback;
|
|
451
451
|
private rpcHandlers;
|
|
452
|
+
private agentStateLock;
|
|
453
|
+
private metadataLock;
|
|
452
454
|
constructor(token: string, secret: Uint8Array, session: Session);
|
|
453
455
|
onUserMessage(callback: (data: UserMessage) => void): void;
|
|
454
456
|
/**
|
package/dist/lib.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-
|
|
1
|
+
export { A as ApiClient, a as ApiSessionClient, R as RawJSONLinesSchema, c as configuration, i as initLoggerWithGlobalConfiguration, b as initializeConfiguration, l as logger } from './types-Dz5kZrVh.mjs';
|
|
2
2
|
import 'axios';
|
|
3
3
|
import 'chalk';
|
|
4
4
|
import 'fs';
|
|
@@ -358,6 +358,40 @@ function createBackoff(opts) {
|
|
|
358
358
|
}
|
|
359
359
|
let backoff = createBackoff();
|
|
360
360
|
|
|
361
|
+
class AsyncLock {
|
|
362
|
+
permits = 1;
|
|
363
|
+
promiseResolverQueue = [];
|
|
364
|
+
async inLock(func) {
|
|
365
|
+
try {
|
|
366
|
+
await this.lock();
|
|
367
|
+
return await func();
|
|
368
|
+
} finally {
|
|
369
|
+
this.unlock();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
async lock() {
|
|
373
|
+
if (this.permits > 0) {
|
|
374
|
+
this.permits = this.permits - 1;
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
await new Promise((resolve) => this.promiseResolverQueue.push(resolve));
|
|
378
|
+
}
|
|
379
|
+
unlock() {
|
|
380
|
+
this.permits += 1;
|
|
381
|
+
if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
|
|
382
|
+
throw new Error("this.permits should never be > 0 when there is someone waiting.");
|
|
383
|
+
} else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
|
|
384
|
+
this.permits -= 1;
|
|
385
|
+
const nextResolver = this.promiseResolverQueue.shift();
|
|
386
|
+
if (nextResolver) {
|
|
387
|
+
setTimeout(() => {
|
|
388
|
+
nextResolver(true);
|
|
389
|
+
}, 0);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
361
395
|
class ApiSessionClient extends node_events.EventEmitter {
|
|
362
396
|
token;
|
|
363
397
|
secret;
|
|
@@ -370,6 +404,8 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
370
404
|
pendingMessages = [];
|
|
371
405
|
pendingMessageCallback = null;
|
|
372
406
|
rpcHandlers = /* @__PURE__ */ new Map();
|
|
407
|
+
agentStateLock = new AsyncLock();
|
|
408
|
+
metadataLock = new AsyncLock();
|
|
373
409
|
constructor(token, secret, session) {
|
|
374
410
|
super();
|
|
375
411
|
this.token = token;
|
|
@@ -589,19 +625,21 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
589
625
|
* @param handler - Handler function that returns the updated metadata
|
|
590
626
|
*/
|
|
591
627
|
updateMetadata(handler) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
this.metadataVersion = answer.version;
|
|
598
|
-
} else if (answer.result === "version-mismatch") {
|
|
599
|
-
if (answer.version > this.metadataVersion) {
|
|
600
|
-
this.metadataVersion = answer.version;
|
|
628
|
+
this.metadataLock.inLock(async () => {
|
|
629
|
+
await backoff(async () => {
|
|
630
|
+
let updated = handler(this.metadata);
|
|
631
|
+
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(updated, this.secret)) });
|
|
632
|
+
if (answer.result === "success") {
|
|
601
633
|
this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
634
|
+
this.metadataVersion = answer.version;
|
|
635
|
+
} else if (answer.result === "version-mismatch") {
|
|
636
|
+
if (answer.version > this.metadataVersion) {
|
|
637
|
+
this.metadataVersion = answer.version;
|
|
638
|
+
this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
639
|
+
}
|
|
640
|
+
throw new Error("Metadata version mismatch");
|
|
641
|
+
} else if (answer.result === "error") ;
|
|
642
|
+
});
|
|
605
643
|
});
|
|
606
644
|
}
|
|
607
645
|
/**
|
|
@@ -610,20 +648,22 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
610
648
|
*/
|
|
611
649
|
updateAgentState(handler) {
|
|
612
650
|
exports.logger.debugLargeJson("Updating agent state", this.agentState);
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
this.agentStateVersion = answer.version;
|
|
619
|
-
exports.logger.debug("Agent state updated", this.agentState);
|
|
620
|
-
} else if (answer.result === "version-mismatch") {
|
|
621
|
-
if (answer.version > this.agentStateVersion) {
|
|
622
|
-
this.agentStateVersion = answer.version;
|
|
651
|
+
this.agentStateLock.inLock(async () => {
|
|
652
|
+
await backoff(async () => {
|
|
653
|
+
let updated = handler(this.agentState || {});
|
|
654
|
+
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(updated, this.secret)) : null });
|
|
655
|
+
if (answer.result === "success") {
|
|
623
656
|
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
657
|
+
this.agentStateVersion = answer.version;
|
|
658
|
+
exports.logger.debug("Agent state updated", this.agentState);
|
|
659
|
+
} else if (answer.result === "version-mismatch") {
|
|
660
|
+
if (answer.version > this.agentStateVersion) {
|
|
661
|
+
this.agentStateVersion = answer.version;
|
|
662
|
+
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
|
|
663
|
+
}
|
|
664
|
+
throw new Error("Agent state version mismatch");
|
|
665
|
+
} else if (answer.result === "error") ;
|
|
666
|
+
});
|
|
627
667
|
});
|
|
628
668
|
}
|
|
629
669
|
/**
|
|
@@ -356,6 +356,40 @@ function createBackoff(opts) {
|
|
|
356
356
|
}
|
|
357
357
|
let backoff = createBackoff();
|
|
358
358
|
|
|
359
|
+
class AsyncLock {
|
|
360
|
+
permits = 1;
|
|
361
|
+
promiseResolverQueue = [];
|
|
362
|
+
async inLock(func) {
|
|
363
|
+
try {
|
|
364
|
+
await this.lock();
|
|
365
|
+
return await func();
|
|
366
|
+
} finally {
|
|
367
|
+
this.unlock();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
async lock() {
|
|
371
|
+
if (this.permits > 0) {
|
|
372
|
+
this.permits = this.permits - 1;
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
await new Promise((resolve) => this.promiseResolverQueue.push(resolve));
|
|
376
|
+
}
|
|
377
|
+
unlock() {
|
|
378
|
+
this.permits += 1;
|
|
379
|
+
if (this.permits > 1 && this.promiseResolverQueue.length > 0) {
|
|
380
|
+
throw new Error("this.permits should never be > 0 when there is someone waiting.");
|
|
381
|
+
} else if (this.permits === 1 && this.promiseResolverQueue.length > 0) {
|
|
382
|
+
this.permits -= 1;
|
|
383
|
+
const nextResolver = this.promiseResolverQueue.shift();
|
|
384
|
+
if (nextResolver) {
|
|
385
|
+
setTimeout(() => {
|
|
386
|
+
nextResolver(true);
|
|
387
|
+
}, 0);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
359
393
|
class ApiSessionClient extends EventEmitter {
|
|
360
394
|
token;
|
|
361
395
|
secret;
|
|
@@ -368,6 +402,8 @@ class ApiSessionClient extends EventEmitter {
|
|
|
368
402
|
pendingMessages = [];
|
|
369
403
|
pendingMessageCallback = null;
|
|
370
404
|
rpcHandlers = /* @__PURE__ */ new Map();
|
|
405
|
+
agentStateLock = new AsyncLock();
|
|
406
|
+
metadataLock = new AsyncLock();
|
|
371
407
|
constructor(token, secret, session) {
|
|
372
408
|
super();
|
|
373
409
|
this.token = token;
|
|
@@ -587,19 +623,21 @@ class ApiSessionClient extends EventEmitter {
|
|
|
587
623
|
* @param handler - Handler function that returns the updated metadata
|
|
588
624
|
*/
|
|
589
625
|
updateMetadata(handler) {
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
this.metadataVersion = answer.version;
|
|
596
|
-
} else if (answer.result === "version-mismatch") {
|
|
597
|
-
if (answer.version > this.metadataVersion) {
|
|
598
|
-
this.metadataVersion = answer.version;
|
|
626
|
+
this.metadataLock.inLock(async () => {
|
|
627
|
+
await backoff(async () => {
|
|
628
|
+
let updated = handler(this.metadata);
|
|
629
|
+
const answer = await this.socket.emitWithAck("update-metadata", { sid: this.sessionId, expectedVersion: this.metadataVersion, metadata: encodeBase64(encrypt(updated, this.secret)) });
|
|
630
|
+
if (answer.result === "success") {
|
|
599
631
|
this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
632
|
+
this.metadataVersion = answer.version;
|
|
633
|
+
} else if (answer.result === "version-mismatch") {
|
|
634
|
+
if (answer.version > this.metadataVersion) {
|
|
635
|
+
this.metadataVersion = answer.version;
|
|
636
|
+
this.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
637
|
+
}
|
|
638
|
+
throw new Error("Metadata version mismatch");
|
|
639
|
+
} else if (answer.result === "error") ;
|
|
640
|
+
});
|
|
603
641
|
});
|
|
604
642
|
}
|
|
605
643
|
/**
|
|
@@ -608,20 +646,22 @@ class ApiSessionClient extends EventEmitter {
|
|
|
608
646
|
*/
|
|
609
647
|
updateAgentState(handler) {
|
|
610
648
|
logger.debugLargeJson("Updating agent state", this.agentState);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
this.agentStateVersion = answer.version;
|
|
617
|
-
logger.debug("Agent state updated", this.agentState);
|
|
618
|
-
} else if (answer.result === "version-mismatch") {
|
|
619
|
-
if (answer.version > this.agentStateVersion) {
|
|
620
|
-
this.agentStateVersion = answer.version;
|
|
649
|
+
this.agentStateLock.inLock(async () => {
|
|
650
|
+
await backoff(async () => {
|
|
651
|
+
let updated = handler(this.agentState || {});
|
|
652
|
+
const answer = await this.socket.emitWithAck("update-state", { sid: this.sessionId, expectedVersion: this.agentStateVersion, agentState: updated ? encodeBase64(encrypt(updated, this.secret)) : null });
|
|
653
|
+
if (answer.result === "success") {
|
|
621
654
|
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
655
|
+
this.agentStateVersion = answer.version;
|
|
656
|
+
logger.debug("Agent state updated", this.agentState);
|
|
657
|
+
} else if (answer.result === "version-mismatch") {
|
|
658
|
+
if (answer.version > this.agentStateVersion) {
|
|
659
|
+
this.agentStateVersion = answer.version;
|
|
660
|
+
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
|
|
661
|
+
}
|
|
662
|
+
throw new Error("Agent state version mismatch");
|
|
663
|
+
} else if (answer.result === "error") ;
|
|
664
|
+
});
|
|
625
665
|
});
|
|
626
666
|
}
|
|
627
667
|
/**
|