| 173 | } |
| 174 | |
| 175 | export function flushDiagnosticsToSessionFile(options: { force?: boolean } = {}): string | null { |
| 176 | const scope = diagnosticsStorage.getStore(); |
| 177 | if (!scope) return null; |
| 178 | if (!options.force && !scope.debug && !scope.flushOnSuccess) return null; |
| 179 | if (scope.events.length === 0) return null; |
| 180 | |
| 181 | try { |
| 182 | if (scope.logPath) { |
| 183 | const pendingEvents = scope.events.slice(scope.liveWrittenEventCount); |
| 184 | if (pendingEvents.length > 0) { |
| 185 | const lines = pendingEvents.map((entry) => JSON.stringify(redactDiagnosticData(entry))); |
| 186 | appendDiagnosticLine(scope.logPath, `${lines.join('\n')}\n`); |
| 187 | } |
| 188 | const logPath = scope.logPath; |
| 189 | scope.events = []; |
| 190 | scope.liveWrittenEventCount = 0; |
| 191 | return logPath; |
| 192 | } |
| 193 | |
| 194 | const sessionDir = sanitizePathPart(scope.session ?? 'default'); |
| 195 | const dayDir = new Date().toISOString().slice(0, 10); |
| 196 | const baseDir = path.join(os.homedir(), '.agent-device', 'logs', sessionDir, dayDir); |
| 197 | fs.mkdirSync(baseDir, { recursive: true }); |
| 198 | const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); |
| 199 | const filePath = path.join(baseDir, `${timestamp}-${scope.diagnosticId}.ndjson`); |
| 200 | const lines = scope.events.map((entry) => JSON.stringify(redactDiagnosticData(entry))); |
| 201 | fs.writeFileSync(filePath, `${lines.join('\n')}\n`); |
| 202 | scope.events = []; |
| 203 | return filePath; |
| 204 | } catch { |
| 205 | return null; |
| 206 | } |
| 207 | } |
| 208 | |
| 209 | function sanitizePathPart(value: string): string { |
| 210 | return value.replace(/[^a-zA-Z0-9._-]/g, '_'); |