forkoff 1.1.3 → 1.1.5
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.js +8 -6
- package/dist/tools/claude-process.d.ts +3 -3
- package/dist/tools/claude-process.js +21 -9
- package/dist/websocket.js +6 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1098,7 +1098,7 @@ function createProgram() {
|
|
|
1098
1098
|
websocket_1.wsClient.on('disconnected', (reason) => {
|
|
1099
1099
|
console.log(chalk_1.default.yellow(`\nMobile disconnected: ${reason}`));
|
|
1100
1100
|
console.log(chalk_1.default.dim('Waiting for mobile to reconnect...'));
|
|
1101
|
-
tools_1.claudeProcessManager.
|
|
1101
|
+
tools_1.claudeProcessManager.resolveAllPendingPrompts('deny', 'mobile disconnected');
|
|
1102
1102
|
tools_1.claudeProcessManager.cleanupAllPermissionState();
|
|
1103
1103
|
tools_1.claudeProcessManager.clearAllTakenOver();
|
|
1104
1104
|
});
|
|
@@ -1136,15 +1136,17 @@ function createProgram() {
|
|
|
1136
1136
|
// Helper function to wait for pairing via WebSocket event
|
|
1137
1137
|
async function waitForPairing() {
|
|
1138
1138
|
return new Promise((resolve) => {
|
|
1139
|
-
|
|
1140
|
-
resolve({ mobileDeviceId: data.mobileDeviceId });
|
|
1141
|
-
});
|
|
1142
|
-
// Handle Ctrl+C
|
|
1143
|
-
process.on('SIGINT', () => {
|
|
1139
|
+
const sigintHandler = () => {
|
|
1144
1140
|
console.log(chalk_1.default.yellow('\nPairing cancelled.'));
|
|
1145
1141
|
websocket_1.wsClient.disconnect();
|
|
1146
1142
|
process.exit(0);
|
|
1143
|
+
};
|
|
1144
|
+
websocket_1.wsClient.once('pair_device', (data) => {
|
|
1145
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
1146
|
+
resolve({ mobileDeviceId: data.mobileDeviceId });
|
|
1147
1147
|
});
|
|
1148
|
+
// Handle Ctrl+C
|
|
1149
|
+
process.on('SIGINT', sigintHandler);
|
|
1148
1150
|
});
|
|
1149
1151
|
}
|
|
1150
1152
|
// Logs command — list and manage debug log files
|
|
@@ -252,10 +252,10 @@ declare class ClaudeProcessManager extends EventEmitter {
|
|
|
252
252
|
*/
|
|
253
253
|
getAllPendingPrompts(): PermissionPromptEvent[];
|
|
254
254
|
/**
|
|
255
|
-
*
|
|
256
|
-
* Called when mobile disconnects
|
|
255
|
+
* Resolve all pending permission prompts with a given decision.
|
|
256
|
+
* Called when mobile disconnects — defaults to 'deny' since the user can't verify.
|
|
257
257
|
*/
|
|
258
|
-
|
|
258
|
+
resolveAllPendingPrompts(decision: 'allow' | 'deny', reason: string): void;
|
|
259
259
|
/**
|
|
260
260
|
* Tear down all permission hooks and IPC managers.
|
|
261
261
|
* Called when mobile disconnects — hooks get re-configured on next Take Over + message.
|
|
@@ -318,10 +318,23 @@ class ClaudeProcessManager extends events_1.EventEmitter {
|
|
|
318
318
|
// If there's an existing process, kill it first (Claude SDK only supports 1 turn per process)
|
|
319
319
|
if (info?.process && info.process.exitCode === null) {
|
|
320
320
|
console.log(`[Claude Process] Killing existing process for new message (SDK limitation: 1 turn per process)`);
|
|
321
|
-
info.process
|
|
322
|
-
// Wait for process to die
|
|
323
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
321
|
+
const oldProc = info.process;
|
|
324
322
|
this.processes.delete(terminalSessionId);
|
|
323
|
+
oldProc.kill('SIGTERM');
|
|
324
|
+
// Wait for process to exit: SIGTERM first, escalate to SIGKILL after 1.5s
|
|
325
|
+
await new Promise(resolve => {
|
|
326
|
+
const escalate = setTimeout(() => { try {
|
|
327
|
+
oldProc.kill('SIGKILL');
|
|
328
|
+
}
|
|
329
|
+
catch { } }, 1500);
|
|
330
|
+
const timeout = setTimeout(() => { clearTimeout(escalate); resolve(); }, 3000);
|
|
331
|
+
oldProc.once('close', () => { clearTimeout(timeout); clearTimeout(escalate); resolve(); });
|
|
332
|
+
if (oldProc.exitCode !== null) {
|
|
333
|
+
clearTimeout(timeout);
|
|
334
|
+
clearTimeout(escalate);
|
|
335
|
+
resolve();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
325
338
|
info = undefined;
|
|
326
339
|
}
|
|
327
340
|
// Get session info from either current process or closed sessions
|
|
@@ -878,19 +891,18 @@ class ClaudeProcessManager extends events_1.EventEmitter {
|
|
|
878
891
|
return allPrompts;
|
|
879
892
|
}
|
|
880
893
|
/**
|
|
881
|
-
*
|
|
882
|
-
* Called when mobile disconnects
|
|
894
|
+
* Resolve all pending permission prompts with a given decision.
|
|
895
|
+
* Called when mobile disconnects — defaults to 'deny' since the user can't verify.
|
|
883
896
|
*/
|
|
884
|
-
|
|
897
|
+
resolveAllPendingPrompts(decision, reason) {
|
|
885
898
|
const pending = this.getAllPendingPrompts();
|
|
886
899
|
for (const prompt of pending) {
|
|
887
|
-
// Find the IPC manager that owns this prompt and respond
|
|
888
900
|
for (const [, ipcManager] of this.permissionIpcManagers) {
|
|
889
|
-
ipcManager.handleResponse(prompt.promptId,
|
|
901
|
+
ipcManager.handleResponse(prompt.promptId, decision, reason);
|
|
890
902
|
}
|
|
891
903
|
}
|
|
892
904
|
if (pending.length > 0) {
|
|
893
|
-
console.log(`[Claude Process] Auto
|
|
905
|
+
console.log(`[Claude Process] Auto-${decision === 'allow' ? 'allowed' : 'denied'} ${pending.length} pending permission prompt(s): ${reason}`);
|
|
894
906
|
}
|
|
895
907
|
}
|
|
896
908
|
/**
|
package/dist/websocket.js
CHANGED
|
@@ -32,6 +32,7 @@ const ALLOWED_ENCRYPTED_EVENTS = new Set([
|
|
|
32
32
|
'sdk_session_history',
|
|
33
33
|
'claude_abort',
|
|
34
34
|
'usage_stats_request',
|
|
35
|
+
'session_release',
|
|
35
36
|
]);
|
|
36
37
|
// Events that carry user data and MUST be encrypted — plaintext fallback is refused.
|
|
37
38
|
// If E2EE is not established, these are queued (not sent in plaintext).
|
|
@@ -156,10 +157,11 @@ class WebSocketClient extends events_1.EventEmitter {
|
|
|
156
157
|
wireUpTransportEvents() {
|
|
157
158
|
if (!this.server)
|
|
158
159
|
return;
|
|
159
|
-
// When mobile connects, emit connected + start heartbeat + initiate E2EE
|
|
160
|
+
// When mobile connects, emit connected + send immediate status + start heartbeat + initiate E2EE
|
|
160
161
|
this.server.on('mobile_connected', (data) => {
|
|
161
162
|
console.log(`[WS] Mobile connected: ${data.deviceId?.substring(0, 8)}...`);
|
|
162
163
|
this.emit('connected');
|
|
164
|
+
this.sendHeartbeat(); // Immediate status update — don't wait for first 30s interval
|
|
163
165
|
this.startHeartbeat();
|
|
164
166
|
// SECURITY: Clear stale E2EE peer state on every mobile connect.
|
|
165
167
|
// Mobile sessions are in-memory only — lost on app restart/reconnect.
|
|
@@ -474,10 +476,9 @@ class WebSocketClient extends events_1.EventEmitter {
|
|
|
474
476
|
}
|
|
475
477
|
/** SECURITY: Check if plaintext inbound events should be dropped (E2EE active with peer) */
|
|
476
478
|
shouldDropPlaintextInbound() {
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// In embedded relay mode, mobile connects directly — drop plaintext when E2EE active
|
|
479
|
+
// Drop plaintext events when an E2EE session is established with the peer —
|
|
480
|
+
// legitimate events should arrive as encrypted_message, not plaintext.
|
|
481
|
+
// Applies to both cloud relay and local modes.
|
|
481
482
|
return !!(this.e2eePeerDeviceId && this.e2eeManager?.hasSessionKey(this.e2eePeerDeviceId));
|
|
482
483
|
}
|
|
483
484
|
disconnect() {
|
package/package.json
CHANGED