clay-server 2.23.1 → 2.23.2-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/lib/build-user-env.js +6 -0
- package/lib/project.js +2 -0
- package/lib/public/modules/notifications.js +5 -1
- package/lib/sdk-bridge.js +83 -78
- package/lib/sdk-worker.js +56 -3
- package/package.json +1 -1
package/lib/build-user-env.js
CHANGED
|
@@ -51,6 +51,12 @@ function buildUserEnv(osUserInfo) {
|
|
|
51
51
|
env.XDG_RUNTIME_DIR = process.env.XDG_RUNTIME_DIR;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// Force Node.js to prefer IPv4. Without this, the SDK CLI subprocess
|
|
55
|
+
// tries IPv6 first (happy eyeballs), times out on servers without IPv6
|
|
56
|
+
// outbound, then falls back to IPv4. This causes multi-second delays
|
|
57
|
+
// on cold start (compounded by exponential backoff retries).
|
|
58
|
+
env.NODE_OPTIONS = (env.NODE_OPTIONS ? env.NODE_OPTIONS + " " : "") + "--dns-result-order=ipv4first";
|
|
59
|
+
|
|
54
60
|
return env;
|
|
55
61
|
}
|
|
56
62
|
|
package/lib/project.js
CHANGED
|
@@ -3788,6 +3788,8 @@ function createProjectContext(opts) {
|
|
|
3788
3788
|
sendToSession(session.localId, { type: "status", status: "processing" });
|
|
3789
3789
|
if (!session.queryInstance && (!session.worker || session.messageQueue !== "worker")) {
|
|
3790
3790
|
// No active query (or worker idle between queries): start a new query
|
|
3791
|
+
session._queryStartTs = Date.now();
|
|
3792
|
+
console.log("[PERF] project.js: startQuery called, localId=" + session.localId + " t=0ms");
|
|
3791
3793
|
sdk.startQuery(session, fullText, msg.images, getLinuxUserForSession(session));
|
|
3792
3794
|
} else {
|
|
3793
3795
|
sdk.pushMessage(session, fullText, msg.images);
|
|
@@ -72,6 +72,10 @@ export function initNotifications(_ctx) {
|
|
|
72
72
|
if (window.visualViewport) {
|
|
73
73
|
var layout = $("layout");
|
|
74
74
|
var mobileTabBar = document.getElementById("mobile-tab-bar");
|
|
75
|
+
// Capture initial viewport height before any keyboard interaction.
|
|
76
|
+
// In iOS PWA standalone mode, window.innerHeight shrinks when the
|
|
77
|
+
// keyboard opens, so we need a stable baseline for comparison.
|
|
78
|
+
var stableViewportHeight = window.innerHeight;
|
|
75
79
|
function onViewportChange() {
|
|
76
80
|
var vv = window.visualViewport;
|
|
77
81
|
// Shrink layout to visual viewport height so input area sits above keyboard
|
|
@@ -80,7 +84,7 @@ export function initNotifications(_ctx) {
|
|
|
80
84
|
layout.style.top = vv.offsetTop + "px";
|
|
81
85
|
document.documentElement.scrollTop = 0;
|
|
82
86
|
// Toggle class so CSS can remove the tab-bar bottom padding while keyboard is up
|
|
83
|
-
var keyboardOpen = vv.height <
|
|
87
|
+
var keyboardOpen = vv.height < stableViewportHeight - 100;
|
|
84
88
|
document.body.classList.toggle("keyboard-open", keyboardOpen);
|
|
85
89
|
if (!keyboardOpen) ctx.scrollToBottom();
|
|
86
90
|
// Hide tab bar when software keyboard is open
|
package/lib/sdk-bridge.js
CHANGED
|
@@ -189,6 +189,26 @@ function createSDKBridge(opts) {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
function processSDKMessage(session, parsed) {
|
|
192
|
+
// Timing: log key SDK milestones relative to query start
|
|
193
|
+
if (session._queryStartTs) {
|
|
194
|
+
var _elapsed = Date.now() - session._queryStartTs;
|
|
195
|
+
if (parsed.type === "system" && parsed.subtype === "init") {
|
|
196
|
+
console.log("[PERF] processSDKMessage: system/init +" + _elapsed + "ms");
|
|
197
|
+
}
|
|
198
|
+
if (parsed.type === "stream_event" && parsed.event) {
|
|
199
|
+
if (parsed.event.type === "message_start") {
|
|
200
|
+
console.log("[PERF] processSDKMessage: message_start (API response begun) +" + _elapsed + "ms");
|
|
201
|
+
}
|
|
202
|
+
if (parsed.event.type === "content_block_delta" && !session._firstTextLogged) {
|
|
203
|
+
session._firstTextLogged = true;
|
|
204
|
+
console.log("[PERF] processSDKMessage: FIRST content_block_delta (visible text) +" + _elapsed + "ms");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (parsed.type === "result") {
|
|
208
|
+
console.log("[PERF] processSDKMessage: result +" + _elapsed + "ms");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
192
212
|
// Extract session_id from any message that carries it
|
|
193
213
|
if (parsed.session_id && !session.cliSessionId) {
|
|
194
214
|
session.cliSessionId = parsed.session_id;
|
|
@@ -756,7 +776,9 @@ function createSDKBridge(opts) {
|
|
|
756
776
|
});
|
|
757
777
|
|
|
758
778
|
// Create Unix socket server
|
|
779
|
+
var spawnT0 = Date.now();
|
|
759
780
|
worker.server = net.createServer(function(connection) {
|
|
781
|
+
console.log("[PERF] spawnWorker: socket connection accepted +" + (Date.now() - spawnT0) + "ms");
|
|
760
782
|
worker.connection = connection;
|
|
761
783
|
connection.on("data", function(chunk) {
|
|
762
784
|
worker.buffer += chunk.toString();
|
|
@@ -767,6 +789,7 @@ function createSDKBridge(opts) {
|
|
|
767
789
|
try {
|
|
768
790
|
var msg = JSON.parse(lines[i]);
|
|
769
791
|
if (msg.type === "ready") {
|
|
792
|
+
console.log("[PERF] spawnWorker: 'ready' IPC received +" + (Date.now() - spawnT0) + "ms");
|
|
770
793
|
worker.ready = true;
|
|
771
794
|
if (worker._readyResolve) {
|
|
772
795
|
worker._readyResolve();
|
|
@@ -787,6 +810,7 @@ function createSDKBridge(opts) {
|
|
|
787
810
|
});
|
|
788
811
|
|
|
789
812
|
worker.server.listen(socketPath, function() {
|
|
813
|
+
console.log("[PERF] spawnWorker: socket listen ready +" + (Date.now() - spawnT0) + "ms");
|
|
790
814
|
// Set socket permissions so the target user can connect
|
|
791
815
|
try { fs.chmodSync(socketPath, 0o777); } catch (e) {}
|
|
792
816
|
|
|
@@ -933,125 +957,96 @@ function createSDKBridge(opts) {
|
|
|
933
957
|
* Mirrors the in-process startQuery flow but delegates SDK execution to the worker.
|
|
934
958
|
*/
|
|
935
959
|
async function startQueryViaWorker(session, text, images, linuxUser) {
|
|
960
|
+
var t0 = session._queryStartTs || Date.now();
|
|
961
|
+
function perf(label) { console.log("[PERF] sdk-bridge: " + label + " +" + (Date.now() - t0) + "ms"); }
|
|
962
|
+
perf("startQueryViaWorker entered");
|
|
963
|
+
|
|
936
964
|
// Wait for the previous worker to fully exit before spawning a new one.
|
|
937
965
|
// Without this, the new worker may try to resume the SDK session file
|
|
938
966
|
// while the old worker is still flushing it to disk (800ms grace period),
|
|
939
967
|
// causing "no conversation found" and losing all prior context.
|
|
940
968
|
if (session._workerExitPromise) {
|
|
941
|
-
|
|
969
|
+
perf("waiting for old worker exit");
|
|
942
970
|
var exitWait = session._workerExitPromise;
|
|
943
971
|
session._workerExitPromise = null;
|
|
944
972
|
await Promise.race([
|
|
945
973
|
exitWait,
|
|
946
974
|
new Promise(function(resolve) { setTimeout(resolve, 3000); }),
|
|
947
975
|
]);
|
|
948
|
-
|
|
976
|
+
perf("old worker exit wait done");
|
|
949
977
|
}
|
|
950
978
|
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
if (
|
|
979
|
+
// Reuse existing worker if alive, otherwise spawn a new one.
|
|
980
|
+
// Spawn FIRST so the worker starts booting while we do dir setup below.
|
|
981
|
+
var worker;
|
|
982
|
+
var reusingWorker = false;
|
|
983
|
+
if (session.worker && session.worker.ready && session.worker.process && !session.worker.process.killed) {
|
|
984
|
+
worker = session.worker;
|
|
985
|
+
reusingWorker = true;
|
|
986
|
+
// Clear old message handlers so they don't fire for the new query
|
|
987
|
+
worker.messageHandlers = [];
|
|
988
|
+
worker._queryEnded = false;
|
|
989
|
+
worker._abortSent = false;
|
|
990
|
+
perf("reusing existing worker pid=" + (worker.process ? worker.process.pid : "?"));
|
|
991
|
+
} else {
|
|
956
992
|
try {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
var claudeDir = path.join(linuxUserHome0, ".claude");
|
|
962
|
-
var projectSlug0 = (cwd || "").replace(/\//g, "-");
|
|
963
|
-
var projDir = path.join(claudeDir, "projects", projectSlug0);
|
|
964
|
-
// Create the project directory if missing and chown the whole tree
|
|
965
|
-
if (!fs.existsSync(projDir)) {
|
|
966
|
-
fs.mkdirSync(projDir, { recursive: true });
|
|
967
|
-
try { require("child_process").execSync("chown -R " + uid0 + " " + JSON.stringify(claudeDir)); } catch (e) {}
|
|
968
|
-
} else {
|
|
969
|
-
// Fix existing directory if root-owned
|
|
970
|
-
try {
|
|
971
|
-
var pstat = fs.statSync(projDir);
|
|
972
|
-
if (pstat.uid !== uid0) {
|
|
973
|
-
require("child_process").execSync("chown " + uid0 + " " + JSON.stringify(projDir));
|
|
974
|
-
}
|
|
975
|
-
} catch (e) {}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
993
|
+
perf("spawning new worker");
|
|
994
|
+
worker = spawnWorker(linuxUser);
|
|
995
|
+
perf("spawnWorker returned");
|
|
996
|
+
session.worker = worker;
|
|
978
997
|
} catch (e) {
|
|
979
|
-
|
|
998
|
+
session.isProcessing = false;
|
|
999
|
+
onProcessingChanged();
|
|
1000
|
+
sendAndRecord(session, { type: "error", text: "Failed to spawn worker for " + linuxUser + ": " + (e.message || e) });
|
|
1001
|
+
sendAndRecord(session, { type: "done", code: 1 });
|
|
1002
|
+
sm.broadcastSessionList();
|
|
1003
|
+
return;
|
|
980
1004
|
}
|
|
981
1005
|
}
|
|
982
1006
|
|
|
983
|
-
//
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
|
|
1007
|
+
// Ensure the linux user's .claude project directory exists and is writable,
|
|
1008
|
+
// then pre-copy CLI session file if needed. This runs while the worker is
|
|
1009
|
+
// booting (readyPromise pending), so it adds no extra latency.
|
|
1010
|
+
perf("dir setup start");
|
|
1011
|
+
if (linuxUser) {
|
|
987
1012
|
try {
|
|
988
1013
|
var configMod = require("./config");
|
|
989
1014
|
var osUsersMod = require("./os-users");
|
|
990
1015
|
var originalHome = configMod.REAL_HOME || require("os").homedir();
|
|
991
1016
|
var linuxUserHome = osUsersMod.getLinuxUserHome(linuxUser);
|
|
992
|
-
|
|
1017
|
+
var uid = osUsersMod.getLinuxUserUid(linuxUser);
|
|
1018
|
+
if (originalHome !== linuxUserHome && uid != null) {
|
|
993
1019
|
var projectSlug = (cwd || "").replace(/\//g, "-");
|
|
994
|
-
var sessionFileName = session.cliSessionId + ".jsonl";
|
|
995
|
-
var srcFile = path.join(originalHome, ".claude", "projects", projectSlug, sessionFileName);
|
|
996
1020
|
var dstDir = path.join(linuxUserHome, ".claude", "projects", projectSlug);
|
|
997
|
-
|
|
998
|
-
var uid = osUsersMod.getLinuxUserUid(linuxUser);
|
|
999
|
-
// Ensure the projects directory is owned by the linux user so the
|
|
1000
|
-
// SDK can create new session files. Without this, mkdirSync creates
|
|
1001
|
-
// root-owned directories and the SDK silently fails to save sessions.
|
|
1021
|
+
// Create and chown the project directory once
|
|
1002
1022
|
if (!fs.existsSync(dstDir)) {
|
|
1003
1023
|
fs.mkdirSync(dstDir, { recursive: true });
|
|
1004
|
-
|
|
1005
|
-
try { require("child_process").execSync("chown -R " + uid + " " + JSON.stringify(dstDir)); } catch (e2) {}
|
|
1006
|
-
}
|
|
1024
|
+
try { require("child_process").execSync("chown -R " + uid + " " + JSON.stringify(path.join(linuxUserHome, ".claude"))); } catch (e2) {}
|
|
1007
1025
|
} else {
|
|
1008
|
-
// Fix ownership of existing directories created by root
|
|
1009
1026
|
try {
|
|
1010
1027
|
var dirStat = fs.statSync(dstDir);
|
|
1011
|
-
if (
|
|
1028
|
+
if (dirStat.uid !== uid) {
|
|
1012
1029
|
require("child_process").execSync("chown " + uid + " " + JSON.stringify(dstDir));
|
|
1013
1030
|
}
|
|
1014
1031
|
} catch (e2) {}
|
|
1015
1032
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1033
|
+
// Pre-copy CLI session file so the worker can resume the conversation
|
|
1034
|
+
if (session.cliSessionId) {
|
|
1035
|
+
var sessionFileName = session.cliSessionId + ".jsonl";
|
|
1036
|
+
var srcFile = path.join(originalHome, ".claude", "projects", projectSlug, sessionFileName);
|
|
1037
|
+
var dstFile = path.join(dstDir, sessionFileName);
|
|
1038
|
+
if (fs.existsSync(srcFile) && !fs.existsSync(dstFile)) {
|
|
1039
|
+
fs.copyFileSync(srcFile, dstFile);
|
|
1019
1040
|
try { require("child_process").execSync("chown " + uid + " " + JSON.stringify(dstFile)); } catch (e2) {}
|
|
1041
|
+
console.log("[sdk-bridge] Pre-copied CLI session " + session.cliSessionId + " to " + linuxUser);
|
|
1020
1042
|
}
|
|
1021
|
-
console.log("[sdk-bridge] Pre-copied CLI session " + session.cliSessionId + " to " + linuxUser);
|
|
1022
1043
|
}
|
|
1023
1044
|
}
|
|
1024
1045
|
} catch (copyErr) {
|
|
1025
|
-
console.log("[sdk-bridge]
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
// Reuse existing worker if alive, otherwise spawn a new one.
|
|
1030
|
-
// Keeping the worker alive between queries lets the SDK maintain session
|
|
1031
|
-
// state in memory, avoiding disk-based resume that fails on abort.
|
|
1032
|
-
var worker;
|
|
1033
|
-
var reusingWorker = false;
|
|
1034
|
-
if (session.worker && session.worker.ready && session.worker.process && !session.worker.process.killed) {
|
|
1035
|
-
worker = session.worker;
|
|
1036
|
-
reusingWorker = true;
|
|
1037
|
-
// Clear old message handlers so they don't fire for the new query
|
|
1038
|
-
worker.messageHandlers = [];
|
|
1039
|
-
worker._queryEnded = false;
|
|
1040
|
-
worker._abortSent = false;
|
|
1041
|
-
console.log("[sdk-bridge] Reusing existing worker pid=" + (worker.process ? worker.process.pid : "?"));
|
|
1042
|
-
} else {
|
|
1043
|
-
try {
|
|
1044
|
-
worker = spawnWorker(linuxUser);
|
|
1045
|
-
session.worker = worker;
|
|
1046
|
-
} catch (e) {
|
|
1047
|
-
session.isProcessing = false;
|
|
1048
|
-
onProcessingChanged();
|
|
1049
|
-
sendAndRecord(session, { type: "error", text: "Failed to spawn worker for " + linuxUser + ": " + (e.message || e) });
|
|
1050
|
-
sendAndRecord(session, { type: "done", code: 1 });
|
|
1051
|
-
sm.broadcastSessionList();
|
|
1052
|
-
return;
|
|
1046
|
+
console.log("[sdk-bridge] Dir setup / session pre-copy skipped:", copyErr.message);
|
|
1053
1047
|
}
|
|
1054
1048
|
}
|
|
1049
|
+
perf("dir setup done");
|
|
1055
1050
|
|
|
1056
1051
|
session.messageQueue = "worker"; // sentinel: messages go via worker IPC
|
|
1057
1052
|
session.blocks = {};
|
|
@@ -1119,7 +1114,12 @@ function createSDKBridge(opts) {
|
|
|
1119
1114
|
}
|
|
1120
1115
|
|
|
1121
1116
|
// Set up message handler for worker events
|
|
1117
|
+
var firstEventLogged = false;
|
|
1122
1118
|
worker.onMessage(function(msg) {
|
|
1119
|
+
if (!firstEventLogged && msg.type === "sdk_event") {
|
|
1120
|
+
firstEventLogged = true;
|
|
1121
|
+
perf("FIRST sdk_event received (type=" + (msg.event && msg.event.type || "?") + ")");
|
|
1122
|
+
}
|
|
1123
1123
|
switch (msg.type) {
|
|
1124
1124
|
case "sdk_event":
|
|
1125
1125
|
processSDKMessage(session, msg.event);
|
|
@@ -1312,8 +1312,10 @@ function createSDKBridge(opts) {
|
|
|
1312
1312
|
|
|
1313
1313
|
// Wait for worker to be ready, then send query
|
|
1314
1314
|
if (!reusingWorker) {
|
|
1315
|
+
perf("awaiting readyPromise");
|
|
1315
1316
|
try {
|
|
1316
1317
|
await worker.readyPromise;
|
|
1318
|
+
perf("readyPromise resolved");
|
|
1317
1319
|
} catch (e) {
|
|
1318
1320
|
session.isProcessing = false;
|
|
1319
1321
|
onProcessingChanged();
|
|
@@ -1325,6 +1327,7 @@ function createSDKBridge(opts) {
|
|
|
1325
1327
|
}
|
|
1326
1328
|
}
|
|
1327
1329
|
|
|
1330
|
+
perf("sending query_start to worker");
|
|
1328
1331
|
worker.send({
|
|
1329
1332
|
type: "query_start",
|
|
1330
1333
|
prompt: initialMessage,
|
|
@@ -1332,7 +1335,9 @@ function createSDKBridge(opts) {
|
|
|
1332
1335
|
singleTurn: !!session.singleTurn,
|
|
1333
1336
|
originalHome: require("./config").REAL_HOME || null,
|
|
1334
1337
|
projectPath: session.cwd || null,
|
|
1338
|
+
_perfT0: t0,
|
|
1335
1339
|
});
|
|
1340
|
+
perf("query_start sent");
|
|
1336
1341
|
}
|
|
1337
1342
|
|
|
1338
1343
|
function cleanupSessionWorker(session, fromWorker) {
|
package/lib/sdk-worker.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
// Usage: node sdk-worker.js <socket-path>
|
|
6
6
|
|
|
7
7
|
// Early diagnostic — writes directly to fd 2 to ensure output even if pipes close fast
|
|
8
|
-
|
|
8
|
+
var _workerBootTs = Date.now();
|
|
9
|
+
try { require("fs").writeSync(2, "[sdk-worker] BOOT pid=" + process.pid + " uid=" + (typeof process.getuid === "function" ? process.getuid() : "?") + " argv=" + process.argv.slice(1).join(" ") + " bootTs=" + _workerBootTs + "\n"); } catch (e) {}
|
|
9
10
|
|
|
10
11
|
var net = require("net");
|
|
11
12
|
var crypto = require("crypto");
|
|
@@ -204,9 +205,16 @@ function handleElicitationResponse(msg) {
|
|
|
204
205
|
|
|
205
206
|
// --- Query handling ---
|
|
206
207
|
async function handleQueryStart(msg) {
|
|
208
|
+
var t0 = msg._perfT0 || Date.now();
|
|
209
|
+
var localT0 = Date.now();
|
|
210
|
+
function perf(label) { console.log("[PERF] sdk-worker: " + label + " +" + (Date.now() - t0) + "ms (local +" + (Date.now() - localT0) + "ms)"); }
|
|
211
|
+
perf("handleQueryStart entered");
|
|
212
|
+
|
|
207
213
|
var sdk;
|
|
208
214
|
try {
|
|
215
|
+
perf("loading SDK");
|
|
209
216
|
sdk = await getSDK();
|
|
217
|
+
perf("SDK loaded");
|
|
210
218
|
} catch (e) {
|
|
211
219
|
sendToDaemon({ type: "query_error", error: "Failed to load SDK: " + (e.message || e), exitCode: null, stderr: null });
|
|
212
220
|
return;
|
|
@@ -223,6 +231,17 @@ async function handleQueryStart(msg) {
|
|
|
223
231
|
// Build query options (callbacks are local, everything else from daemon)
|
|
224
232
|
var options = msg.options || {};
|
|
225
233
|
options.abortController = abortController;
|
|
234
|
+
// Override CLI subprocess spawn to inject NODE_OPTIONS for IPv4-first DNS.
|
|
235
|
+
// The SDK constructs its own env for the CLI process, so worker env vars
|
|
236
|
+
// like NODE_OPTIONS are not inherited. We intercept the spawn to fix this.
|
|
237
|
+
options.spawnClaudeCodeProcess = function(spawnOpts) {
|
|
238
|
+
spawnOpts.env.NODE_OPTIONS = (spawnOpts.env.NODE_OPTIONS || "") + " --dns-result-order=ipv4first";
|
|
239
|
+
return require("child_process").spawn(spawnOpts.command, spawnOpts.args, {
|
|
240
|
+
cwd: spawnOpts.cwd,
|
|
241
|
+
env: spawnOpts.env,
|
|
242
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
243
|
+
});
|
|
244
|
+
};
|
|
226
245
|
options.canUseTool = function(toolName, input, toolOpts) {
|
|
227
246
|
// AskUserQuestion is handled specially: we send it as a separate IPC type
|
|
228
247
|
// so the daemon can use its own AskUserQuestion handling logic
|
|
@@ -249,11 +268,13 @@ async function handleQueryStart(msg) {
|
|
|
249
268
|
return onElicitation(request, elicitOpts);
|
|
250
269
|
};
|
|
251
270
|
|
|
271
|
+
perf("creating query instance");
|
|
252
272
|
try {
|
|
253
273
|
queryInstance = sdk.query({
|
|
254
274
|
prompt: messageQueue,
|
|
255
275
|
options: options,
|
|
256
276
|
});
|
|
277
|
+
perf("query instance created");
|
|
257
278
|
} catch (e) {
|
|
258
279
|
sendToDaemon({ type: "query_error", error: "Failed to create query: " + (e.message || e), exitCode: null, stderr: null });
|
|
259
280
|
queryInstance = null;
|
|
@@ -269,9 +290,41 @@ async function handleQueryStart(msg) {
|
|
|
269
290
|
|
|
270
291
|
// Stream events to daemon
|
|
271
292
|
try {
|
|
293
|
+
var firstEvent = true;
|
|
294
|
+
var firstText = true;
|
|
295
|
+
var eventCounts = {};
|
|
272
296
|
for await (var event of queryInstance) {
|
|
297
|
+
var etype = (event && event.type || "?");
|
|
298
|
+
var esubtype = (event && event.subtype || "");
|
|
299
|
+
eventCounts[etype] = (eventCounts[etype] || 0) + 1;
|
|
300
|
+
if (firstEvent) {
|
|
301
|
+
perf("FIRST event from SDK (type=" + etype + " subtype=" + esubtype + ")");
|
|
302
|
+
firstEvent = false;
|
|
303
|
+
}
|
|
304
|
+
// Log every non-content event, and the first content/text event
|
|
305
|
+
if (etype !== "content_block_delta" && etype !== "content_block_start" && etype !== "content_block_stop") {
|
|
306
|
+
var extraInfo = "";
|
|
307
|
+
if (esubtype === "api_retry") {
|
|
308
|
+
// Dump full event to see all available fields
|
|
309
|
+
try {
|
|
310
|
+
var retryDump = JSON.stringify(event, function(k, v) {
|
|
311
|
+
if (typeof v === "string" && v.length > 200) return v.substring(0, 200) + "...[truncated]";
|
|
312
|
+
return v;
|
|
313
|
+
});
|
|
314
|
+
extraInfo = " FULL=" + retryDump;
|
|
315
|
+
} catch (je) {
|
|
316
|
+
extraInfo = " keys=" + Object.keys(event).join(",");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
perf("SDK event #" + eventCounts[etype] + " type=" + etype + " subtype=" + esubtype + extraInfo);
|
|
320
|
+
}
|
|
321
|
+
if (firstText && (etype === "content_block_delta" || etype === "assistant" || (etype === "content_block_start"))) {
|
|
322
|
+
perf("FIRST TEXT/CONTENT event (type=" + etype + " subtype=" + esubtype + ")");
|
|
323
|
+
firstText = false;
|
|
324
|
+
}
|
|
273
325
|
sendToDaemon({ type: "sdk_event", event: event });
|
|
274
326
|
}
|
|
327
|
+
perf("all events streamed (counts=" + JSON.stringify(eventCounts) + "), sending query_done");
|
|
275
328
|
sendToDaemon({ type: "query_done" });
|
|
276
329
|
} catch (err) {
|
|
277
330
|
var errMsg = err.message || String(err);
|
|
@@ -424,9 +477,9 @@ function gracefulExit(code) {
|
|
|
424
477
|
var _keepAlive = setInterval(function() {}, 30000);
|
|
425
478
|
|
|
426
479
|
// --- Connect to daemon socket ---
|
|
427
|
-
try { require("fs").writeSync(2, "[sdk-worker] Connecting to socket: " + socketPath + "\n"); } catch (e) {}
|
|
480
|
+
try { require("fs").writeSync(2, "[sdk-worker] Connecting to socket: " + socketPath + " +" + (Date.now() - _workerBootTs) + "ms since boot\n"); } catch (e) {}
|
|
428
481
|
conn = net.connect(socketPath, function() {
|
|
429
|
-
try { require("fs").writeSync(2, "[sdk-worker] Connected, sending ready\n"); } catch (e) {}
|
|
482
|
+
try { require("fs").writeSync(2, "[sdk-worker] Connected, sending ready +" + (Date.now() - _workerBootTs) + "ms since boot\n"); } catch (e) {}
|
|
430
483
|
sendToDaemon({ type: "ready" });
|
|
431
484
|
});
|
|
432
485
|
|