130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
/**
|
|
* Session logging for DroidClaw.
|
|
* Writes incremental .partial.json after each step (crash-safe),
|
|
* and a final .json summary at session end.
|
|
*/
|
|
|
|
import { mkdirSync, writeFileSync } from "fs";
|
|
import { join } from "path";
|
|
import type { ActionDecision } from "./actions.js";
|
|
|
|
export interface StepLog {
|
|
step: number;
|
|
timestamp: string;
|
|
foregroundApp: string | null;
|
|
elementCount: number;
|
|
screenChanged: boolean;
|
|
llmDecision: {
|
|
action: string;
|
|
reason?: string;
|
|
coordinates?: [number, number];
|
|
text?: string;
|
|
think?: string;
|
|
plan?: string[];
|
|
planProgress?: string;
|
|
};
|
|
actionResult: {
|
|
success: boolean;
|
|
message: string;
|
|
};
|
|
llmLatencyMs: number;
|
|
actionLatencyMs: number;
|
|
}
|
|
|
|
export interface SessionSummary {
|
|
sessionId: string;
|
|
goal: string;
|
|
provider: string;
|
|
model: string;
|
|
startTime: string;
|
|
endTime: string;
|
|
totalSteps: number;
|
|
successCount: number;
|
|
failCount: number;
|
|
completed: boolean;
|
|
steps: StepLog[];
|
|
}
|
|
|
|
export class SessionLogger {
|
|
private sessionId: string;
|
|
private logDir: string;
|
|
private steps: StepLog[] = [];
|
|
private goal: string;
|
|
private provider: string;
|
|
private model: string;
|
|
private startTime: string;
|
|
|
|
constructor(logDir: string, goal: string, provider: string, model: string) {
|
|
this.sessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
this.logDir = logDir;
|
|
this.goal = goal;
|
|
this.provider = provider;
|
|
this.model = model;
|
|
this.startTime = new Date().toISOString();
|
|
|
|
mkdirSync(this.logDir, { recursive: true });
|
|
}
|
|
|
|
logStep(
|
|
step: number,
|
|
foregroundApp: string | null,
|
|
elementCount: number,
|
|
screenChanged: boolean,
|
|
decision: ActionDecision,
|
|
result: { success: boolean; message: string },
|
|
llmLatencyMs: number,
|
|
actionLatencyMs: number
|
|
): void {
|
|
const entry: StepLog = {
|
|
step,
|
|
timestamp: new Date().toISOString(),
|
|
foregroundApp,
|
|
elementCount,
|
|
screenChanged,
|
|
llmDecision: {
|
|
action: decision.action,
|
|
reason: decision.reason,
|
|
coordinates: decision.coordinates,
|
|
text: decision.text,
|
|
think: decision.think,
|
|
plan: decision.plan,
|
|
planProgress: decision.planProgress,
|
|
},
|
|
actionResult: {
|
|
success: result.success,
|
|
message: result.message,
|
|
},
|
|
llmLatencyMs,
|
|
actionLatencyMs,
|
|
};
|
|
this.steps.push(entry);
|
|
|
|
// Write partial file after each step (crash-safe)
|
|
const partialPath = join(this.logDir, `${this.sessionId}.partial.json`);
|
|
writeFileSync(partialPath, JSON.stringify(this.buildSummary(false), null, 2));
|
|
}
|
|
|
|
finalize(completed: boolean): void {
|
|
const summary = this.buildSummary(completed);
|
|
const finalPath = join(this.logDir, `${this.sessionId}.json`);
|
|
writeFileSync(finalPath, JSON.stringify(summary, null, 2));
|
|
console.log(`Session log saved: ${finalPath}`);
|
|
}
|
|
|
|
private buildSummary(completed: boolean): SessionSummary {
|
|
return {
|
|
sessionId: this.sessionId,
|
|
goal: this.goal,
|
|
provider: this.provider,
|
|
model: this.model,
|
|
startTime: this.startTime,
|
|
endTime: new Date().toISOString(),
|
|
totalSteps: this.steps.length,
|
|
successCount: this.steps.filter((s) => s.actionResult.success).length,
|
|
failCount: this.steps.filter((s) => !s.actionResult.success).length,
|
|
completed,
|
|
steps: this.steps,
|
|
};
|
|
}
|
|
}
|