Skip to content

Commit 3082855

Browse files
author
Tommy Tong
committed
feat: optimize macos
1 parent f34ea5f commit 3082855

File tree

3 files changed

+96
-34
lines changed

3 files changed

+96
-34
lines changed

src/cmd-input.ts

Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,6 @@ import { USER_INPUT_TIMEOUT_SECONDS } from './constants.js'; // Import the const
1010
// Get the directory name of the current module
1111
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1212

13-
/**
14-
* Generate a unique temporary file path
15-
* @returns Path to a temporary file
16-
*/
17-
async function getTempFilePath(): Promise<string> {
18-
const tempDir = os.tmpdir();
19-
const randomId = crypto.randomBytes(8).toString('hex');
20-
const tempFile = path.join(tempDir, `cmd-ui-response-${randomId}.txt`);
21-
return tempFile;
22-
}
23-
2413
/**
2514
* Display a command window with a prompt and return user input
2615
* @param projectName Name of the project requesting input (used for title)
@@ -38,7 +27,10 @@ export async function getCmdWindowInput(
3827
predefinedOptions?: string[],
3928
): Promise<string> {
4029
// Create a temporary file for the detached process to write to
41-
const tempFilePath = await getTempFilePath();
30+
const sessionId = crypto.randomBytes(8).toString('hex');
31+
const tempDir = os.tmpdir();
32+
const tempFilePath = path.join(tempDir, `cmd-ui-response-${sessionId}.txt`);
33+
const heartbeatFilePath = path.join(tempDir, `cmd-ui-heartbeat-${sessionId}.txt`);
4234
let processExited = false;
4335

4436
return new Promise<string>(async (resolve) => {
@@ -51,6 +43,7 @@ export async function getCmdWindowInput(
5143
prompt: promptMessage,
5244
timeout: timeoutSeconds,
5345
showCountdown,
46+
sessionId,
5447
outputFile: tempFilePath,
5548
predefinedOptions,
5649
};
@@ -127,6 +120,36 @@ export async function getCmdWindowInput(
127120
// Create an empty temp file before watching for user response
128121
await fs.writeFile(tempFilePath, '', 'utf8');
129122

123+
// Wait briefly for the heartbeat file to potentially be created
124+
await new Promise(res => setTimeout(res, 500));
125+
126+
let lastHeartbeatTime = Date.now();
127+
let heartbeatInterval: NodeJS.Timeout | null = null;
128+
let heartbeatFileSeen = false; // Track if we've ever seen the heartbeat file
129+
const startTime = Date.now(); // Record start time for initial grace period
130+
131+
// Helper function for cleanup and resolution
132+
const cleanupAndResolve = async (response: string) => {
133+
if (heartbeatInterval) {
134+
clearInterval(heartbeatInterval);
135+
heartbeatInterval = null;
136+
}
137+
if (watcher) {
138+
watcher.close(); // Ensure watcher is closed
139+
}
140+
if (timeoutHandle) {
141+
clearTimeout(timeoutHandle); // Ensure timeout is cleared
142+
}
143+
144+
// Use Promise.allSettled to attempt cleanup without failing if one file is missing
145+
await Promise.allSettled([
146+
fs.unlink(tempFilePath).catch(() => {}), // Ignore errors
147+
fs.unlink(heartbeatFilePath).catch(() => {}) // Ignore errors
148+
]);
149+
150+
resolve(response);
151+
};
152+
130153
// Watch for content being written to the temp file
131154
const watcher = watch(tempFilePath, async (eventType: string) => {
132155
if (eventType === 'change') {
@@ -136,33 +159,51 @@ export async function getCmdWindowInput(
136159
const response = data.trim();
137160
watcher.close();
138161
clearTimeout(timeoutHandle);
162+
if (heartbeatInterval) clearInterval(heartbeatInterval);
139163
cleanupAndResolve(response);
140164
}
141165
}
142166
});
143167

144-
// Timeout to stop watching if no response within limit
145-
const timeoutHandle = setTimeout(
146-
() => {
147-
watcher.close();
148-
cleanupAndResolve('');
149-
},
150-
timeoutSeconds * 1000 + 5000,
151-
);
152-
153-
// Helper function for cleanup and resolution
154-
const cleanupAndResolve = async (response: string) => {
155-
// Clean up the temporary file if it exists
168+
// Start heartbeat check interval
169+
heartbeatInterval = setInterval(async () => {
156170
try {
157-
await fs.unlink(tempFilePath);
171+
const stats = await fs.stat(heartbeatFilePath);
172+
const now = Date.now();
173+
// If file hasn't been modified in the last 3 seconds, assume dead
174+
if (now - stats.mtime.getTime() > 3000) {
175+
// console.log('Heartbeat expired.');
176+
cleanupAndResolve('');
177+
} else {
178+
lastHeartbeatTime = now; // Update last known good time
179+
heartbeatFileSeen = true; // Mark that we've seen the file
180+
}
158181
} catch (err: any) {
159-
// Ignore if file is already removed, otherwise log unexpected errors
160-
if (err.code !== 'ENOENT') {
161-
// console.error('Error deleting temp file:', err);
182+
if (err.code === 'ENOENT') {
183+
// File not found
184+
if (heartbeatFileSeen) {
185+
// File existed before but is now gone, assume dead
186+
// console.log('Heartbeat file disappeared.');
187+
cleanupAndResolve('');
188+
} else if (Date.now() - startTime > 7000) {
189+
// File never appeared and initial grace period (7s) passed, assume dead
190+
// console.log('Heartbeat file never appeared.');
191+
cleanupAndResolve('');
192+
}
193+
// Otherwise, file just hasn't appeared yet, wait longer
194+
} else if (err.code !== 'ENOENT') {
195+
// Log other errors, but potentially continue?
196+
// console.error('Heartbeat check error:', err);
197+
// Maybe stop checking if there's a persistent error other than ENOENT?
198+
// cleanupAndResolve(''); // Or resolve immediately on other errors?
162199
}
163200
}
201+
}, 1500); // Check every 1.5 seconds
164202

165-
resolve(response);
166-
};
203+
// Timeout to stop watching if no response within limit
204+
const timeoutHandle = setTimeout(() => {
205+
// console.log('Overall timeout reached.');
206+
cleanupAndResolve('');
207+
}, timeoutSeconds * 1000 + 5000); // Add a bit more buffer
167208
});
168209
}

src/cmd-intensive-chat.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ export async function askQuestionInSession(
210210
}
211211

212212
// Timeout reached
213-
return null;
213+
return "User closed intensive chat session";
214214
}
215215

216216
/**

src/cmd-single-input-ui.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { render, Box, Text, useApp } from 'ink';
33
import { ProgressBar, TextInput } from '@inkjs/ui';
44
import { useInput } from 'ink';
55
import fs from 'fs/promises';
6+
import path from 'path'; // Import path module
67

78
// Parse command line arguments from a single JSON-encoded argument for safety
89
const parseArgs = () => {
910
const args = process.argv.slice(2);
10-
const defaults = { prompt: "Enter your response:", timeout: 30, showCountdown: false as boolean, outputFile: undefined as string | undefined, predefinedOptions: undefined as string[] | undefined };
11+
const defaults = { prompt: "Enter your response:", timeout: 30, showCountdown: false as boolean, sessionId: undefined as string | undefined, outputFile: undefined as string | undefined, predefinedOptions: undefined as string[] | undefined };
1112
if (args[0]) {
1213
try {
1314
// Decode base64-encoded JSON payload to avoid quoting issues
@@ -186,6 +187,9 @@ const App: FC<AppProps> = ({ projectName, prompt, timeout, showCountdown, output
186187
console.clear(); // Clear console before rendering UI
187188
const { exit } = useApp();
188189
const [timeLeft, setTimeLeft] = useState(timeout);
190+
const heartbeatFilePath = (outputFile && options.sessionId)
191+
? path.join(path.dirname(outputFile), `cmd-ui-heartbeat-${options.sessionId}.txt`)
192+
: undefined;
189193

190194
// Handle countdown and auto-exit on timeout
191195
useEffect(() => {
@@ -203,8 +207,25 @@ const App: FC<AppProps> = ({ projectName, prompt, timeout, showCountdown, output
203207
});
204208
}, 1000);
205209

206-
return () => clearInterval(timer);
207-
}, [exit]);
210+
// Add heartbeat interval
211+
let heartbeatInterval: NodeJS.Timeout | undefined;
212+
if (heartbeatFilePath) {
213+
heartbeatInterval = setInterval(async () => {
214+
try {
215+
await fs.writeFile(heartbeatFilePath, '', 'utf8'); // Update heartbeat file
216+
} catch (e) {
217+
// Ignore errors writing heartbeat file (e.g., if directory is removed)
218+
}
219+
}, 1000); // Update every second
220+
}
221+
222+
return () => {
223+
clearInterval(timer);
224+
if (heartbeatInterval) {
225+
clearInterval(heartbeatInterval); // Clear heartbeat interval on cleanup
226+
}
227+
};
228+
}, [exit, heartbeatFilePath]); // Add heartbeatFilePath to dependency array
208229

209230
// Handle final submission
210231
const handleSubmit = (value: string) => {

0 commit comments

Comments
 (0)