happy-coder 0.6.3 → 0.7.1-beta.1
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 +1623 -774
- package/dist/index.mjs +1631 -782
- package/dist/lib.cjs +3 -11
- package/dist/lib.d.cts +162 -14
- package/dist/lib.d.mts +162 -14
- package/dist/lib.mjs +1 -1
- package/dist/{types-Dz5kZrVh.mjs → types-BZC9-exR.mjs} +413 -43
- package/dist/{types-BDtHM1DY.cjs → types-CzvFvJwf.cjs} +458 -89
- package/package.json +16 -8
|
@@ -16,40 +16,32 @@ var expoServerSdk = require('expo-server-sdk');
|
|
|
16
16
|
|
|
17
17
|
class Configuration {
|
|
18
18
|
serverUrl;
|
|
19
|
-
installationLocation;
|
|
20
19
|
isDaemonProcess;
|
|
21
20
|
// Directories and paths (from persistence)
|
|
22
|
-
|
|
21
|
+
happyHomeDir;
|
|
23
22
|
logsDir;
|
|
24
23
|
daemonLogsDir;
|
|
25
24
|
settingsFile;
|
|
26
25
|
privateKeyFile;
|
|
27
|
-
|
|
28
|
-
constructor(
|
|
29
|
-
this.serverUrl = process.env.
|
|
26
|
+
daemonStateFile;
|
|
27
|
+
constructor() {
|
|
28
|
+
this.serverUrl = process.env.HAPPY_SERVER_URL || "https://handy-api.korshakov.org";
|
|
30
29
|
const args = process.argv.slice(2);
|
|
31
|
-
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" &&
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
this.
|
|
35
|
-
} else if (location === "global") {
|
|
36
|
-
this.happyDir = node_path.join(os.homedir(), ".happy");
|
|
37
|
-
this.installationLocation = "global";
|
|
30
|
+
this.isDaemonProcess = args.length >= 2 && args[0] === "daemon" && args[1] === "start-sync";
|
|
31
|
+
if (process.env.HAPPY_HOME_DIR) {
|
|
32
|
+
const expandedPath = process.env.HAPPY_HOME_DIR.replace(/^~/, os.homedir());
|
|
33
|
+
this.happyHomeDir = expandedPath;
|
|
38
34
|
} else {
|
|
39
|
-
this.
|
|
40
|
-
this.installationLocation = "global";
|
|
35
|
+
this.happyHomeDir = node_path.join(os.homedir(), ".happy");
|
|
41
36
|
}
|
|
42
|
-
this.logsDir = node_path.join(this.
|
|
43
|
-
this.daemonLogsDir = node_path.join(this.
|
|
44
|
-
this.settingsFile = node_path.join(this.
|
|
45
|
-
this.privateKeyFile = node_path.join(this.
|
|
46
|
-
this.
|
|
37
|
+
this.logsDir = node_path.join(this.happyHomeDir, "logs");
|
|
38
|
+
this.daemonLogsDir = node_path.join(this.happyHomeDir, "logs-daemon");
|
|
39
|
+
this.settingsFile = node_path.join(this.happyHomeDir, "settings.json");
|
|
40
|
+
this.privateKeyFile = node_path.join(this.happyHomeDir, "access.key");
|
|
41
|
+
this.daemonStateFile = node_path.join(this.happyHomeDir, "daemon.state.json");
|
|
47
42
|
}
|
|
48
43
|
}
|
|
49
|
-
|
|
50
|
-
function initializeConfiguration(location) {
|
|
51
|
-
exports.configuration = new Configuration(location);
|
|
52
|
-
}
|
|
44
|
+
const configuration = new Configuration();
|
|
53
45
|
|
|
54
46
|
function createTimestampForFilename(date = /* @__PURE__ */ new Date()) {
|
|
55
47
|
return date.toLocaleString("sv-SE", {
|
|
@@ -73,17 +65,22 @@ function createTimestampForLogEntry(date = /* @__PURE__ */ new Date()) {
|
|
|
73
65
|
});
|
|
74
66
|
}
|
|
75
67
|
async function getSessionLogPath() {
|
|
76
|
-
if (!node_fs.existsSync(
|
|
77
|
-
await promises.mkdir(
|
|
68
|
+
if (!node_fs.existsSync(configuration.logsDir)) {
|
|
69
|
+
await promises.mkdir(configuration.logsDir, { recursive: true });
|
|
78
70
|
}
|
|
79
71
|
const timestamp = createTimestampForFilename();
|
|
80
|
-
const filename =
|
|
81
|
-
return node_path.join(
|
|
72
|
+
const filename = configuration.isDaemonProcess ? `${timestamp}-daemon.log` : `${timestamp}.log`;
|
|
73
|
+
return node_path.join(configuration.logsDir, filename);
|
|
82
74
|
}
|
|
83
75
|
class Logger {
|
|
84
76
|
constructor(logFilePathPromise = getSessionLogPath()) {
|
|
85
77
|
this.logFilePathPromise = logFilePathPromise;
|
|
78
|
+
if (process.env.DANGEROUSLY_LOG_TO_SERVER_FOR_AI_AUTO_DEBUGGING && process.env.HAPPY_SERVER_URL) {
|
|
79
|
+
this.dangerouslyUnencryptedServerLoggingUrl = process.env.HAPPY_SERVER_URL;
|
|
80
|
+
console.log(chalk.yellow("[REMOTE LOGGING] Sending logs to server for AI debugging"));
|
|
81
|
+
}
|
|
86
82
|
}
|
|
83
|
+
dangerouslyUnencryptedServerLoggingUrl;
|
|
87
84
|
// Use local timezone for simplicity of locating the logs,
|
|
88
85
|
// in practice you will not need absolute timestamps
|
|
89
86
|
localTimezoneTimestamp() {
|
|
@@ -158,11 +155,38 @@ class Logger {
|
|
|
158
155
|
}
|
|
159
156
|
}
|
|
160
157
|
}
|
|
158
|
+
async sendToRemoteServer(level, message, ...args) {
|
|
159
|
+
if (!this.dangerouslyUnencryptedServerLoggingUrl) return;
|
|
160
|
+
try {
|
|
161
|
+
await fetch(this.dangerouslyUnencryptedServerLoggingUrl + "/logs-combined-from-cli-and-mobile-for-simple-ai-debugging", {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: { "Content-Type": "application/json" },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
166
|
+
level,
|
|
167
|
+
message: `${message} ${args.map(
|
|
168
|
+
(a) => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)
|
|
169
|
+
).join(" ")}`,
|
|
170
|
+
source: "cli",
|
|
171
|
+
platform: process.platform
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
} catch (error) {
|
|
175
|
+
}
|
|
176
|
+
}
|
|
161
177
|
logToFile(prefix, message, ...args) {
|
|
162
178
|
const logLine = `${prefix} ${message} ${args.map(
|
|
163
179
|
(arg) => typeof arg === "string" ? arg : JSON.stringify(arg)
|
|
164
180
|
).join(" ")}
|
|
165
181
|
`;
|
|
182
|
+
if (this.dangerouslyUnencryptedServerLoggingUrl) {
|
|
183
|
+
let level = "info";
|
|
184
|
+
if (prefix.includes(this.localTimezoneTimestamp())) {
|
|
185
|
+
level = "debug";
|
|
186
|
+
}
|
|
187
|
+
this.sendToRemoteServer(level, message, ...args).catch(() => {
|
|
188
|
+
});
|
|
189
|
+
}
|
|
166
190
|
this.logFilePathPromise.then((logFilePath) => {
|
|
167
191
|
try {
|
|
168
192
|
fs.appendFileSync(logFilePath, logLine);
|
|
@@ -181,16 +205,7 @@ class Logger {
|
|
|
181
205
|
});
|
|
182
206
|
}
|
|
183
207
|
}
|
|
184
|
-
|
|
185
|
-
function initLoggerWithGlobalConfiguration() {
|
|
186
|
-
exports.logger = new Logger();
|
|
187
|
-
if (process.env.DEBUG) {
|
|
188
|
-
exports.logger.logFilePathPromise.then((logPath) => {
|
|
189
|
-
exports.logger.info(chalk.yellow("[DEBUG MODE] Debug logging enabled"));
|
|
190
|
-
exports.logger.info(chalk.gray(`Log file: ${logPath}`));
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
208
|
+
let logger = new Logger();
|
|
194
209
|
|
|
195
210
|
const SessionMessageContentSchema = z.z.object({
|
|
196
211
|
c: z.z.string(),
|
|
@@ -219,10 +234,26 @@ const UpdateSessionBodySchema = z.z.object({
|
|
|
219
234
|
value: z.z.string()
|
|
220
235
|
}).nullish()
|
|
221
236
|
});
|
|
237
|
+
const UpdateMachineBodySchema = z.z.object({
|
|
238
|
+
t: z.z.literal("update-machine"),
|
|
239
|
+
machineId: z.z.string(),
|
|
240
|
+
metadata: z.z.object({
|
|
241
|
+
version: z.z.number(),
|
|
242
|
+
value: z.z.string()
|
|
243
|
+
}).nullish(),
|
|
244
|
+
daemonState: z.z.object({
|
|
245
|
+
version: z.z.number(),
|
|
246
|
+
value: z.z.string()
|
|
247
|
+
}).nullish()
|
|
248
|
+
});
|
|
222
249
|
z.z.object({
|
|
223
250
|
id: z.z.string(),
|
|
224
251
|
seq: z.z.number(),
|
|
225
|
-
body: z.z.union([
|
|
252
|
+
body: z.z.union([
|
|
253
|
+
UpdateBodySchema,
|
|
254
|
+
UpdateSessionBodySchema,
|
|
255
|
+
UpdateMachineBodySchema
|
|
256
|
+
]),
|
|
226
257
|
createdAt: z.z.number()
|
|
227
258
|
});
|
|
228
259
|
z.z.object({
|
|
@@ -235,6 +266,44 @@ z.z.object({
|
|
|
235
266
|
agentState: z.z.any().nullable(),
|
|
236
267
|
agentStateVersion: z.z.number()
|
|
237
268
|
});
|
|
269
|
+
z.z.object({
|
|
270
|
+
host: z.z.string(),
|
|
271
|
+
platform: z.z.string(),
|
|
272
|
+
happyCliVersion: z.z.string(),
|
|
273
|
+
homeDir: z.z.string(),
|
|
274
|
+
happyHomeDir: z.z.string()
|
|
275
|
+
});
|
|
276
|
+
z.z.object({
|
|
277
|
+
status: z.z.union([
|
|
278
|
+
z.z.enum(["running", "shutting-down"]),
|
|
279
|
+
z.z.string()
|
|
280
|
+
// Forward compatibility
|
|
281
|
+
]),
|
|
282
|
+
pid: z.z.number().optional(),
|
|
283
|
+
httpPort: z.z.number().optional(),
|
|
284
|
+
startedAt: z.z.number().optional(),
|
|
285
|
+
shutdownRequestedAt: z.z.number().optional(),
|
|
286
|
+
shutdownSource: z.z.union([
|
|
287
|
+
z.z.enum(["mobile-app", "cli", "os-signal", "unknown"]),
|
|
288
|
+
z.z.string()
|
|
289
|
+
// Forward compatibility
|
|
290
|
+
]).optional()
|
|
291
|
+
});
|
|
292
|
+
z.z.object({
|
|
293
|
+
id: z.z.string(),
|
|
294
|
+
metadata: z.z.any(),
|
|
295
|
+
// Decrypted MachineMetadata
|
|
296
|
+
metadataVersion: z.z.number(),
|
|
297
|
+
daemonState: z.z.any().nullable(),
|
|
298
|
+
// Decrypted DaemonState
|
|
299
|
+
daemonStateVersion: z.z.number(),
|
|
300
|
+
// We don't really care about these on the CLI for now
|
|
301
|
+
// ApiMachineClient will not sync these
|
|
302
|
+
active: z.z.boolean(),
|
|
303
|
+
activeAt: z.z.number(),
|
|
304
|
+
createdAt: z.z.number(),
|
|
305
|
+
updatedAt: z.z.number()
|
|
306
|
+
});
|
|
238
307
|
z.z.object({
|
|
239
308
|
content: SessionMessageContentSchema,
|
|
240
309
|
createdAt: z.z.number(),
|
|
@@ -325,6 +394,7 @@ function decrypt(data, secret) {
|
|
|
325
394
|
const encrypted = data.slice(tweetnacl.secretbox.nonceLength);
|
|
326
395
|
const decrypted = tweetnacl.secretbox.open(encrypted, nonce, secret);
|
|
327
396
|
if (!decrypted) {
|
|
397
|
+
logger.debug("[ERROR] Decryption failed");
|
|
328
398
|
return null;
|
|
329
399
|
}
|
|
330
400
|
return JSON.parse(new TextDecoder().decode(decrypted));
|
|
@@ -415,7 +485,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
415
485
|
this.metadataVersion = session.metadataVersion;
|
|
416
486
|
this.agentState = session.agentState;
|
|
417
487
|
this.agentStateVersion = session.agentStateVersion;
|
|
418
|
-
this.socket = socket_ioClient.io(
|
|
488
|
+
this.socket = socket_ioClient.io(configuration.serverUrl, {
|
|
419
489
|
auth: {
|
|
420
490
|
token: this.token,
|
|
421
491
|
clientType: "session-scoped",
|
|
@@ -431,7 +501,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
431
501
|
autoConnect: false
|
|
432
502
|
});
|
|
433
503
|
this.socket.on("connect", () => {
|
|
434
|
-
|
|
504
|
+
logger.debug("Socket connected successfully");
|
|
435
505
|
this.reregisterHandlers();
|
|
436
506
|
});
|
|
437
507
|
this.socket.on("rpc-request", async (data, callback) => {
|
|
@@ -439,7 +509,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
439
509
|
const method = data.method;
|
|
440
510
|
const handler = this.rpcHandlers.get(method);
|
|
441
511
|
if (!handler) {
|
|
442
|
-
|
|
512
|
+
logger.debug("[SOCKET] [RPC] [ERROR] method not found", { method });
|
|
443
513
|
const errorResponse = { error: "Method not found" };
|
|
444
514
|
const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
|
|
445
515
|
callback(encryptedError);
|
|
@@ -450,28 +520,28 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
450
520
|
const encryptedResponse = encodeBase64(encrypt(result, this.secret));
|
|
451
521
|
callback(encryptedResponse);
|
|
452
522
|
} catch (error) {
|
|
453
|
-
|
|
523
|
+
logger.debug("[SOCKET] [RPC] [ERROR] Error handling RPC request", { error });
|
|
454
524
|
const errorResponse = { error: error instanceof Error ? error.message : "Unknown error" };
|
|
455
525
|
const encryptedError = encodeBase64(encrypt(errorResponse, this.secret));
|
|
456
526
|
callback(encryptedError);
|
|
457
527
|
}
|
|
458
528
|
});
|
|
459
529
|
this.socket.on("disconnect", (reason) => {
|
|
460
|
-
|
|
530
|
+
logger.debug("[API] Socket disconnected:", reason);
|
|
461
531
|
});
|
|
462
532
|
this.socket.on("connect_error", (error) => {
|
|
463
|
-
|
|
533
|
+
logger.debug("[API] Socket connection error:", error);
|
|
464
534
|
});
|
|
465
535
|
this.socket.on("update", (data) => {
|
|
466
536
|
try {
|
|
467
|
-
|
|
537
|
+
logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", data);
|
|
468
538
|
if (!data.body) {
|
|
469
|
-
|
|
539
|
+
logger.debug("[SOCKET] [UPDATE] [ERROR] No body in update!");
|
|
470
540
|
return;
|
|
471
541
|
}
|
|
472
542
|
if (data.body.t === "new-message" && data.body.message.content.t === "encrypted") {
|
|
473
543
|
const body = decrypt(decodeBase64(data.body.message.content.c), this.secret);
|
|
474
|
-
|
|
544
|
+
logger.debugLargeJson("[SOCKET] [UPDATE] Received update:", body);
|
|
475
545
|
const userResult = UserMessageSchema.safeParse(body);
|
|
476
546
|
if (userResult.success) {
|
|
477
547
|
if (this.pendingMessageCallback) {
|
|
@@ -491,15 +561,17 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
491
561
|
this.agentState = data.body.agentState.value ? decrypt(decodeBase64(data.body.agentState.value), this.secret) : null;
|
|
492
562
|
this.agentStateVersion = data.body.agentState.version;
|
|
493
563
|
}
|
|
564
|
+
} else if (data.body.t === "update-machine") {
|
|
565
|
+
logger.debug(`[SOCKET] WARNING: Session client received unexpected machine update - ignoring`);
|
|
494
566
|
} else {
|
|
495
567
|
this.emit("message", data.body);
|
|
496
568
|
}
|
|
497
569
|
} catch (error) {
|
|
498
|
-
|
|
570
|
+
logger.debug("[SOCKET] [UPDATE] [ERROR] Error handling update", { error });
|
|
499
571
|
}
|
|
500
572
|
});
|
|
501
573
|
this.socket.on("error", (error) => {
|
|
502
|
-
|
|
574
|
+
logger.debug("[API] Socket error:", error);
|
|
503
575
|
});
|
|
504
576
|
this.socket.connect();
|
|
505
577
|
}
|
|
@@ -539,7 +611,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
539
611
|
}
|
|
540
612
|
};
|
|
541
613
|
}
|
|
542
|
-
|
|
614
|
+
logger.debugLargeJson("[SOCKET] Sending message through socket:", content);
|
|
543
615
|
const encrypted = encodeBase64(encrypt(content, this.secret));
|
|
544
616
|
this.socket.emit("message", {
|
|
545
617
|
sid: this.sessionId,
|
|
@@ -549,7 +621,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
549
621
|
try {
|
|
550
622
|
this.sendUsageData(body.message.usage);
|
|
551
623
|
} catch (error) {
|
|
552
|
-
|
|
624
|
+
logger.debug("[SOCKET] Failed to send usage data:", error);
|
|
553
625
|
}
|
|
554
626
|
}
|
|
555
627
|
if (body.type === "summary" && "summary" in body && "leafUuid" in body) {
|
|
@@ -617,7 +689,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
617
689
|
output: 0
|
|
618
690
|
}
|
|
619
691
|
};
|
|
620
|
-
|
|
692
|
+
logger.debugLargeJson("[SOCKET] Sending usage data:", usageReport);
|
|
621
693
|
this.socket.emit("usage-report", usageReport);
|
|
622
694
|
}
|
|
623
695
|
/**
|
|
@@ -647,7 +719,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
647
719
|
* @param handler - Handler function that returns the updated agent state
|
|
648
720
|
*/
|
|
649
721
|
updateAgentState(handler) {
|
|
650
|
-
|
|
722
|
+
logger.debugLargeJson("Updating agent state", this.agentState);
|
|
651
723
|
this.agentStateLock.inLock(async () => {
|
|
652
724
|
await backoff(async () => {
|
|
653
725
|
let updated = handler(this.agentState || {});
|
|
@@ -655,7 +727,7 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
655
727
|
if (answer.result === "success") {
|
|
656
728
|
this.agentState = answer.agentState ? decrypt(decodeBase64(answer.agentState), this.secret) : null;
|
|
657
729
|
this.agentStateVersion = answer.version;
|
|
658
|
-
|
|
730
|
+
logger.debug("Agent state updated", this.agentState);
|
|
659
731
|
} else if (answer.result === "version-mismatch") {
|
|
660
732
|
if (answer.version > this.agentStateVersion) {
|
|
661
733
|
this.agentStateVersion = answer.version;
|
|
@@ -675,18 +747,18 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
675
747
|
const prefixedMethod = `${this.sessionId}:${method}`;
|
|
676
748
|
this.rpcHandlers.set(prefixedMethod, handler);
|
|
677
749
|
this.socket.emit("rpc-register", { method: prefixedMethod });
|
|
678
|
-
|
|
750
|
+
logger.debug("Registered RPC handler", { method, prefixedMethod });
|
|
679
751
|
}
|
|
680
752
|
/**
|
|
681
753
|
* Re-register all RPC handlers after reconnection
|
|
682
754
|
*/
|
|
683
755
|
reregisterHandlers() {
|
|
684
|
-
|
|
756
|
+
logger.debug("Re-registering RPC handlers after reconnection", {
|
|
685
757
|
totalMethods: this.rpcHandlers.size
|
|
686
758
|
});
|
|
687
759
|
for (const [prefixedMethod] of this.rpcHandlers) {
|
|
688
760
|
this.socket.emit("rpc-register", { method: prefixedMethod });
|
|
689
|
-
|
|
761
|
+
logger.debug("Re-registered method", { prefixedMethod });
|
|
690
762
|
}
|
|
691
763
|
}
|
|
692
764
|
/**
|
|
@@ -710,6 +782,243 @@ class ApiSessionClient extends node_events.EventEmitter {
|
|
|
710
782
|
}
|
|
711
783
|
}
|
|
712
784
|
|
|
785
|
+
class ApiMachineClient {
|
|
786
|
+
constructor(token, secret, machine) {
|
|
787
|
+
this.token = token;
|
|
788
|
+
this.secret = secret;
|
|
789
|
+
this.machine = machine;
|
|
790
|
+
}
|
|
791
|
+
socket;
|
|
792
|
+
keepAliveInterval = null;
|
|
793
|
+
// RPC handlers
|
|
794
|
+
spawnSession;
|
|
795
|
+
stopSession;
|
|
796
|
+
requestShutdown;
|
|
797
|
+
setRPCHandlers({
|
|
798
|
+
spawnSession,
|
|
799
|
+
stopSession,
|
|
800
|
+
requestShutdown
|
|
801
|
+
}) {
|
|
802
|
+
this.spawnSession = spawnSession;
|
|
803
|
+
this.stopSession = stopSession;
|
|
804
|
+
this.requestShutdown = requestShutdown;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Update machine metadata
|
|
808
|
+
* Currently unused, changes from the mobile client are more likely
|
|
809
|
+
* for example to set a custom name.
|
|
810
|
+
*/
|
|
811
|
+
async updateMachineMetadata(handler) {
|
|
812
|
+
await backoff(async () => {
|
|
813
|
+
const updated = handler(this.machine.metadata);
|
|
814
|
+
const answer = await this.socket.emitWithAck("machine-update-metadata", {
|
|
815
|
+
machineId: this.machine.id,
|
|
816
|
+
metadata: encodeBase64(encrypt(updated, this.secret)),
|
|
817
|
+
expectedVersion: this.machine.metadataVersion
|
|
818
|
+
});
|
|
819
|
+
if (answer.result === "success") {
|
|
820
|
+
this.machine.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
821
|
+
this.machine.metadataVersion = answer.version;
|
|
822
|
+
logger.debug("[API MACHINE] Metadata updated successfully");
|
|
823
|
+
} else if (answer.result === "version-mismatch") {
|
|
824
|
+
if (answer.version > this.machine.metadataVersion) {
|
|
825
|
+
this.machine.metadataVersion = answer.version;
|
|
826
|
+
this.machine.metadata = decrypt(decodeBase64(answer.metadata), this.secret);
|
|
827
|
+
}
|
|
828
|
+
throw new Error("Metadata version mismatch");
|
|
829
|
+
}
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Update daemon state (runtime info) - similar to session updateAgentState
|
|
834
|
+
* Simplified without lock - relies on backoff for retry
|
|
835
|
+
*/
|
|
836
|
+
async updateDaemonState(handler) {
|
|
837
|
+
await backoff(async () => {
|
|
838
|
+
const updated = handler(this.machine.daemonState);
|
|
839
|
+
const answer = await this.socket.emitWithAck("machine-update-state", {
|
|
840
|
+
machineId: this.machine.id,
|
|
841
|
+
daemonState: encodeBase64(encrypt(updated, this.secret)),
|
|
842
|
+
expectedVersion: this.machine.daemonStateVersion
|
|
843
|
+
});
|
|
844
|
+
if (answer.result === "success") {
|
|
845
|
+
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState), this.secret);
|
|
846
|
+
this.machine.daemonStateVersion = answer.version;
|
|
847
|
+
logger.debug("[API MACHINE] Daemon state updated successfully");
|
|
848
|
+
} else if (answer.result === "version-mismatch") {
|
|
849
|
+
if (answer.version > this.machine.daemonStateVersion) {
|
|
850
|
+
this.machine.daemonStateVersion = answer.version;
|
|
851
|
+
this.machine.daemonState = decrypt(decodeBase64(answer.daemonState), this.secret);
|
|
852
|
+
}
|
|
853
|
+
throw new Error("Daemon state version mismatch");
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
connect() {
|
|
858
|
+
const serverUrl = configuration.serverUrl.replace(/^http/, "ws");
|
|
859
|
+
logger.debug(`[API MACHINE] Connecting to ${serverUrl}`);
|
|
860
|
+
this.socket = socket_ioClient.io(serverUrl, {
|
|
861
|
+
transports: ["websocket"],
|
|
862
|
+
auth: {
|
|
863
|
+
token: this.token,
|
|
864
|
+
clientType: "machine-scoped",
|
|
865
|
+
machineId: this.machine.id
|
|
866
|
+
},
|
|
867
|
+
path: "/v1/updates",
|
|
868
|
+
reconnection: true,
|
|
869
|
+
reconnectionDelay: 1e3,
|
|
870
|
+
reconnectionDelayMax: 5e3
|
|
871
|
+
});
|
|
872
|
+
const spawnMethod = `${this.machine.id}:spawn-happy-session`;
|
|
873
|
+
const stopMethod = `${this.machine.id}:stop-session`;
|
|
874
|
+
const stopDaemonMethod = `${this.machine.id}:stop-daemon`;
|
|
875
|
+
this.socket.on("connect", () => {
|
|
876
|
+
logger.debug("[API MACHINE] Connected to server");
|
|
877
|
+
this.updateDaemonState((state) => ({
|
|
878
|
+
...state,
|
|
879
|
+
status: "running",
|
|
880
|
+
pid: process.pid,
|
|
881
|
+
httpPort: this.machine.daemonState?.httpPort,
|
|
882
|
+
startedAt: Date.now()
|
|
883
|
+
}));
|
|
884
|
+
this.socket.emit("rpc-register", { method: spawnMethod });
|
|
885
|
+
this.socket.emit("rpc-register", { method: stopMethod });
|
|
886
|
+
this.socket.emit("rpc-register", { method: stopDaemonMethod });
|
|
887
|
+
logger.debug(`[API MACHINE] Registered RPC methods: ${spawnMethod}, ${stopMethod}, ${stopDaemonMethod}`);
|
|
888
|
+
this.startKeepAlive();
|
|
889
|
+
});
|
|
890
|
+
this.socket.on("rpc-request", async (data, callback) => {
|
|
891
|
+
logger.debugLargeJson(`[API MACHINE] Received RPC request:`, data);
|
|
892
|
+
try {
|
|
893
|
+
const spawnMethod2 = `${this.machine.id}:spawn-happy-session`;
|
|
894
|
+
const stopMethod2 = `${this.machine.id}:stop-session`;
|
|
895
|
+
const stopDaemonMethod2 = `${this.machine.id}:stop-daemon`;
|
|
896
|
+
if (data.method === spawnMethod2) {
|
|
897
|
+
if (!this.spawnSession) {
|
|
898
|
+
throw new Error("Spawn session handler not set");
|
|
899
|
+
}
|
|
900
|
+
const { directory, sessionId } = decrypt(decodeBase64(data.params), this.secret) || {};
|
|
901
|
+
if (!directory) {
|
|
902
|
+
throw new Error("Directory is required");
|
|
903
|
+
}
|
|
904
|
+
const session = await this.spawnSession(directory, sessionId);
|
|
905
|
+
if (!session) {
|
|
906
|
+
throw new Error("Failed to spawn session");
|
|
907
|
+
}
|
|
908
|
+
logger.debug(`[API MACHINE] Spawned session ${session.happySessionId || "pending"} with PID ${session.pid}`);
|
|
909
|
+
if (!session.happySessionId) {
|
|
910
|
+
throw new Error(`Session spawned (PID ${session.pid}) but no sessionId received from webhook. The session process may still be initializing.`);
|
|
911
|
+
}
|
|
912
|
+
const response = { sessionId: session.happySessionId };
|
|
913
|
+
logger.debug(`[API MACHINE] Sending RPC response:`, response);
|
|
914
|
+
callback(encodeBase64(encrypt(response, this.secret)));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (data.method === stopMethod2) {
|
|
918
|
+
logger.debug("[API MACHINE] Received stop-session RPC request");
|
|
919
|
+
const decryptedParams = decrypt(decodeBase64(data.params), this.secret);
|
|
920
|
+
const { sessionId } = decryptedParams || {};
|
|
921
|
+
if (!this.stopSession) {
|
|
922
|
+
throw new Error("Stop session handler not set");
|
|
923
|
+
}
|
|
924
|
+
if (!sessionId) {
|
|
925
|
+
throw new Error("Session ID is required");
|
|
926
|
+
}
|
|
927
|
+
const success = this.stopSession(sessionId);
|
|
928
|
+
if (!success) {
|
|
929
|
+
throw new Error("Session not found or failed to stop");
|
|
930
|
+
}
|
|
931
|
+
logger.debug(`[API MACHINE] Stopped session ${sessionId}`);
|
|
932
|
+
const response = { message: "Session stopped" };
|
|
933
|
+
const encryptedResponse = encodeBase64(encrypt(response, this.secret));
|
|
934
|
+
callback(encryptedResponse);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (data.method === stopDaemonMethod2) {
|
|
938
|
+
logger.debug("[API MACHINE] Received stop-daemon RPC request");
|
|
939
|
+
callback(encodeBase64(encrypt({
|
|
940
|
+
message: "Daemon stop request acknowledged, starting shutdown sequence..."
|
|
941
|
+
}, this.secret)));
|
|
942
|
+
setTimeout(() => {
|
|
943
|
+
logger.debug("[API MACHINE] Initiating daemon shutdown from RPC");
|
|
944
|
+
if (this.requestShutdown) {
|
|
945
|
+
this.requestShutdown();
|
|
946
|
+
}
|
|
947
|
+
}, 100);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
throw new Error(`Unknown RPC method: ${data.method}`);
|
|
951
|
+
} catch (error) {
|
|
952
|
+
logger.debug(`[API MACHINE] RPC handler failed:`, error.message || error);
|
|
953
|
+
logger.debug(`[API MACHINE] Error stack:`, error.stack);
|
|
954
|
+
callback(encodeBase64(encrypt({ error: error.message || String(error) }, this.secret)));
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
this.socket.on("update", (data) => {
|
|
958
|
+
if (data.body.t === "update-machine" && data.body.machineId === this.machine.id) {
|
|
959
|
+
const update = data.body;
|
|
960
|
+
if (update.metadata) {
|
|
961
|
+
logger.debug("[API MACHINE] Received external metadata update");
|
|
962
|
+
this.machine.metadata = decrypt(decodeBase64(update.metadata.value), this.secret);
|
|
963
|
+
this.machine.metadataVersion = update.metadata.version;
|
|
964
|
+
}
|
|
965
|
+
if (update.daemonState) {
|
|
966
|
+
logger.debug("[API MACHINE] Received external daemon state update");
|
|
967
|
+
this.machine.daemonState = decrypt(decodeBase64(update.daemonState.value), this.secret);
|
|
968
|
+
this.machine.daemonStateVersion = update.daemonState.version;
|
|
969
|
+
}
|
|
970
|
+
} else {
|
|
971
|
+
logger.debug(`[API MACHINE] Received unknown update type: ${data.body.t}`);
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
this.socket.on("disconnect", () => {
|
|
975
|
+
logger.debug("[API MACHINE] Disconnected from server");
|
|
976
|
+
this.stopKeepAlive();
|
|
977
|
+
});
|
|
978
|
+
this.socket.io.on("reconnect", () => {
|
|
979
|
+
logger.debug("[API MACHINE] Reconnected to server");
|
|
980
|
+
this.socket.emit("rpc-register", { method: spawnMethod });
|
|
981
|
+
this.socket.emit("rpc-register", { method: stopMethod });
|
|
982
|
+
this.socket.emit("rpc-register", { method: stopDaemonMethod });
|
|
983
|
+
});
|
|
984
|
+
this.socket.on("connect_error", (error) => {
|
|
985
|
+
logger.debug(`[API MACHINE] Connection error: ${error.message}`);
|
|
986
|
+
});
|
|
987
|
+
this.socket.io.on("error", (error) => {
|
|
988
|
+
logger.debug("[API MACHINE] Socket error:", error);
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
startKeepAlive() {
|
|
992
|
+
this.stopKeepAlive();
|
|
993
|
+
this.keepAliveInterval = setInterval(() => {
|
|
994
|
+
const payload = {
|
|
995
|
+
machineId: this.machine.id,
|
|
996
|
+
time: Date.now()
|
|
997
|
+
};
|
|
998
|
+
if (process.env.VERBOSE) {
|
|
999
|
+
logger.debugLargeJson(`[API MACHINE] Emitting machine-alive`, payload);
|
|
1000
|
+
}
|
|
1001
|
+
this.socket.emit("machine-alive", payload);
|
|
1002
|
+
}, 2e4);
|
|
1003
|
+
logger.debug("[API MACHINE] Keep-alive started (20s interval)");
|
|
1004
|
+
}
|
|
1005
|
+
stopKeepAlive() {
|
|
1006
|
+
if (this.keepAliveInterval) {
|
|
1007
|
+
clearInterval(this.keepAliveInterval);
|
|
1008
|
+
this.keepAliveInterval = null;
|
|
1009
|
+
logger.debug("[API MACHINE] Keep-alive stopped");
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
shutdown() {
|
|
1013
|
+
logger.debug("[API MACHINE] Shutting down");
|
|
1014
|
+
this.stopKeepAlive();
|
|
1015
|
+
if (this.socket) {
|
|
1016
|
+
this.socket.close();
|
|
1017
|
+
logger.debug("[API MACHINE] Socket closed");
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
713
1022
|
class PushNotificationClient {
|
|
714
1023
|
token;
|
|
715
1024
|
baseUrl;
|
|
@@ -733,13 +1042,13 @@ class PushNotificationClient {
|
|
|
733
1042
|
}
|
|
734
1043
|
}
|
|
735
1044
|
);
|
|
736
|
-
|
|
1045
|
+
logger.debug(`Fetched ${response.data.tokens.length} push tokens`);
|
|
737
1046
|
response.data.tokens.forEach((token, index) => {
|
|
738
|
-
|
|
1047
|
+
logger.debug(`[PUSH] Token ${index + 1}: id=${token.id}, token=${token.token}, created=${new Date(token.createdAt).toISOString()}, updated=${new Date(token.updatedAt).toISOString()}`);
|
|
739
1048
|
});
|
|
740
1049
|
return response.data.tokens;
|
|
741
1050
|
} catch (error) {
|
|
742
|
-
|
|
1051
|
+
logger.debug("[PUSH] [ERROR] Failed to fetch push tokens:", error);
|
|
743
1052
|
throw new Error(`Failed to fetch push tokens: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
744
1053
|
}
|
|
745
1054
|
}
|
|
@@ -748,7 +1057,7 @@ class PushNotificationClient {
|
|
|
748
1057
|
* @param messages - Array of push messages to send
|
|
749
1058
|
*/
|
|
750
1059
|
async sendPushNotifications(messages) {
|
|
751
|
-
|
|
1060
|
+
logger.debug(`Sending ${messages.length} push notifications`);
|
|
752
1061
|
const validMessages = messages.filter((message) => {
|
|
753
1062
|
if (Array.isArray(message.to)) {
|
|
754
1063
|
return message.to.every((token) => expoServerSdk.Expo.isExpoPushToken(token));
|
|
@@ -756,7 +1065,7 @@ class PushNotificationClient {
|
|
|
756
1065
|
return expoServerSdk.Expo.isExpoPushToken(message.to);
|
|
757
1066
|
});
|
|
758
1067
|
if (validMessages.length === 0) {
|
|
759
|
-
|
|
1068
|
+
logger.debug("No valid Expo push tokens found");
|
|
760
1069
|
return;
|
|
761
1070
|
}
|
|
762
1071
|
const chunks = this.expo.chunkPushNotifications(validMessages);
|
|
@@ -770,7 +1079,7 @@ class PushNotificationClient {
|
|
|
770
1079
|
const errors = ticketChunk.filter((ticket) => ticket.status === "error");
|
|
771
1080
|
if (errors.length > 0) {
|
|
772
1081
|
const errorDetails = errors.map((e) => ({ message: e.message, details: e.details }));
|
|
773
|
-
|
|
1082
|
+
logger.debug("[PUSH] Some notifications failed:", errorDetails);
|
|
774
1083
|
}
|
|
775
1084
|
if (errors.length === ticketChunk.length) {
|
|
776
1085
|
throw new Error("All push notifications in chunk failed");
|
|
@@ -779,7 +1088,7 @@ class PushNotificationClient {
|
|
|
779
1088
|
} catch (error) {
|
|
780
1089
|
const elapsed = Date.now() - startTime;
|
|
781
1090
|
if (elapsed >= timeout) {
|
|
782
|
-
|
|
1091
|
+
logger.debug("[PUSH] Timeout reached after 5 minutes, giving up on chunk");
|
|
783
1092
|
break;
|
|
784
1093
|
}
|
|
785
1094
|
attempt++;
|
|
@@ -787,13 +1096,13 @@ class PushNotificationClient {
|
|
|
787
1096
|
const remainingTime = timeout - elapsed;
|
|
788
1097
|
const waitTime = Math.min(delay, remainingTime);
|
|
789
1098
|
if (waitTime > 0) {
|
|
790
|
-
|
|
1099
|
+
logger.debug(`[PUSH] Retrying in ${waitTime}ms (attempt ${attempt})`);
|
|
791
1100
|
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
792
1101
|
}
|
|
793
1102
|
}
|
|
794
1103
|
}
|
|
795
1104
|
}
|
|
796
|
-
|
|
1105
|
+
logger.debug(`Push notifications sent successfully`);
|
|
797
1106
|
}
|
|
798
1107
|
/**
|
|
799
1108
|
* Send a push notification to all registered devices for the user
|
|
@@ -802,21 +1111,21 @@ class PushNotificationClient {
|
|
|
802
1111
|
* @param data - Additional data to send with the notification
|
|
803
1112
|
*/
|
|
804
1113
|
sendToAllDevices(title, body, data) {
|
|
805
|
-
|
|
1114
|
+
logger.debug(`[PUSH] sendToAllDevices called with title: "${title}", body: "${body}"`);
|
|
806
1115
|
(async () => {
|
|
807
1116
|
try {
|
|
808
|
-
|
|
1117
|
+
logger.debug("[PUSH] Fetching push tokens...");
|
|
809
1118
|
const tokens = await this.fetchPushTokens();
|
|
810
|
-
|
|
1119
|
+
logger.debug(`[PUSH] Fetched ${tokens.length} push tokens`);
|
|
811
1120
|
tokens.forEach((token, index) => {
|
|
812
|
-
|
|
1121
|
+
logger.debug(`[PUSH] Using token ${index + 1}: id=${token.id}, token=${token.token}`);
|
|
813
1122
|
});
|
|
814
1123
|
if (tokens.length === 0) {
|
|
815
|
-
|
|
1124
|
+
logger.debug("No push tokens found for user");
|
|
816
1125
|
return;
|
|
817
1126
|
}
|
|
818
1127
|
const messages = tokens.map((token, index) => {
|
|
819
|
-
|
|
1128
|
+
logger.debug(`[PUSH] Creating message ${index + 1} for token: ${token.token}`);
|
|
820
1129
|
return {
|
|
821
1130
|
to: token.token,
|
|
822
1131
|
title,
|
|
@@ -826,11 +1135,11 @@ class PushNotificationClient {
|
|
|
826
1135
|
priority: "high"
|
|
827
1136
|
};
|
|
828
1137
|
});
|
|
829
|
-
|
|
1138
|
+
logger.debug(`[PUSH] Sending ${messages.length} push notifications...`);
|
|
830
1139
|
await this.sendPushNotifications(messages);
|
|
831
|
-
|
|
1140
|
+
logger.debug("[PUSH] Push notifications sent successfully");
|
|
832
1141
|
} catch (error) {
|
|
833
|
-
|
|
1142
|
+
logger.debug("[PUSH] Error sending to all devices:", error);
|
|
834
1143
|
}
|
|
835
1144
|
})();
|
|
836
1145
|
}
|
|
@@ -851,7 +1160,7 @@ class ApiClient {
|
|
|
851
1160
|
async getOrCreateSession(opts) {
|
|
852
1161
|
try {
|
|
853
1162
|
const response = await axios.post(
|
|
854
|
-
`${
|
|
1163
|
+
`${configuration.serverUrl}/v1/sessions`,
|
|
855
1164
|
{
|
|
856
1165
|
tag: opts.tag,
|
|
857
1166
|
metadata: encodeBase64(encrypt(opts.metadata, this.secret)),
|
|
@@ -861,10 +1170,12 @@ class ApiClient {
|
|
|
861
1170
|
headers: {
|
|
862
1171
|
"Authorization": `Bearer ${this.token}`,
|
|
863
1172
|
"Content-Type": "application/json"
|
|
864
|
-
}
|
|
1173
|
+
},
|
|
1174
|
+
timeout: 5e3
|
|
1175
|
+
// 5 second timeout
|
|
865
1176
|
}
|
|
866
1177
|
);
|
|
867
|
-
|
|
1178
|
+
logger.debug(`Session created/loaded: ${response.data.session.id} (tag: ${opts.tag})`);
|
|
868
1179
|
let raw = response.data.session;
|
|
869
1180
|
let session = {
|
|
870
1181
|
id: raw.id,
|
|
@@ -878,22 +1189,81 @@ class ApiClient {
|
|
|
878
1189
|
};
|
|
879
1190
|
return session;
|
|
880
1191
|
} catch (error) {
|
|
881
|
-
|
|
1192
|
+
logger.debug("[API] [ERROR] Failed to get or create session:", error);
|
|
882
1193
|
throw new Error(`Failed to get or create session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
883
1194
|
}
|
|
884
1195
|
}
|
|
885
1196
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
* @returns Session client
|
|
1197
|
+
* Get machine by ID from the server
|
|
1198
|
+
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
889
1199
|
*/
|
|
890
|
-
|
|
891
|
-
|
|
1200
|
+
async getMachine(machineId) {
|
|
1201
|
+
const response = await axios.get(`${configuration.serverUrl}/v1/machines/${machineId}`, {
|
|
1202
|
+
headers: {
|
|
1203
|
+
"Authorization": `Bearer ${this.token}`,
|
|
1204
|
+
"Content-Type": "application/json"
|
|
1205
|
+
},
|
|
1206
|
+
timeout: 2e3
|
|
1207
|
+
});
|
|
1208
|
+
const raw = response.data.machine;
|
|
1209
|
+
if (!raw) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
logger.debug(`[API] Machine ${machineId} fetched from server`);
|
|
1213
|
+
const machine = {
|
|
1214
|
+
id: raw.id,
|
|
1215
|
+
metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
|
|
1216
|
+
metadataVersion: raw.metadataVersion || 0,
|
|
1217
|
+
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
|
|
1218
|
+
daemonStateVersion: raw.daemonStateVersion || 0,
|
|
1219
|
+
active: raw.active,
|
|
1220
|
+
activeAt: raw.activeAt,
|
|
1221
|
+
createdAt: raw.createdAt,
|
|
1222
|
+
updatedAt: raw.updatedAt
|
|
1223
|
+
};
|
|
1224
|
+
return machine;
|
|
892
1225
|
}
|
|
893
1226
|
/**
|
|
894
|
-
*
|
|
895
|
-
*
|
|
1227
|
+
* Register or update machine with the server
|
|
1228
|
+
* Returns the current machine state from the server with decrypted metadata and daemonState
|
|
896
1229
|
*/
|
|
1230
|
+
async createOrReturnExistingAsIs(opts) {
|
|
1231
|
+
const response = await axios.post(
|
|
1232
|
+
`${configuration.serverUrl}/v1/machines`,
|
|
1233
|
+
{
|
|
1234
|
+
id: opts.machineId,
|
|
1235
|
+
metadata: encodeBase64(encrypt(opts.metadata, this.secret)),
|
|
1236
|
+
daemonState: opts.daemonState ? encodeBase64(encrypt(opts.daemonState, this.secret)) : void 0
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
headers: {
|
|
1240
|
+
"Authorization": `Bearer ${this.token}`,
|
|
1241
|
+
"Content-Type": "application/json"
|
|
1242
|
+
},
|
|
1243
|
+
timeout: 5e3
|
|
1244
|
+
}
|
|
1245
|
+
);
|
|
1246
|
+
const raw = response.data.machine;
|
|
1247
|
+
logger.debug(`[API] Machine ${opts.machineId} registered/updated with server`);
|
|
1248
|
+
const machine = {
|
|
1249
|
+
id: raw.id,
|
|
1250
|
+
metadata: raw.metadata ? decrypt(decodeBase64(raw.metadata), this.secret) : null,
|
|
1251
|
+
metadataVersion: raw.metadataVersion || 0,
|
|
1252
|
+
daemonState: raw.daemonState ? decrypt(decodeBase64(raw.daemonState), this.secret) : null,
|
|
1253
|
+
daemonStateVersion: raw.daemonStateVersion || 0,
|
|
1254
|
+
active: raw.active,
|
|
1255
|
+
activeAt: raw.activeAt,
|
|
1256
|
+
createdAt: raw.createdAt,
|
|
1257
|
+
updatedAt: raw.updatedAt
|
|
1258
|
+
};
|
|
1259
|
+
return machine;
|
|
1260
|
+
}
|
|
1261
|
+
sessionSyncClient(session) {
|
|
1262
|
+
return new ApiSessionClient(this.token, this.secret, session);
|
|
1263
|
+
}
|
|
1264
|
+
machineSyncClient(machine) {
|
|
1265
|
+
return new ApiMachineClient(this.token, this.secret, machine);
|
|
1266
|
+
}
|
|
897
1267
|
push() {
|
|
898
1268
|
return this.pushClient;
|
|
899
1269
|
}
|
|
@@ -951,10 +1321,9 @@ exports.ApiClient = ApiClient;
|
|
|
951
1321
|
exports.ApiSessionClient = ApiSessionClient;
|
|
952
1322
|
exports.RawJSONLinesSchema = RawJSONLinesSchema;
|
|
953
1323
|
exports.backoff = backoff;
|
|
1324
|
+
exports.configuration = configuration;
|
|
954
1325
|
exports.decodeBase64 = decodeBase64;
|
|
955
1326
|
exports.delay = delay;
|
|
956
1327
|
exports.encodeBase64 = encodeBase64;
|
|
957
1328
|
exports.encodeBase64Url = encodeBase64Url;
|
|
958
|
-
exports.
|
|
959
|
-
exports.initLoggerWithGlobalConfiguration = initLoggerWithGlobalConfiguration;
|
|
960
|
-
exports.initializeConfiguration = initializeConfiguration;
|
|
1329
|
+
exports.logger = logger;
|