mirror of
https://github.com/thedotmack/claude-mem
synced 2026-04-25 17:15:04 +02:00
fix: typing cleanup and viewer tsconfig split for PR feedback
- Add explicit return types for SessionStore query methods - Exclude src/ui/viewer from root tsconfig, give it its own DOM-typed config - Add bun to root tsconfig types, plus misc typing tweaks flagged by Greptile - Rebuilt plugin/scripts/* artifacts Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,9 @@
|
||||
"cursor:uninstall": "bun plugin/scripts/worker-service.cjs cursor uninstall",
|
||||
"cursor:status": "bun plugin/scripts/worker-service.cjs cursor status",
|
||||
"cursor:setup": "bun plugin/scripts/worker-service.cjs cursor setup",
|
||||
"typecheck": "tsc --noEmit && tsc --noEmit -p src/ui/viewer/tsconfig.json",
|
||||
"typecheck:root": "tsc --noEmit",
|
||||
"typecheck:viewer": "tsc --noEmit -p src/ui/viewer/tsconfig.json",
|
||||
"test": "bun test",
|
||||
"test:sqlite": "bun test tests/sqlite/",
|
||||
"test:agents": "bun test tests/worker/agents/",
|
||||
@@ -131,6 +134,7 @@
|
||||
"@tree-sitter-grammars/tree-sitter-toml": "^0.7.0",
|
||||
"@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
|
||||
"@tree-sitter-grammars/tree-sitter-zig": "^1.1.2",
|
||||
"@types/bun": "^1.3.13",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/express": "^4.17.21",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -351,7 +351,8 @@ function runNpmInstallInMarketplace(): void {
|
||||
execSync('npm install --production', {
|
||||
cwd: marketplaceDir,
|
||||
stdio: 'pipe',
|
||||
...(IS_WINDOWS ? { shell: true as const } : {}),
|
||||
encoding: 'utf8',
|
||||
...(IS_WINDOWS ? { shell: process.env.ComSpec ?? 'cmd.exe' } : {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -370,7 +371,8 @@ function runSmartInstall(): boolean {
|
||||
try {
|
||||
execSync(`node "${smartInstallPath}"`, {
|
||||
stdio: 'inherit',
|
||||
...(IS_WINDOWS ? { shell: true as const } : {}),
|
||||
encoding: 'utf8',
|
||||
...(IS_WINDOWS ? { shell: process.env.ComSpec ?? 'cmd.exe' } : {}),
|
||||
});
|
||||
return true;
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -480,15 +480,6 @@ const QUERIES: Record<string, string> = {
|
||||
(class_definition name: (identifier) @name) @cls
|
||||
(import_statement) @imp
|
||||
(import_declaration) @imp
|
||||
`,
|
||||
|
||||
php: `
|
||||
(function_definition name: (name) @name) @func
|
||||
(method_declaration name: (name) @name) @method
|
||||
(class_declaration name: (name) @name) @cls
|
||||
(interface_declaration name: (name) @name) @iface
|
||||
(trait_declaration name: (name) @name) @trait_def
|
||||
(namespace_use_declaration) @imp
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Database } from 'bun:sqlite';
|
||||
import { Database, type SQLQueryBindings } from 'bun:sqlite';
|
||||
import { DATA_DIR, DB_PATH, ensureDir, OBSERVER_SESSIONS_PROJECT } from '../../shared/paths.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import {
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
LatestPromptResult
|
||||
} from '../../types/database.js';
|
||||
import type { PendingMessageStore } from './PendingMessageStore.js';
|
||||
import type { ObservationSearchResult, SessionSummarySearchResult } from './types.js';
|
||||
import { computeObservationContentHash } from './observations/store.js';
|
||||
import { parseFileList } from './observations/files.js';
|
||||
import { DEFAULT_PLATFORM_SOURCE, normalizePlatformSource, sortPlatformSources } from '../../shared/platform-source.js';
|
||||
@@ -1111,7 +1112,18 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(project, limit);
|
||||
return stmt.all(project, limit) as Array<{
|
||||
request: string | null;
|
||||
investigated: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
files_read: string | null;
|
||||
files_edited: string | null;
|
||||
notes: string | null;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1136,7 +1148,15 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(project, limit);
|
||||
return stmt.all(project, limit) as Array<{
|
||||
memory_session_id: string;
|
||||
request: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1156,7 +1176,12 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(project, limit);
|
||||
return stmt.all(project, limit) as Array<{
|
||||
type: string;
|
||||
text: string;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1192,7 +1217,18 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(limit);
|
||||
return stmt.all(limit) as Array<{
|
||||
id: number;
|
||||
type: string;
|
||||
title: string | null;
|
||||
subtitle: string | null;
|
||||
text: string;
|
||||
project: string;
|
||||
platform_source: string;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1236,7 +1272,22 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(limit);
|
||||
return stmt.all(limit) as Array<{
|
||||
id: number;
|
||||
request: string | null;
|
||||
investigated: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
files_read: string | null;
|
||||
files_edited: string | null;
|
||||
notes: string | null;
|
||||
project: string;
|
||||
platform_source: string;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1268,7 +1319,16 @@ export class SessionStore {
|
||||
LIMIT ?
|
||||
`);
|
||||
|
||||
return stmt.all(limit);
|
||||
return stmt.all(limit) as Array<{
|
||||
id: number;
|
||||
content_session_id: string;
|
||||
project: string;
|
||||
platform_source: string;
|
||||
prompt_number: number;
|
||||
prompt_text: string;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1282,7 +1342,7 @@ export class SessionStore {
|
||||
WHERE project IS NOT NULL AND project != ''
|
||||
AND project != ?
|
||||
`;
|
||||
const params: unknown[] = [OBSERVER_SESSIONS_PROJECT];
|
||||
const params: SQLQueryBindings[] = [OBSERVER_SESSIONS_PROJECT];
|
||||
|
||||
if (normalizedPlatformSource) {
|
||||
query += ' AND COALESCE(platform_source, ?) = ?';
|
||||
@@ -1403,7 +1463,13 @@ export class SessionStore {
|
||||
ORDER BY started_at_epoch ASC
|
||||
`);
|
||||
|
||||
return stmt.all(project, limit);
|
||||
return stmt.all(project, limit) as Array<{
|
||||
memory_session_id: string | null;
|
||||
status: string;
|
||||
started_at: string;
|
||||
user_prompt: string | null;
|
||||
has_summary: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1422,7 +1488,12 @@ export class SessionStore {
|
||||
ORDER BY created_at_epoch ASC
|
||||
`);
|
||||
|
||||
return stmt.all(memorySessionId);
|
||||
return stmt.all(memorySessionId) as Array<{
|
||||
title: string;
|
||||
subtitle: string;
|
||||
type: string;
|
||||
prompt_number: number | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1444,7 +1515,7 @@ export class SessionStore {
|
||||
getObservationsByIds(
|
||||
ids: number[],
|
||||
options: { orderBy?: 'date_desc' | 'date_asc'; limit?: number; project?: string; type?: string | string[]; concepts?: string | string[]; files?: string | string[] } = {}
|
||||
): ObservationRecord[] {
|
||||
): ObservationSearchResult[] {
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
const { orderBy = 'date_desc', limit, project, type, concepts, files } = options;
|
||||
@@ -1508,7 +1579,7 @@ export class SessionStore {
|
||||
${limitClause}
|
||||
`);
|
||||
|
||||
return stmt.all(...params) as ObservationRecord[];
|
||||
return stmt.all(...params) as ObservationSearchResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1538,7 +1609,19 @@ export class SessionStore {
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
return stmt.get(memorySessionId) || null;
|
||||
return (stmt.get(memorySessionId) as {
|
||||
request: string | null;
|
||||
investigated: string | null;
|
||||
learned: string | null;
|
||||
completed: string | null;
|
||||
next_steps: string | null;
|
||||
files_read: string | null;
|
||||
files_edited: string | null;
|
||||
notes: string | null;
|
||||
prompt_number: number | null;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
} | null) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1598,7 +1681,16 @@ export class SessionStore {
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
return stmt.get(id) || null;
|
||||
return (stmt.get(id) as {
|
||||
id: number;
|
||||
content_session_id: string;
|
||||
memory_session_id: string | null;
|
||||
project: string;
|
||||
platform_source: string;
|
||||
user_prompt: string;
|
||||
custom_title: string | null;
|
||||
status: string;
|
||||
} | null) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2216,7 +2308,7 @@ export class SessionStore {
|
||||
getSessionSummariesByIds(
|
||||
ids: number[],
|
||||
options: { orderBy?: 'date_desc' | 'date_asc'; limit?: number; project?: string } = {}
|
||||
): SessionSummaryRecord[] {
|
||||
): SessionSummarySearchResult[] {
|
||||
if (ids.length === 0) return [];
|
||||
|
||||
const { orderBy = 'date_desc', limit, project } = options;
|
||||
@@ -2238,7 +2330,7 @@ export class SessionStore {
|
||||
${limitClause}
|
||||
`);
|
||||
|
||||
return stmt.all(...params) as SessionSummaryRecord[];
|
||||
return stmt.all(...params) as SessionSummarySearchResult[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2470,7 +2562,15 @@ export class SessionStore {
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
return stmt.get(id) || null;
|
||||
return (stmt.get(id) as {
|
||||
id: number;
|
||||
content_session_id: string;
|
||||
prompt_number: number;
|
||||
prompt_text: string;
|
||||
project: string;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
} | null) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2546,7 +2646,18 @@ export class SessionStore {
|
||||
LIMIT 1
|
||||
`);
|
||||
|
||||
return stmt.get(id) || null;
|
||||
return (stmt.get(id) as {
|
||||
id: number;
|
||||
memory_session_id: string | null;
|
||||
content_session_id: string;
|
||||
project: string;
|
||||
user_prompt: string;
|
||||
request_summary: string | null;
|
||||
learned_summary: string | null;
|
||||
status: string;
|
||||
created_at: string;
|
||||
created_at_epoch: number;
|
||||
} | null) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -124,7 +124,7 @@ export class TranscriptEventProcessor {
|
||||
const project = this.resolveProject(entry, watch, schema, event, session);
|
||||
if (project) session.project = project;
|
||||
|
||||
const fields = resolveFields(event.fields, entry, { watch, schema, session });
|
||||
const fields = resolveFields(event.fields, entry, { watch, schema, session: session as unknown as Record<string, unknown> });
|
||||
|
||||
switch (event.action) {
|
||||
case 'session_context':
|
||||
|
||||
@@ -79,6 +79,7 @@ import { DatabaseManager } from './worker/DatabaseManager.js';
|
||||
import { SessionManager } from './worker/SessionManager.js';
|
||||
import { SSEBroadcaster } from './worker/SSEBroadcaster.js';
|
||||
import { SDKAgent } from './worker/SDKAgent.js';
|
||||
import type { WorkerRef } from './worker/agents/types.js';
|
||||
import { GeminiAgent, isGeminiSelected, isGeminiAvailable } from './worker/GeminiAgent.js';
|
||||
import { OpenRouterAgent, isOpenRouterSelected, isOpenRouterAvailable } from './worker/OpenRouterAgent.js';
|
||||
import { PaginationHelper } from './worker/PaginationHelper.js';
|
||||
@@ -136,7 +137,7 @@ export function buildStatusOutput(status: 'ready' | 'error', message?: string):
|
||||
};
|
||||
}
|
||||
|
||||
export class WorkerService {
|
||||
export class WorkerService implements WorkerRef {
|
||||
private server: Server;
|
||||
private startTime: number = Date.now();
|
||||
private mcpClient: Client;
|
||||
@@ -149,7 +150,7 @@ export class WorkerService {
|
||||
// Service layer
|
||||
private dbManager: DatabaseManager;
|
||||
private sessionManager: SessionManager;
|
||||
private sseBroadcaster: SSEBroadcaster;
|
||||
public sseBroadcaster: SSEBroadcaster;
|
||||
private sdkAgent: SDKAgent;
|
||||
private geminiAgent: GeminiAgent;
|
||||
private openRouterAgent: OpenRouterAgent;
|
||||
@@ -496,7 +497,10 @@ export class WorkerService {
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath, // Use resolved path, not bare 'node' which fails on non-interactive PATH (#1876)
|
||||
args: [mcpServerPath],
|
||||
env: sanitizeEnv(process.env)
|
||||
env: Object.fromEntries(
|
||||
Object.entries(sanitizeEnv(process.env)).filter(([, value]) => value !== undefined)
|
||||
) as Record<string, string>
|
||||
|
||||
});
|
||||
|
||||
const MCP_INIT_TIMEOUT_MS = 300000;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* - Efficient LIMIT+1 trick to avoid COUNT(*) query
|
||||
*/
|
||||
|
||||
import type { SQLQueryBindings } from 'bun:sqlite';
|
||||
import { DatabaseManager } from './DatabaseManager.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { OBSERVER_SESSIONS_PROJECT } from '../../shared/paths.js';
|
||||
@@ -102,7 +103,7 @@ export class PaginationHelper {
|
||||
FROM observations o
|
||||
LEFT JOIN sdk_sessions s ON o.memory_session_id = s.memory_session_id
|
||||
`;
|
||||
const params: unknown[] = [];
|
||||
const params: SQLQueryBindings[] = [];
|
||||
const conditions: string[] = [];
|
||||
|
||||
if (project) {
|
||||
|
||||
@@ -110,7 +110,7 @@ export class SDKAgent {
|
||||
logger.info('SDK', 'Starting SDK query', {
|
||||
sessionDbId: session.sessionDbId,
|
||||
contentSessionId: session.contentSessionId,
|
||||
memorySessionId: session.memorySessionId,
|
||||
memorySessionId: session.memorySessionId ?? undefined,
|
||||
hasRealMemorySessionId,
|
||||
shouldResume,
|
||||
resume_parameter: shouldResume ? session.memorySessionId : '(none - fresh start)',
|
||||
@@ -144,7 +144,7 @@ export class SDKAgent {
|
||||
// instead of polluting user's actual project resume lists
|
||||
cwd: OBSERVER_SESSIONS_DIR,
|
||||
// Only resume if shouldResume is true (memorySessionId exists, not first prompt, not forceInit)
|
||||
...(shouldResume && { resume: session.memorySessionId }),
|
||||
...(shouldResume && session.memorySessionId ? { resume: session.memorySessionId } : {}),
|
||||
disallowedTools,
|
||||
abortController: session.abortController,
|
||||
pathToClaudeCodeExecutable: claudePath,
|
||||
|
||||
@@ -37,7 +37,9 @@ export class SettingsManager {
|
||||
for (const row of rows) {
|
||||
const key = row.key as keyof ViewerSettings;
|
||||
if (key in settings) {
|
||||
settings[key] = JSON.parse(row.value) as ViewerSettings[typeof key];
|
||||
// Object.assign narrows correctly across the discriminated union
|
||||
// where `settings[key] = value` would collapse to `never`.
|
||||
Object.assign(settings, { [key]: JSON.parse(row.value) });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -329,7 +329,7 @@ async function syncAndBroadcastObservations(
|
||||
// Only runs if CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED is true (default: false)
|
||||
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
|
||||
// Handle both string 'true' and boolean true from JSON settings
|
||||
const settingValue = settings.CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED;
|
||||
const settingValue: unknown = settings.CLAUDE_MEM_FOLDER_CLAUDEMD_ENABLED;
|
||||
const folderClaudeMdEnabled = settingValue === 'true' || settingValue === true;
|
||||
|
||||
if (folderClaudeMdEnabled) {
|
||||
|
||||
@@ -73,6 +73,17 @@ export class MemoryRoutes extends BaseRouteHandler {
|
||||
});
|
||||
|
||||
// 4. Sync to ChromaDB (async, fire-and-forget)
|
||||
if (!chromaSync) {
|
||||
logger.debug('CHROMA', 'ChromaDB sync skipped (chromaSync not available)', { id: result.id });
|
||||
res.json({
|
||||
success: true,
|
||||
id: result.id,
|
||||
title: observation.title,
|
||||
project: targetProject,
|
||||
message: `Memory saved as observation #${result.id}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
chromaSync.syncObservation(
|
||||
result.id,
|
||||
memorySessionId,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { getPackageRoot } from '../../../../shared/paths.js';
|
||||
import { logger } from '../../../../utils/logger.js';
|
||||
import { SettingsManager } from '../../SettingsManager.js';
|
||||
import { getBranchInfo, switchBranch, pullUpdates } from '../../BranchManager.js';
|
||||
import { ModeManager } from '../../domain/ModeManager.js';
|
||||
import { ModeManager } from '../../../domain/ModeManager.js';
|
||||
import { BaseRouteHandler } from '../BaseRouteHandler.js';
|
||||
import { validateBody } from '../middleware/validateBody.js';
|
||||
import { SettingsDefaultsManager } from '../../../../shared/SettingsDefaultsManager.js';
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import { logger } from '../../../utils/logger.js';
|
||||
import type { ObservationRecord } from '../../../types/database.js';
|
||||
import type { ObservationSearchResult } from '../../sqlite/types.js';
|
||||
import type { SessionStore } from '../../sqlite/SessionStore.js';
|
||||
import type { SearchOrchestrator } from '../search/SearchOrchestrator.js';
|
||||
import { CorpusRenderer } from './CorpusRenderer.js';
|
||||
@@ -121,19 +121,19 @@ export class CorpusBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a raw ObservationRecord (with JSON string fields) to a CorpusObservation
|
||||
* Map a raw ObservationSearchResult (with JSON string fields) to a CorpusObservation
|
||||
*/
|
||||
private mapObservationToCorpus(row: ObservationRecord): CorpusObservation {
|
||||
private mapObservationToCorpus(row: ObservationSearchResult): CorpusObservation {
|
||||
return {
|
||||
id: row.id,
|
||||
type: row.type,
|
||||
title: (row as any).title || '',
|
||||
subtitle: (row as any).subtitle || null,
|
||||
narrative: (row as any).narrative || null,
|
||||
facts: safeParseJsonArray((row as any).facts),
|
||||
concepts: safeParseJsonArray((row as any).concepts),
|
||||
files_read: safeParseJsonArray((row as any).files_read),
|
||||
files_modified: safeParseJsonArray((row as any).files_modified),
|
||||
title: row.title || '',
|
||||
subtitle: row.subtitle || null,
|
||||
narrative: row.narrative || null,
|
||||
facts: safeParseJsonArray(row.facts),
|
||||
concepts: safeParseJsonArray(row.concepts),
|
||||
files_read: safeParseJsonArray(row.files_read),
|
||||
files_modified: safeParseJsonArray(row.files_modified),
|
||||
project: row.project,
|
||||
created_at: row.created_at,
|
||||
created_at_epoch: row.created_at_epoch,
|
||||
|
||||
@@ -107,20 +107,25 @@ export class ChromaSearchStrategy extends BaseSearchStrategy implements SearchSt
|
||||
let sessions: SessionSummarySearchResult[] = [];
|
||||
let prompts: UserPromptSearchResult[] = [];
|
||||
|
||||
// Chroma already ranks by vector similarity; 'relevance' has no SQL
|
||||
// equivalent, so drop it before hydrating rows from SessionStore.
|
||||
const sqlOrderBy: 'date_desc' | 'date_asc' | undefined =
|
||||
options.orderBy === 'relevance' ? undefined : options.orderBy;
|
||||
|
||||
if (categorized.obsIds.length > 0) {
|
||||
const obsOptions = { type: options.obsType, concepts: options.concepts, files: options.files, orderBy: options.orderBy, limit: options.limit, project: options.project };
|
||||
const obsOptions = { type: options.obsType, concepts: options.concepts, files: options.files, orderBy: sqlOrderBy, limit: options.limit, project: options.project };
|
||||
observations = this.sessionStore.getObservationsByIds(categorized.obsIds, obsOptions);
|
||||
}
|
||||
|
||||
if (categorized.sessionIds.length > 0) {
|
||||
sessions = this.sessionStore.getSessionSummariesByIds(categorized.sessionIds, {
|
||||
orderBy: options.orderBy, limit: options.limit, project: options.project
|
||||
orderBy: sqlOrderBy, limit: options.limit, project: options.project
|
||||
});
|
||||
}
|
||||
|
||||
if (categorized.promptIds.length > 0) {
|
||||
prompts = this.sessionStore.getUserPromptsByIds(categorized.promptIds, {
|
||||
orderBy: options.orderBy, limit: options.limit, project: options.project
|
||||
orderBy: sqlOrderBy, limit: options.limit, project: options.project
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ export class SettingsDefaultsManager {
|
||||
* Handles both string 'true' and boolean true from JSON
|
||||
*/
|
||||
static getBool(key: keyof SettingsDefaults): boolean {
|
||||
const value = this.get(key);
|
||||
const value: unknown = this.get(key);
|
||||
return value === 'true' || value === true;
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ export function validateWorkerPidFile(options: ValidateWorkerPidOptions = {}): V
|
||||
let pidInfo: PidInfo | null = null;
|
||||
|
||||
try {
|
||||
pidInfo = JSON.parse(readFileSync(pidFilePath, 'utf-8')) as PidInfo;
|
||||
pidInfo = JSON.parse(readFileSync(pidFilePath, 'utf-8')) as PidInfo | null;
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
logger.warn('SYSTEM', 'Failed to parse worker PID file, removing it', { path: pidFilePath }, error);
|
||||
@@ -178,7 +178,8 @@ export function validateWorkerPidFile(options: ValidateWorkerPidOptions = {}): V
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
if (verifyPidFileOwnership(pidInfo)) {
|
||||
const isAlive = verifyPidFileOwnership(pidInfo);
|
||||
if (isAlive && pidInfo) {
|
||||
if (options.logAlive ?? true) {
|
||||
logger.info('SYSTEM', 'Worker already running (PID alive)', {
|
||||
existingPid: pidInfo.pid,
|
||||
@@ -190,9 +191,9 @@ export function validateWorkerPidFile(options: ValidateWorkerPidOptions = {}): V
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Removing stale PID file (worker process is dead or PID has been reused)', {
|
||||
pid: pidInfo.pid,
|
||||
port: pidInfo.port,
|
||||
startedAt: pidInfo.startedAt
|
||||
pid: pidInfo?.pid,
|
||||
port: pidInfo?.port,
|
||||
startedAt: pidInfo?.startedAt
|
||||
});
|
||||
rmSync(pidFilePath, { force: true });
|
||||
return 'stale';
|
||||
|
||||
@@ -597,9 +597,9 @@ export async function waitForSlot(maxConcurrent: number, timeoutMs: number = 60_
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface SpawnedSdkProcess {
|
||||
stdin: ChildProcess['stdin'];
|
||||
stdout: ChildProcess['stdout'];
|
||||
stderr: ChildProcess['stderr'];
|
||||
stdin: NonNullable<ChildProcess['stdin']>;
|
||||
stdout: NonNullable<ChildProcess['stdout']>;
|
||||
stderr: NonNullable<ChildProcess['stderr']>;
|
||||
readonly killed: boolean;
|
||||
readonly exitCode: number | null;
|
||||
kill: ChildProcess['kill'];
|
||||
@@ -720,6 +720,18 @@ export function spawnSdkProcess(
|
||||
notifySlotAvailable();
|
||||
});
|
||||
|
||||
if (!child.stdin || !child.stdout || !child.stderr) {
|
||||
logger.error('PROCESS', 'Spawned SDK child missing required stdio streams', {
|
||||
sessionDbId,
|
||||
pid,
|
||||
hasStdin: Boolean(child.stdin),
|
||||
hasStdout: Boolean(child.stdout),
|
||||
hasStderr: Boolean(child.stderr),
|
||||
});
|
||||
try { child.kill('SIGKILL'); } catch { /* already dead */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
const spawned: SpawnedSdkProcess = {
|
||||
stdin: child.stdin,
|
||||
stdout: child.stdout,
|
||||
|
||||
@@ -15,7 +15,7 @@ type DataItem = Observation | Summary | UserPrompt;
|
||||
/**
|
||||
* Generic pagination hook for observations, summaries, and prompts
|
||||
*/
|
||||
function usePaginationFor(endpoint: string, dataType: DataType, currentFilter: string, currentSource: string) {
|
||||
function usePaginationFor<TItem extends DataItem>(endpoint: string, dataType: DataType, currentFilter: string, currentSource: string) {
|
||||
const [state, setState] = useState<PaginationState>({
|
||||
isLoading: false,
|
||||
hasMore: true
|
||||
@@ -30,7 +30,7 @@ function usePaginationFor(endpoint: string, dataType: DataType, currentFilter: s
|
||||
* Load more items from the API
|
||||
* Automatically resets offset to 0 if filter has changed
|
||||
*/
|
||||
const loadMore = useCallback(async (): Promise<DataItem[]> => {
|
||||
const loadMore = useCallback(async (): Promise<TItem[]> => {
|
||||
// Check if filter changed - if so, reset pagination synchronously
|
||||
const selectionKey = `${currentSource}::${currentFilter}`;
|
||||
const filterChanged = lastSelectionRef.current !== selectionKey;
|
||||
@@ -75,7 +75,7 @@ function usePaginationFor(endpoint: string, dataType: DataType, currentFilter: s
|
||||
throw new Error(`Failed to load ${dataType}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json() as { items: DataItem[], hasMore: boolean };
|
||||
const data = await response.json() as { items: TItem[], hasMore: boolean };
|
||||
|
||||
const nextState = {
|
||||
...stateRef.current,
|
||||
@@ -106,9 +106,9 @@ function usePaginationFor(endpoint: string, dataType: DataType, currentFilter: s
|
||||
* Hook for paginating observations
|
||||
*/
|
||||
export function usePagination(currentFilter: string, currentSource: string) {
|
||||
const observations = usePaginationFor(API_ENDPOINTS.OBSERVATIONS, 'observations', currentFilter, currentSource);
|
||||
const summaries = usePaginationFor(API_ENDPOINTS.SUMMARIES, 'summaries', currentFilter, currentSource);
|
||||
const prompts = usePaginationFor(API_ENDPOINTS.PROMPTS, 'prompts', currentFilter, currentSource);
|
||||
const observations = usePaginationFor<Observation>(API_ENDPOINTS.OBSERVATIONS, 'observations', currentFilter, currentSource);
|
||||
const summaries = usePaginationFor<Summary>(API_ENDPOINTS.SUMMARIES, 'summaries', currentFilter, currentSource);
|
||||
const prompts = usePaginationFor<UserPrompt>(API_ENDPOINTS.PROMPTS, 'prompts', currentFilter, currentSource);
|
||||
|
||||
return {
|
||||
observations,
|
||||
|
||||
9
src/ui/viewer/tsconfig.json
Normal file
9
src/ui/viewer/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./**/*"],
|
||||
"exclude": []
|
||||
}
|
||||
@@ -15,12 +15,47 @@ export enum LogLevel {
|
||||
SILENT = 4
|
||||
}
|
||||
|
||||
export type Component = 'HOOK' | 'WORKER' | 'SDK' | 'PARSER' | 'DB' | 'SYSTEM' | 'HTTP' | 'SESSION' | 'CHROMA' | 'CHROMA_MCP' | 'CHROMA_SYNC' | 'FOLDER_INDEX' | 'CLAUDE_MD' | 'QUEUE' | 'TELEGRAM';
|
||||
export type Component =
|
||||
| 'AGENTS_MD'
|
||||
| 'BRANCH'
|
||||
| 'CHROMA'
|
||||
| 'CHROMA_MCP'
|
||||
| 'CHROMA_SYNC'
|
||||
| 'CLAUDE_MD'
|
||||
| 'CONFIG'
|
||||
| 'CONSOLE'
|
||||
| 'CURSOR'
|
||||
| 'DB'
|
||||
| 'DEDUP'
|
||||
| 'ENV'
|
||||
| 'FOLDER_INDEX'
|
||||
| 'HOOK'
|
||||
| 'HTTP'
|
||||
| 'IMPORT'
|
||||
| 'INGEST'
|
||||
| 'OPENCLAW'
|
||||
| 'OPENCODE'
|
||||
| 'PARSER'
|
||||
| 'PROCESS'
|
||||
| 'PROJECT_NAME'
|
||||
| 'QUEUE'
|
||||
| 'SDK'
|
||||
| 'SDK_SPAWN'
|
||||
| 'SEARCH'
|
||||
| 'SECURITY'
|
||||
| 'SESSION'
|
||||
| 'SETTINGS'
|
||||
| 'SHUTDOWN'
|
||||
| 'SYSTEM'
|
||||
| 'TELEGRAM'
|
||||
| 'TRANSCRIPT'
|
||||
| 'WINDSURF'
|
||||
| 'WORKER';
|
||||
|
||||
interface LogContext {
|
||||
sessionId?: number;
|
||||
sessionId?: string | number;
|
||||
memorySessionId?: string;
|
||||
correlationId?: string;
|
||||
correlationId?: string | number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"types": ["node"],
|
||||
"types": ["node", "bun"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
@@ -24,6 +24,7 @@
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"tests"
|
||||
"tests",
|
||||
"src/ui/viewer"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user