chore: improve storage/ implementation; use log(...) consistently (#473)
This PR tidies up primitives under storage/. **Noop changes:** * Promote logger implementation to top-level utility outside of agent/ * Use logger within storage primitives * Cleanup doc strings and comments **Functional changes:** * Increase command history size to 10_000 * Remove unnecessary debounce implementation and ensure a session ID is created only once per agent loop --------- Signed-off-by: Thibault Sottiaux <tibo@openai.com>
This commit is contained in:
committed by
GitHub
parent
8f1ea7fa85
commit
dc276999a9
@@ -1,12 +1,13 @@
|
||||
import { log } from "../logger/log.js";
|
||||
import { existsSync } from "fs";
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
const HISTORY_FILE = path.join(os.homedir(), ".codex", "history.json");
|
||||
const DEFAULT_HISTORY_SIZE = 1000;
|
||||
const DEFAULT_HISTORY_SIZE = 10_000;
|
||||
|
||||
// Regex patterns for sensitive commands that should not be saved
|
||||
// Regex patterns for sensitive commands that should not be saved.
|
||||
const SENSITIVE_PATTERNS = [
|
||||
/\b[A-Za-z0-9-_]{20,}\b/, // API keys and tokens
|
||||
/\bpassword\b/i,
|
||||
@@ -18,7 +19,7 @@ const SENSITIVE_PATTERNS = [
|
||||
export interface HistoryConfig {
|
||||
maxSize: number;
|
||||
saveHistory: boolean;
|
||||
sensitivePatterns: Array<string>; // Array of regex patterns as strings
|
||||
sensitivePatterns: Array<string>; // Regex patterns.
|
||||
}
|
||||
|
||||
export interface HistoryEntry {
|
||||
@@ -32,9 +33,6 @@ export const DEFAULT_HISTORY_CONFIG: HistoryConfig = {
|
||||
sensitivePatterns: [],
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads command history from the history file
|
||||
*/
|
||||
export async function loadCommandHistory(): Promise<Array<HistoryEntry>> {
|
||||
try {
|
||||
if (!existsSync(HISTORY_FILE)) {
|
||||
@@ -45,26 +43,21 @@ export async function loadCommandHistory(): Promise<Array<HistoryEntry>> {
|
||||
const history = JSON.parse(data) as Array<HistoryEntry>;
|
||||
return Array.isArray(history) ? history : [];
|
||||
} catch (error) {
|
||||
// Use error logger but for production would use a proper logging system
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to load command history:", error);
|
||||
log(`error: failed to load command history: ${error}`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves command history to the history file
|
||||
*/
|
||||
export async function saveCommandHistory(
|
||||
history: Array<HistoryEntry>,
|
||||
config: HistoryConfig = DEFAULT_HISTORY_CONFIG,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Create directory if it doesn't exist
|
||||
// Create directory if it doesn't exist.
|
||||
const dir = path.dirname(HISTORY_FILE);
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
|
||||
// Trim history to max size
|
||||
// Trim history to max size.
|
||||
const trimmedHistory = history.slice(-config.maxSize);
|
||||
|
||||
await fs.writeFile(
|
||||
@@ -73,14 +66,10 @@ export async function saveCommandHistory(
|
||||
"utf-8",
|
||||
);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save command history:", error);
|
||||
log(`error: failed to save command history: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a command to history if it's not sensitive
|
||||
*/
|
||||
export async function addToHistory(
|
||||
command: string,
|
||||
history: Array<HistoryEntry>,
|
||||
@@ -90,46 +79,41 @@ export async function addToHistory(
|
||||
return history;
|
||||
}
|
||||
|
||||
// Check if command contains sensitive information
|
||||
if (isSensitiveCommand(command, config.sensitivePatterns)) {
|
||||
// Skip commands with sensitive information.
|
||||
if (commandHasSensitiveInfo(command, config.sensitivePatterns)) {
|
||||
return history;
|
||||
}
|
||||
|
||||
// Check for duplicate (don't add if it's the same as the last command)
|
||||
// Check for duplicate (don't add if it's the same as the last command).
|
||||
const lastEntry = history[history.length - 1];
|
||||
if (lastEntry && lastEntry.command === command) {
|
||||
return history;
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
const newEntry: HistoryEntry = {
|
||||
command,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
const newHistory = [...history, newEntry];
|
||||
|
||||
// Save to file
|
||||
// Add new entry.
|
||||
const newHistory: Array<HistoryEntry> = [
|
||||
...history,
|
||||
{
|
||||
command,
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
];
|
||||
await saveCommandHistory(newHistory, config);
|
||||
|
||||
return newHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a command contains sensitive information
|
||||
*/
|
||||
function isSensitiveCommand(
|
||||
function commandHasSensitiveInfo(
|
||||
command: string,
|
||||
additionalPatterns: Array<string> = [],
|
||||
): boolean {
|
||||
// Check built-in patterns
|
||||
// Check built-in patterns.
|
||||
for (const pattern of SENSITIVE_PATTERNS) {
|
||||
if (pattern.test(command)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check additional patterns from config
|
||||
// Check additional patterns from config.
|
||||
for (const patternStr of additionalPatterns) {
|
||||
try {
|
||||
const pattern = new RegExp(patternStr);
|
||||
@@ -137,23 +121,19 @@ function isSensitiveCommand(
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
// Invalid regex pattern, skip it
|
||||
// Invalid regex pattern, skip it.
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the command history
|
||||
*/
|
||||
export async function clearCommandHistory(): Promise<void> {
|
||||
try {
|
||||
if (existsSync(HISTORY_FILE)) {
|
||||
await fs.writeFile(HISTORY_FILE, JSON.stringify([]), "utf-8");
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to clear command history:", error);
|
||||
log(`error: failed to clear command history: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import type { ResponseItem } from "openai/resources/responses/responses";
|
||||
|
||||
import { loadConfig } from "../config";
|
||||
import { log } from "../logger/log.js";
|
||||
import fs from "fs/promises";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
const SESSIONS_ROOT = path.join(os.homedir(), ".codex", "sessions");
|
||||
|
||||
async function saveRolloutToHomeSessions(
|
||||
async function saveRolloutAsync(
|
||||
sessionId: string,
|
||||
items: Array<ResponseItem>,
|
||||
): Promise<void> {
|
||||
await fs.mkdir(SESSIONS_ROOT, { recursive: true });
|
||||
|
||||
const sessionId = crypto.randomUUID();
|
||||
const timestamp = new Date().toISOString();
|
||||
const ts = timestamp.replace(/[:.]/g, "-").slice(0, 10);
|
||||
const filename = `rollout-${ts}-${sessionId}.json`;
|
||||
@@ -39,23 +38,15 @@ async function saveRolloutToHomeSessions(
|
||||
"utf8",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Failed to save rollout to ${filePath}: `, error);
|
||||
log(`error: failed to save rollout to ${filePath}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
let debounceTimer: NodeJS.Timeout | null = null;
|
||||
let pendingItems: Array<ResponseItem> | null = null;
|
||||
|
||||
export function saveRollout(items: Array<ResponseItem>): void {
|
||||
pendingItems = items;
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
debounceTimer = setTimeout(() => {
|
||||
if (pendingItems) {
|
||||
saveRolloutToHomeSessions(pendingItems).catch(() => {});
|
||||
pendingItems = null;
|
||||
}
|
||||
debounceTimer = null;
|
||||
}, 2000);
|
||||
export function saveRollout(
|
||||
sessionId: string,
|
||||
items: Array<ResponseItem>,
|
||||
): void {
|
||||
// Best-effort. We also do not log here in case of failure as that should be taken care of
|
||||
// by `saveRolloutAsync` already.
|
||||
saveRolloutAsync(sessionId, items).catch(() => {});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user