claude-remote 0.1.4 → 0.1.6
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/package.json +1 -1
- package/server.js +50 -8
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -121,6 +121,7 @@ let switchWatcher = null;
|
|
|
121
121
|
let expectingSwitch = false;
|
|
122
122
|
let expectingSwitchTimer = null;
|
|
123
123
|
let tailRemainder = Buffer.alloc(0);
|
|
124
|
+
let tailCatchingUp = false; // true while reading historical transcript content
|
|
124
125
|
const isTTY = process.stdin.isTTY && process.stdout.isTTY;
|
|
125
126
|
const LEGACY_REPLAY_DELAY_MS = 1500;
|
|
126
127
|
const IMAGE_UPLOAD_TTL_MS = 15 * 60 * 1000;
|
|
@@ -196,16 +197,37 @@ function maybeAttachHookSession(data, source) {
|
|
|
196
197
|
const target = resolveHookTranscript(data);
|
|
197
198
|
if (!target) return;
|
|
198
199
|
|
|
200
|
+
// Already attached to this exact session — no-op
|
|
199
201
|
if (currentSessionId === target.sessionId && transcriptPath &&
|
|
200
202
|
normalizeFsPath(transcriptPath) === normalizeFsPath(target.full)) {
|
|
201
203
|
return;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
// Switching to a different session — apply source-specific guards.
|
|
207
|
+
if (currentSessionId && currentSessionId !== target.sessionId && !expectingSwitch) {
|
|
208
|
+
const targetHasContent = fileLooksLikeTranscript(target.full);
|
|
209
|
+
|
|
210
|
+
if (source === 'session-start') {
|
|
211
|
+
// --resume triggers two session-start hooks in unpredictable order.
|
|
212
|
+
// Don't switch away from a transcript with conversation content to an
|
|
213
|
+
// empty one — the one with content is the real resumed session.
|
|
214
|
+
const currentHasContent = transcriptPath && fileLooksLikeTranscript(transcriptPath);
|
|
215
|
+
if (currentHasContent && !targetHasContent) {
|
|
216
|
+
log(`Ignored hook session from ${source}: ${target.sessionId} (current has content, target empty)`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
} else if (source === 'pre-tool-use') {
|
|
220
|
+
// pre-tool-use is the most reliable signal — it comes from the actually
|
|
221
|
+
// running Claude process. Accept it if the transcript has content.
|
|
222
|
+
if (!targetHasContent) {
|
|
223
|
+
log(`Ignored hook session from ${source}: ${target.sessionId} (no conversation content)`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
// Unknown source — block unexpected switches
|
|
228
|
+
log(`Ignored hook session from ${source}: ${target.sessionId} (current=${currentSessionId})`);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
209
231
|
}
|
|
210
232
|
|
|
211
233
|
log(`Hook session attached from ${source}: ${target.sessionId}`);
|
|
@@ -826,7 +848,16 @@ function attachTranscript(target, startOffset = 0) {
|
|
|
826
848
|
eventBuffer = [];
|
|
827
849
|
eventSeq = 0;
|
|
828
850
|
|
|
829
|
-
|
|
851
|
+
// If transcript file already has content, mark as catching up so we don't
|
|
852
|
+
// broadcast working_started for historical user messages.
|
|
853
|
+
try {
|
|
854
|
+
const stat = fs.statSync(transcriptPath);
|
|
855
|
+
tailCatchingUp = stat.size > transcriptOffset;
|
|
856
|
+
} catch {
|
|
857
|
+
tailCatchingUp = false;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
log(`Transcript attached: ${currentSessionId} (offset=${transcriptOffset} catchUp=${tailCatchingUp})`);
|
|
830
861
|
broadcast({
|
|
831
862
|
type: 'transcript_ready',
|
|
832
863
|
transcript: transcriptPath,
|
|
@@ -886,7 +917,14 @@ function startTailing() {
|
|
|
886
917
|
if (!transcriptPath) return;
|
|
887
918
|
try {
|
|
888
919
|
const stat = fs.statSync(transcriptPath);
|
|
889
|
-
if (stat.size <= transcriptOffset)
|
|
920
|
+
if (stat.size <= transcriptOffset) {
|
|
921
|
+
// Caught up to file end — initial catch-up phase is over
|
|
922
|
+
if (tailCatchingUp) {
|
|
923
|
+
tailCatchingUp = false;
|
|
924
|
+
log('Tail catch-up complete, live mode');
|
|
925
|
+
}
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
890
928
|
|
|
891
929
|
const fd = fs.openSync(transcriptPath, 'r');
|
|
892
930
|
const buf = Buffer.alloc(stat.size - transcriptOffset);
|
|
@@ -905,7 +943,11 @@ function startTailing() {
|
|
|
905
943
|
const event = JSON.parse(line);
|
|
906
944
|
// Detect /clear from JSONL events (covers terminal direct input)
|
|
907
945
|
if (event.type === 'user' || (event.message && event.message.role === 'user')) {
|
|
908
|
-
broadcast
|
|
946
|
+
// Only broadcast working_started for live (new) user messages,
|
|
947
|
+
// not for historical events during catch-up.
|
|
948
|
+
if (!tailCatchingUp) {
|
|
949
|
+
broadcast({ type: 'working_started' });
|
|
950
|
+
}
|
|
909
951
|
const content = event.message && event.message.content;
|
|
910
952
|
if (typeof content === 'string' && /^\/clear\s*$/i.test(content.trim())) {
|
|
911
953
|
markExpectingSwitch();
|