mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-18 21:21:11 +02:00
code clean up
This commit is contained in:
parent
ce30c05604
commit
69c4021ead
10 changed files with 188 additions and 123 deletions
|
|
@ -258,6 +258,20 @@ export async function startServicesWatcher(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function stopRunsWatcher(): void {
|
||||||
|
if (runsWatcher) {
|
||||||
|
runsWatcher();
|
||||||
|
runsWatcher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stopServicesWatcher(): void {
|
||||||
|
if (servicesWatcher) {
|
||||||
|
servicesWatcher();
|
||||||
|
servicesWatcher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Handler Implementations
|
// Handler Implementations
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
import { app, BrowserWindow, protocol, net, shell } from "electron";
|
import { app, BrowserWindow, protocol, net, shell } from "electron";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { setupIpcHandlers, startRunsWatcher, startServicesWatcher, startWorkspaceWatcher, stopWorkspaceWatcher } from "./ipc.js";
|
import {
|
||||||
|
setupIpcHandlers,
|
||||||
|
startRunsWatcher,
|
||||||
|
startServicesWatcher,
|
||||||
|
startWorkspaceWatcher,
|
||||||
|
stopRunsWatcher,
|
||||||
|
stopServicesWatcher,
|
||||||
|
stopWorkspaceWatcher
|
||||||
|
} from "./ipc.js";
|
||||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import { dirname } from "node:path";
|
import { dirname } from "node:path";
|
||||||
import { updateElectronApp, UpdateSourceType } from "update-electron-app";
|
import { updateElectronApp, UpdateSourceType } from "update-electron-app";
|
||||||
|
|
@ -185,4 +193,6 @@ app.on("window-all-closed", () => {
|
||||||
app.on("before-quit", () => {
|
app.on("before-quit", () => {
|
||||||
// Clean up watcher on app quit
|
// Clean up watcher on app quit
|
||||||
stopWorkspaceWatcher();
|
stopWorkspaceWatcher();
|
||||||
|
stopRunsWatcher();
|
||||||
|
stopServicesWatcher();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ function readConfig(): NoteCreationConfig {
|
||||||
? config.strictness
|
? config.strictness
|
||||||
: DEFAULT_STRICTNESS,
|
: DEFAULT_STRICTNESS,
|
||||||
configured: config.configured === true,
|
configured: config.configured === true,
|
||||||
|
onboardingComplete: config.onboardingComplete === true,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return { strictness: DEFAULT_STRICTNESS, configured: false };
|
return { strictness: DEFAULT_STRICTNESS, configured: false };
|
||||||
|
|
@ -83,7 +84,10 @@ export function markStrictnessConfigured(): void {
|
||||||
* Set strictness and mark as configured in one operation.
|
* Set strictness and mark as configured in one operation.
|
||||||
*/
|
*/
|
||||||
export function setStrictnessAndMarkConfigured(strictness: NoteCreationStrictness): void {
|
export function setStrictnessAndMarkConfigured(strictness: NoteCreationStrictness): void {
|
||||||
writeConfig({ strictness, configured: true });
|
const config = readConfig();
|
||||||
|
config.strictness = strictness;
|
||||||
|
config.configured = true;
|
||||||
|
writeConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
type GraphState,
|
type GraphState,
|
||||||
} from './graph_state.js';
|
} from './graph_state.js';
|
||||||
import { buildKnowledgeIndex, formatIndexForPrompt } from './knowledge_index.js';
|
import { buildKnowledgeIndex, formatIndexForPrompt } from './knowledge_index.js';
|
||||||
|
import { limitEventItems } from './limit_event_items.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build obsidian-style knowledge graph by running topic extraction
|
* Build obsidian-style knowledge graph by running topic extraction
|
||||||
|
|
@ -33,14 +34,6 @@ const SOURCE_FOLDERS = [
|
||||||
|
|
||||||
// Voice memos are now created directly in knowledge/Voice Memos/<date>/
|
// Voice memos are now created directly in knowledge/Voice Memos/<date>/
|
||||||
const VOICE_MEMOS_KNOWLEDGE_DIR = path.join(NOTES_OUTPUT_DIR, 'Voice Memos');
|
const VOICE_MEMOS_KNOWLEDGE_DIR = path.join(NOTES_OUTPUT_DIR, 'Voice Memos');
|
||||||
const MAX_EVENT_ITEMS = 50;
|
|
||||||
|
|
||||||
function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
|
||||||
if (items.length <= max) {
|
|
||||||
return { items, truncated: false };
|
|
||||||
}
|
|
||||||
return { items: items.slice(0, max), truncated: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
function extractPathFromToolInput(input: string): string | null {
|
function extractPathFromToolInput(input: string): string | null {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { WorkDir } from '../../config/config.js';
|
||||||
import container from '../../di/container.js';
|
import container from '../../di/container.js';
|
||||||
import { IGranolaConfigRepo } from './repo.js';
|
import { IGranolaConfigRepo } from './repo.js';
|
||||||
import { serviceLogger } from '../../services/service_logger.js';
|
import { serviceLogger } from '../../services/service_logger.js';
|
||||||
|
import { limitEventItems } from '../limit_event_items.js';
|
||||||
import {
|
import {
|
||||||
GetDocumentsResponse,
|
GetDocumentsResponse,
|
||||||
SyncState,
|
SyncState,
|
||||||
|
|
@ -23,14 +24,6 @@ const API_DELAY_MS = 1000; // 1 second delay between API calls
|
||||||
const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit
|
const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit
|
||||||
const MAX_RETRIES = 3; // Maximum retries for rate-limited requests
|
const MAX_RETRIES = 3; // Maximum retries for rate-limited requests
|
||||||
const MAX_BATCH_SIZE = 10; // Process max 10 documents per folder per sync
|
const MAX_BATCH_SIZE = 10; // Process max 10 documents per folder per sync
|
||||||
const MAX_EVENT_ITEMS = 50;
|
|
||||||
|
|
||||||
function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
|
||||||
if (items.length <= max) {
|
|
||||||
return { items, truncated: false };
|
|
||||||
}
|
|
||||||
return { items: items.slice(0, max), truncated: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Wake Signal for Immediate Sync Trigger ---
|
// --- Wake Signal for Immediate Sync Trigger ---
|
||||||
let wakeResolve: (() => void) | null = null;
|
let wakeResolve: (() => void) | null = null;
|
||||||
|
|
|
||||||
8
apps/x/packages/core/src/knowledge/limit_event_items.ts
Normal file
8
apps/x/packages/core/src/knowledge/limit_event_items.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export const MAX_EVENT_ITEMS = 50;
|
||||||
|
|
||||||
|
export function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
||||||
|
if (items.length <= max) {
|
||||||
|
return { items, truncated: false };
|
||||||
|
}
|
||||||
|
return { items: items.slice(0, max), truncated: true };
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import { NodeHtmlMarkdown } from 'node-html-markdown'
|
||||||
import { WorkDir } from '../config/config.js';
|
import { WorkDir } from '../config/config.js';
|
||||||
import { GoogleClientFactory } from './google-client-factory.js';
|
import { GoogleClientFactory } from './google-client-factory.js';
|
||||||
import { serviceLogger } from '../services/service_logger.js';
|
import { serviceLogger } from '../services/service_logger.js';
|
||||||
|
import { limitEventItems } from './limit_event_items.js';
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const SYNC_DIR = path.join(WorkDir, 'calendar_sync');
|
const SYNC_DIR = path.join(WorkDir, 'calendar_sync');
|
||||||
|
|
@ -15,15 +16,6 @@ const REQUIRED_SCOPES = [
|
||||||
'https://www.googleapis.com/auth/calendar.events.readonly',
|
'https://www.googleapis.com/auth/calendar.events.readonly',
|
||||||
'https://www.googleapis.com/auth/drive.readonly'
|
'https://www.googleapis.com/auth/drive.readonly'
|
||||||
];
|
];
|
||||||
const MAX_EVENT_ITEMS = 50;
|
|
||||||
|
|
||||||
function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
|
||||||
if (items.length <= max) {
|
|
||||||
return { items, truncated: false };
|
|
||||||
}
|
|
||||||
return { items: items.slice(0, max), truncated: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const nhm = new NodeHtmlMarkdown();
|
const nhm = new NodeHtmlMarkdown();
|
||||||
|
|
||||||
// --- Wake Signal for Immediate Sync Trigger ---
|
// --- Wake Signal for Immediate Sync Trigger ---
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import path from 'path';
|
||||||
import { WorkDir } from '../config/config.js';
|
import { WorkDir } from '../config/config.js';
|
||||||
import { FirefliesClientFactory } from './fireflies-client-factory.js';
|
import { FirefliesClientFactory } from './fireflies-client-factory.js';
|
||||||
import { serviceLogger, type ServiceRunContext } from '../services/service_logger.js';
|
import { serviceLogger, type ServiceRunContext } from '../services/service_logger.js';
|
||||||
|
import { limitEventItems } from './limit_event_items.js';
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const SYNC_DIR = path.join(WorkDir, 'fireflies_transcripts');
|
const SYNC_DIR = path.join(WorkDir, 'fireflies_transcripts');
|
||||||
|
|
@ -12,14 +13,6 @@ const LOOKBACK_DAYS = 30; // Last 1 month
|
||||||
const API_DELAY_MS = 2000; // 2 second delay between API calls
|
const API_DELAY_MS = 2000; // 2 second delay between API calls
|
||||||
const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit
|
const RATE_LIMIT_RETRY_DELAY_MS = 60 * 1000; // Wait 1 minute on rate limit
|
||||||
const MAX_RETRIES = 3; // Maximum retries for rate-limited requests
|
const MAX_RETRIES = 3; // Maximum retries for rate-limited requests
|
||||||
const MAX_EVENT_ITEMS = 50;
|
|
||||||
|
|
||||||
function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
|
||||||
if (items.length <= max) {
|
|
||||||
return { items, truncated: false };
|
|
||||||
}
|
|
||||||
return { items: items.slice(0, max), truncated: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Wake Signal for Immediate Sync Trigger ---
|
// --- Wake Signal for Immediate Sync Trigger ---
|
||||||
let wakeResolve: (() => void) | null = null;
|
let wakeResolve: (() => void) | null = null;
|
||||||
|
|
@ -603,7 +596,7 @@ async function syncMeetings() {
|
||||||
level: 'info',
|
level: 'info',
|
||||||
message: `Fireflies sync complete: ${newCount} transcript${newCount === 1 ? '' : 's'}`,
|
message: `Fireflies sync complete: ${newCount} transcript${newCount === 1 ? '' : 's'}`,
|
||||||
durationMs: Date.now() - run.startedAt,
|
durationMs: Date.now() - run.startedAt,
|
||||||
outcome: newCount > 0 ? 'ok' : 'error',
|
outcome: newCount > 0 ? 'ok' : 'idle',
|
||||||
summary: { transcripts: newCount },
|
summary: { transcripts: newCount },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,13 @@ import { NodeHtmlMarkdown } from 'node-html-markdown'
|
||||||
import { OAuth2Client } from 'google-auth-library';
|
import { OAuth2Client } from 'google-auth-library';
|
||||||
import { WorkDir } from '../config/config.js';
|
import { WorkDir } from '../config/config.js';
|
||||||
import { GoogleClientFactory } from './google-client-factory.js';
|
import { GoogleClientFactory } from './google-client-factory.js';
|
||||||
import { serviceLogger } from '../services/service_logger.js';
|
import { serviceLogger, type ServiceRunContext } from '../services/service_logger.js';
|
||||||
|
import { limitEventItems } from './limit_event_items.js';
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
const SYNC_DIR = path.join(WorkDir, 'gmail_sync');
|
const SYNC_DIR = path.join(WorkDir, 'gmail_sync');
|
||||||
const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes
|
const SYNC_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes
|
||||||
const REQUIRED_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly';
|
const REQUIRED_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly';
|
||||||
const MAX_EVENT_ITEMS = 50;
|
|
||||||
|
|
||||||
function limitEventItems(items: string[], max: number = MAX_EVENT_ITEMS): { items: string[]; truncated: boolean } {
|
|
||||||
if (items.length <= max) {
|
|
||||||
return { items, truncated: false };
|
|
||||||
}
|
|
||||||
return { items: items.slice(0, max), truncated: true };
|
|
||||||
}
|
|
||||||
|
|
||||||
const nhm = new NodeHtmlMarkdown();
|
const nhm = new NodeHtmlMarkdown();
|
||||||
|
|
||||||
// --- Wake Signal for Immediate Sync Trigger ---
|
// --- Wake Signal for Immediate Sync Trigger ---
|
||||||
|
|
@ -201,79 +193,120 @@ async function fullSync(auth: OAuth2Client, syncDir: string, attachmentsDir: str
|
||||||
console.log(`Performing full sync of last ${lookbackDays} days...`);
|
console.log(`Performing full sync of last ${lookbackDays} days...`);
|
||||||
const gmail = google.gmail({ version: 'v1', auth });
|
const gmail = google.gmail({ version: 'v1', auth });
|
||||||
|
|
||||||
const pastDate = new Date();
|
let run: ServiceRunContext | null = null;
|
||||||
pastDate.setDate(pastDate.getDate() - lookbackDays);
|
const ensureRun = async () => {
|
||||||
const dateQuery = pastDate.toISOString().split('T')[0].replace(/-/g, '/');
|
if (!run) {
|
||||||
|
run = await serviceLogger.startRun({
|
||||||
|
service: 'gmail',
|
||||||
|
message: 'Syncing Gmail',
|
||||||
|
trigger: 'timer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Get History ID
|
try {
|
||||||
const profile = await gmail.users.getProfile({ userId: 'me' });
|
const pastDate = new Date();
|
||||||
const currentHistoryId = profile.data.historyId!;
|
pastDate.setDate(pastDate.getDate() - lookbackDays);
|
||||||
|
const dateQuery = pastDate.toISOString().split('T')[0].replace(/-/g, '/');
|
||||||
|
|
||||||
const threadIds: string[] = [];
|
// Get History ID
|
||||||
let pageToken: string | undefined;
|
const profile = await gmail.users.getProfile({ userId: 'me' });
|
||||||
do {
|
const currentHistoryId = profile.data.historyId!;
|
||||||
const res = await gmail.users.threads.list({
|
|
||||||
userId: 'me',
|
|
||||||
q: `after:${dateQuery}`,
|
|
||||||
pageToken
|
|
||||||
});
|
|
||||||
|
|
||||||
const threads = res.data.threads;
|
const threadIds: string[] = [];
|
||||||
if (threads) {
|
let pageToken: string | undefined;
|
||||||
for (const thread of threads) {
|
do {
|
||||||
if (thread.id) {
|
const res = await gmail.users.threads.list({
|
||||||
threadIds.push(thread.id);
|
userId: 'me',
|
||||||
|
q: `after:${dateQuery}`,
|
||||||
|
pageToken
|
||||||
|
});
|
||||||
|
|
||||||
|
const threads = res.data.threads;
|
||||||
|
if (threads) {
|
||||||
|
for (const thread of threads) {
|
||||||
|
if (thread.id) {
|
||||||
|
threadIds.push(thread.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageToken = res.data.nextPageToken ?? undefined;
|
||||||
|
} while (pageToken);
|
||||||
|
|
||||||
|
if (threadIds.length === 0) {
|
||||||
|
saveState(currentHistoryId, stateFile);
|
||||||
|
console.log("Full sync complete. No threads found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureRun();
|
||||||
|
const limitedThreads = limitEventItems(threadIds);
|
||||||
|
await serviceLogger.log({
|
||||||
|
type: 'changes_identified',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'info',
|
||||||
|
message: `Found ${threadIds.length} thread${threadIds.length === 1 ? '' : 's'} to sync`,
|
||||||
|
counts: { threads: threadIds.length },
|
||||||
|
items: limitedThreads.items,
|
||||||
|
truncated: limitedThreads.truncated,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const threadId of threadIds) {
|
||||||
|
await processThread(auth, threadId, syncDir, attachmentsDir);
|
||||||
}
|
}
|
||||||
pageToken = res.data.nextPageToken ?? undefined;
|
|
||||||
} while (pageToken);
|
|
||||||
|
|
||||||
if (threadIds.length === 0) {
|
|
||||||
saveState(currentHistoryId, stateFile);
|
saveState(currentHistoryId, stateFile);
|
||||||
console.log("Full sync complete. No threads found.");
|
await serviceLogger.log({
|
||||||
return;
|
type: 'run_complete',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'info',
|
||||||
|
message: `Gmail sync complete: ${threadIds.length} thread${threadIds.length === 1 ? '' : 's'}`,
|
||||||
|
durationMs: Date.now() - run!.startedAt,
|
||||||
|
outcome: 'ok',
|
||||||
|
summary: { threads: threadIds.length },
|
||||||
|
});
|
||||||
|
console.log("Full sync complete.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during full sync:", error);
|
||||||
|
await ensureRun();
|
||||||
|
await serviceLogger.log({
|
||||||
|
type: 'error',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'error',
|
||||||
|
message: 'Gmail sync error',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
await serviceLogger.log({
|
||||||
|
type: 'run_complete',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'error',
|
||||||
|
message: 'Gmail sync failed',
|
||||||
|
durationMs: Date.now() - run!.startedAt,
|
||||||
|
outcome: 'error',
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = await serviceLogger.startRun({
|
|
||||||
service: 'gmail',
|
|
||||||
message: 'Syncing Gmail',
|
|
||||||
trigger: 'timer',
|
|
||||||
});
|
|
||||||
const limitedThreads = limitEventItems(threadIds);
|
|
||||||
await serviceLogger.log({
|
|
||||||
type: 'changes_identified',
|
|
||||||
service: run.service,
|
|
||||||
runId: run.runId,
|
|
||||||
level: 'info',
|
|
||||||
message: `Found ${threadIds.length} thread${threadIds.length === 1 ? '' : 's'} to sync`,
|
|
||||||
counts: { threads: threadIds.length },
|
|
||||||
items: limitedThreads.items,
|
|
||||||
truncated: limitedThreads.truncated,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const threadId of threadIds) {
|
|
||||||
await processThread(auth, threadId, syncDir, attachmentsDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveState(currentHistoryId, stateFile);
|
|
||||||
await serviceLogger.log({
|
|
||||||
type: 'run_complete',
|
|
||||||
service: run.service,
|
|
||||||
runId: run.runId,
|
|
||||||
level: 'info',
|
|
||||||
message: `Gmail sync complete: ${threadIds.length} thread${threadIds.length === 1 ? '' : 's'}`,
|
|
||||||
durationMs: Date.now() - run.startedAt,
|
|
||||||
outcome: 'ok',
|
|
||||||
summary: { threads: threadIds.length },
|
|
||||||
});
|
|
||||||
console.log("Full sync complete.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir: string, attachmentsDir: string, stateFile: string, lookbackDays: number) {
|
async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir: string, attachmentsDir: string, stateFile: string, lookbackDays: number) {
|
||||||
console.log(`Checking updates since historyId ${startHistoryId}...`);
|
console.log(`Checking updates since historyId ${startHistoryId}...`);
|
||||||
const gmail = google.gmail({ version: 'v1', auth });
|
const gmail = google.gmail({ version: 'v1', auth });
|
||||||
|
|
||||||
|
let run: ServiceRunContext | null = null;
|
||||||
|
const ensureRun = async () => {
|
||||||
|
if (!run) {
|
||||||
|
run = await serviceLogger.startRun({
|
||||||
|
service: 'gmail',
|
||||||
|
message: 'Syncing Gmail',
|
||||||
|
trigger: 'timer',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await gmail.users.history.list({
|
const res = await gmail.users.history.list({
|
||||||
userId: 'me',
|
userId: 'me',
|
||||||
|
|
@ -308,17 +341,13 @@ async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const run = await serviceLogger.startRun({
|
await ensureRun();
|
||||||
service: 'gmail',
|
|
||||||
message: 'Syncing Gmail',
|
|
||||||
trigger: 'timer',
|
|
||||||
});
|
|
||||||
const threadIdList = Array.from(threadIds);
|
const threadIdList = Array.from(threadIds);
|
||||||
const limitedThreads = limitEventItems(threadIdList);
|
const limitedThreads = limitEventItems(threadIdList);
|
||||||
await serviceLogger.log({
|
await serviceLogger.log({
|
||||||
type: 'changes_identified',
|
type: 'changes_identified',
|
||||||
service: run.service,
|
service: run!.service,
|
||||||
runId: run.runId,
|
runId: run!.runId,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
message: `Found ${threadIdList.length} new thread${threadIdList.length === 1 ? '' : 's'}`,
|
message: `Found ${threadIdList.length} new thread${threadIdList.length === 1 ? '' : 's'}`,
|
||||||
counts: { threads: threadIdList.length },
|
counts: { threads: threadIdList.length },
|
||||||
|
|
@ -334,11 +363,11 @@ async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir:
|
||||||
saveState(profile.data.historyId!, stateFile);
|
saveState(profile.data.historyId!, stateFile);
|
||||||
await serviceLogger.log({
|
await serviceLogger.log({
|
||||||
type: 'run_complete',
|
type: 'run_complete',
|
||||||
service: run.service,
|
service: run!.service,
|
||||||
runId: run.runId,
|
runId: run!.runId,
|
||||||
level: 'info',
|
level: 'info',
|
||||||
message: `Gmail sync complete: ${threadIdList.length} thread${threadIdList.length === 1 ? '' : 's'}`,
|
message: `Gmail sync complete: ${threadIdList.length} thread${threadIdList.length === 1 ? '' : 's'}`,
|
||||||
durationMs: Date.now() - run.startedAt,
|
durationMs: Date.now() - run!.startedAt,
|
||||||
outcome: 'ok',
|
outcome: 'ok',
|
||||||
summary: { threads: threadIdList.length },
|
summary: { threads: threadIdList.length },
|
||||||
});
|
});
|
||||||
|
|
@ -348,13 +377,32 @@ async function partialSync(auth: OAuth2Client, startHistoryId: string, syncDir:
|
||||||
if (e.response?.status === 404) {
|
if (e.response?.status === 404) {
|
||||||
console.log("History ID expired. Falling back to full sync.");
|
console.log("History ID expired. Falling back to full sync.");
|
||||||
await fullSync(auth, syncDir, attachmentsDir, stateFile, lookbackDays);
|
await fullSync(auth, syncDir, attachmentsDir, stateFile, lookbackDays);
|
||||||
} else {
|
return;
|
||||||
console.error("Error during partial sync:", error);
|
}
|
||||||
// If 401, clear tokens to force re-auth next run
|
|
||||||
if (e.response?.status === 401) {
|
console.error("Error during partial sync:", error);
|
||||||
console.log("401 Unauthorized, clearing cache");
|
await ensureRun();
|
||||||
GoogleClientFactory.clearCache();
|
await serviceLogger.log({
|
||||||
}
|
type: 'error',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'error',
|
||||||
|
message: 'Gmail sync error',
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
await serviceLogger.log({
|
||||||
|
type: 'run_complete',
|
||||||
|
service: run!.service,
|
||||||
|
runId: run!.runId,
|
||||||
|
level: 'error',
|
||||||
|
message: 'Gmail sync failed',
|
||||||
|
durationMs: Date.now() - run!.startedAt,
|
||||||
|
outcome: 'error',
|
||||||
|
});
|
||||||
|
// If 401, clear tokens to force re-auth next run
|
||||||
|
if (e.response?.status === 401) {
|
||||||
|
console.log("401 Unauthorized, clearing cache");
|
||||||
|
GoogleClientFactory.clearCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type { ServiceEventType } from "@x/shared/dist/service-events.js";
|
||||||
import { serviceBus } from "./service_bus.js";
|
import { serviceBus } from "./service_bus.js";
|
||||||
|
|
||||||
type ServiceNameType = ServiceEventType["service"];
|
type ServiceNameType = ServiceEventType["service"];
|
||||||
type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
|
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
||||||
type ServiceEventInput = DistributiveOmit<ServiceEventType, "ts">;
|
type ServiceEventInput = DistributiveOmit<ServiceEventType, "ts">;
|
||||||
|
|
||||||
const LOG_DIR = path.join(WorkDir, "logs");
|
const LOG_DIR = path.join(WorkDir, "logs");
|
||||||
|
|
@ -47,8 +47,18 @@ export class ServiceLogger {
|
||||||
private async rotateIfNeeded(nextBytes: number): Promise<void> {
|
private async rotateIfNeeded(nextBytes: number): Promise<void> {
|
||||||
if (this.currentSize + nextBytes <= MAX_LOG_BYTES) return;
|
if (this.currentSize + nextBytes <= MAX_LOG_BYTES) return;
|
||||||
if (this.stream) {
|
if (this.stream) {
|
||||||
this.stream.close();
|
const stream = this.stream;
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
let settled = false;
|
||||||
|
const done = () => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
stream.once("error", done);
|
||||||
|
stream.end(done);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const ts = safeTimestampForFile(new Date().toISOString());
|
const ts = safeTimestampForFile(new Date().toISOString());
|
||||||
const rotatedPath = path.join(LOG_DIR, `services.${ts}.jsonl`);
|
const rotatedPath = path.join(LOG_DIR, `services.${ts}.jsonl`);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue