Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
d6368d7
fix(chat-input): correct auto-resize min/max heights and measurement
Midway65 Apr 4, 2026
4aebd25
fix(accordion): replace full DOM rebuild with targeted in-place updates
Midway65 Apr 4, 2026
7e26b3b
fix(chat-view): remove dead isRetry variable in handleStreamingUpdate
Midway65 Apr 4, 2026
607ec58
remove beta warning banner from chat window
Midway65 Apr 4, 2026
72abed8
add Chat Action Buttons — Copy, Insert, Append, Create File pill
Midway65 Apr 4, 2026
176db1e
fix: remove dead params from createTextBubble after copy button removal
Midway65 Apr 4, 2026
cf8dc23
fix(lint): rename unused bubble param to _bubble in createActionButtons
Midway65 Apr 4, 2026
85ea19b
fix(css): add pointer-events:none to invisible message action pill
Midway65 Apr 4, 2026
0222bdb
fix: move action buttons into existing pill, fix click-blocking
Midway65 Apr 4, 2026
87d8bb2
fix(css): explicit user-select:text on message content + 25% pill opa…
Midway65 Apr 4, 2026
1a01897
fix: move action pill into message header top-right, reduce transition
Midway65 Apr 4, 2026
938e6c3
fix(action-bar): prevent focus loss on Insert/Append button click
Midway65 Apr 4, 2026
e948c79
fix(action-bar): find open note when chat panel has workspace focus
Midway65 Apr 4, 2026
c56b166
fix(create-file-modal): correct inbox case, fix toggle not respected
Midway65 Apr 4, 2026
99864ff
custom/fork-id: rename plugin id and name to nucleus
Midway65 Apr 4, 2026
86fe1f0
merge: integrate upstream v5.6.2
Midway65 Apr 4, 2026
15d90a1
revert(fork-id): restore plugin id=nexus name=Nexus
Midway65 Apr 4, 2026
f47088f
merge: integrate upstream 5.6.3
Midway65 Apr 4, 2026
d4a8f16
merge: integrate upstream v5.6.4
Midway65 Apr 4, 2026
919049c
fix(schema): migration v12 — rebuild note/block embedding tables for …
Midway65 Apr 4, 2026
a85ff4f
fix(schema): fix vec0 dimension migration via native db.exec() path
Midway65 Apr 4, 2026
c77c799
fix(schema): add stub migrations v13-v16 and drop orphaned embedding_…
Midway65 Apr 4, 2026
00e9054
fix(schema): add migration v18 — recreate embedding_metadata without …
Midway65 Apr 4, 2026
dac9d6c
merge: integrate upstream 5.6.5
Midway65 Apr 5, 2026
d54af17
merge: integrate upstream v5.6.6
Midway65 Apr 5, 2026
ac0b83d
fix(schema): migration v19 — drop orphaned semantic_feedback and bloc…
Midway65 Apr 5, 2026
4e87963
fix(css): merge duplicate mobile rule, smooth copy-success transition
Midway65 Apr 5, 2026
17a5eb8
chore: remove dead ContentProcessor utility class
Midway65 Apr 5, 2026
852e895
feat(perplexity): G-C1 — remove stale models, update type literals
Midway65 Apr 5, 2026
6d19189
fix(perplexity): G-C2 — fix request body, remove dead tool code
Midway65 Apr 5, 2026
56f43bb
feat(perplexity): G-C3 — thread response metadata through streaming p…
Midway65 Apr 5, 2026
fba05b5
fix(perplexity): cast requestBody for stripUndefined TypeScript compa…
Midway65 Apr 5, 2026
e34e375
feat(perplexity): G-C4 — citations UI in chat bubble
Midway65 Apr 5, 2026
1bb4fa8
fix(http): use require() for node:https/http in Electron renderer
Midway65 Apr 5, 2026
ceee998
fix(perplexity): strip <think> blocks from display, set explicit max_…
Midway65 Apr 5, 2026
5475d66
fix(branch-nav): handleBranchSwitched fires after switch is done — ju…
Midway65 Apr 5, 2026
ff2b6c5
fix(branch-nav): make handleBranchSwitched a no-op to prevent double …
Midway65 Apr 5, 2026
8059edb
fix(storage): prevent JSONL bloat from streaming chunks; fix large-fi…
Midway65 Apr 5, 2026
3967166
fix(perplexity): placeholder for interrupted streams; raise max_token…
Midway65 Apr 5, 2026
a375a8a
fix(perplexity): increase streaming inactivity timeout from 120s to 300s
Midway65 Apr 5, 2026
5c1e4b7
fix(streaming): propagate timeout error to response stream; add diagn…
Midway65 Apr 5, 2026
147863f
diag(perplexity): log finish_reason and total content chars on stream…
Midway65 Apr 5, 2026
c7eb50b
fix(copy): use branch-aware content for copy action
Midway65 Apr 5, 2026
d3e41ce
revert(perplexity): remove all Work Stream C code (G-C1 through G-C4)
Midway65 Apr 5, 2026
11f9091
fix(context-bar): refresh on tab focus via active-leaf-change
Midway65 Apr 5, 2026
223db8b
merge: integrate upstream v5.6.7
Midway65 Apr 5, 2026
92687ed
fix(storage): delete conversation JSONL on delete; prune orphaned fil…
Midway65 Apr 5, 2026
af70463
fix(chat-header): reset title to 'Chat' when all conversations deleted
Midway65 Apr 5, 2026
dd65f6b
fix(conversation-list): F-01 cleanup timer + createEl for rename inputs
Midway65 Apr 6, 2026
0b2c719
fix(branch-header): F-02 skip re-render when context unchanged
Midway65 Apr 6, 2026
3c928fa
fix(message-bubble): F-03 clear loadingInterval before overwrite
Midway65 Apr 6, 2026
295969c
fix(dead-code): F-04 remove escapeHtml x2 + drop void on sync call
Midway65 Apr 6, 2026
2a2a80f
fix(context-bar): F-05 use Obsidian API idiom for class reset
Midway65 Apr 6, 2026
b436816
fix(conversation-manager): F-06 raise conversation list limit 50 -> 500
Midway65 Apr 6, 2026
9df5102
fix(chat-settings-modal): F-07 persist imageProvider/imageModel on save
Midway65 Apr 6, 2026
b9ef14c
fix(model-selection): F-08 replace hardcoded openai/gpt-4o fallback
Midway65 Apr 6, 2026
b697ab5
fix(provider-modal): F-09 await async save before showing Saved
Midway65 Apr 6, 2026
49a6e71
fix(css): F-10 CSS remediation — all 7 E08 items
Midway65 Apr 6, 2026
265ce88
fix(workspace): guard ws.name?.toLowerCase() in getWorkspaceByNameOrId
Midway65 Apr 6, 2026
07acb3f
fix(workspace): null-guard all ws.name string calls on DB records
Midway65 Apr 6, 2026
1937933
Update connectorContent
Midway65 Apr 6, 2026
da5520e
fix(workspace): G-W1+G-W4 — cheap restore + remove dead per-turn fetches
Midway65 Apr 6, 2026
a59e978
fix(workspace): G-W2 — slim workspace header replaces full JSON blob …
Midway65 Apr 6, 2026
fab62c9
feat(workspace): G-W3 — first-message full context load, slim header …
Midway65 Apr 6, 2026
e15d1f3
fix(workspace): G-W3 regression — preserve loadedWorkspaceData for su…
Midway65 Apr 6, 2026
69c1854
fix(workspace): two code review corrections
Midway65 Apr 7, 2026
e2202c9
fix(workspace): Bug 1 — remove context guard blocking 14/21 workspace…
Midway65 Apr 6, 2026
e7dcb5d
refactor(workspace): drop redundant context param from setWorkspaceCo…
Midway65 Apr 7, 2026
6bd3452
fix(chat): prevent G-W3 flag from being consumed during initialization
Midway65 Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export default defineConfig([
"src/services/embeddings/IndexingQueue.ts",
"src/settings/getStartedStatus.ts",
"src/utils/cli*.ts",
"src/database/storage/JSONLWriter.ts",
],
rules: {
"import/no-nodejs-modules": "off",
Expand Down
4 changes: 2 additions & 2 deletions src/agents/searchManager/services/MemorySearchProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ export class MemorySearchProcessor implements MemorySearchProcessorInterface {

for (const state of statesResult.items) {
let score = 0;
if (state.name.toLowerCase().includes(queryLower)) score += 0.9;
if (state.name?.toLowerCase().includes(queryLower)) score += 0.9;
if (score > 0) {
results.push({ trace: state as unknown as RawMemoryResult['trace'], similarity: score } as RawMemoryResult);
}
Expand All @@ -473,7 +473,7 @@ export class MemorySearchProcessor implements MemorySearchProcessorInterface {

for (const workspace of workspaces) {
let score = 0;
if (workspace.name.toLowerCase().includes(queryLower)) score += 0.9;
if (workspace.name?.toLowerCase().includes(queryLower)) score += 0.9;
if (workspace.description?.toLowerCase().includes(queryLower)) score += 0.8;
if (score > 0) {
results.push({ trace: workspace as unknown as RawMemoryResult['trace'], similarity: score } as RawMemoryResult);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export class ToolBatchExecutionService {
}

const byName = this.knownWorkspaces.find(workspace =>
workspace.name.toLowerCase() === workspaceId.toLowerCase()
workspace.name?.toLowerCase() === workspaceId.toLowerCase()
);
if (byName) {
return null;
Expand Down
37 changes: 21 additions & 16 deletions src/components/LLMProviderModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* - GenericProviderModal (API-key providers)
*/

import { Modal, App } from 'obsidian';
import { Modal, App } from 'obsidian';
import { LLMProviderConfig } from '../types';
import { LLMProviderManager } from '../services/llm/providers/ProviderManager';
import { StaticModelsService } from '../services/StaticModelsService';
Expand Down Expand Up @@ -40,7 +40,7 @@ export interface LLMProviderModalConfig {
config: LLMProviderConfig;
oauthConfig?: OAuthModalConfig;
secondaryOAuthProvider?: SecondaryOAuthProviderConfig;
onSave: (config: LLMProviderConfig) => void;
onSave: (config: LLMProviderConfig) => void | Promise<void>;
/** If true, hide the API key input — provider uses OAuth exclusively */
oauthOnly?: boolean;
}
Expand Down Expand Up @@ -168,7 +168,7 @@ export class LLMProviderModal extends Modal {
clearTimeout(this.autoSaveTimeout);
this.autoSaveTimeout = null;
}
this.config.onSave(config);
void this.config.onSave(config);
this.showSaveStatus('Saved');
setTimeout(() => this.showSaveStatus('Ready'), 2000);
} else {
Expand All @@ -187,19 +187,24 @@ export class LLMProviderModal extends Modal {
this.showSaveStatus('Saving...');

this.autoSaveTimeout = setTimeout(() => {
// Get final config from provider modal
if (this.providerModal) {
this.config.config = this.providerModal.getConfig();
}

// Call the save callback
this.config.onSave(this.config.config);
this.showSaveStatus('Saved');

// Reset status after 2 seconds
setTimeout(() => {
this.showSaveStatus('Ready');
}, 2000);
void (async () => {
// Get final config from provider modal
if (this.providerModal) {
this.config.config = this.providerModal.getConfig();
}

try {
await this.config.onSave(this.config.config);
this.showSaveStatus('Saved');
} catch {
this.showSaveStatus('Save failed');
}

// Reset status after 2 seconds
setTimeout(() => {
this.showSaveStatus('Ready');
}, 2000);
})();
}, 500);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/shared/ChatSettingsRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ export class ChatSettingsRenderer {
dropdown.onChange((value) => {
this.settings.workspaceId = value || null;
this.notifyChange();
void this.syncWorkspacePrompt(value);
this.syncWorkspacePrompt(value);
});
});

Expand Down
1 change: 1 addition & 0 deletions src/core/ingest/VaultIngestionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export class VaultIngestionManager {
};
}


const transcriptionProvider = llmSettings.defaultTranscriptionModel?.provider;
const transcriptionModel = llmSettings.defaultTranscriptionModel?.model;
if (!transcriptionProvider || !transcriptionModel) {
Expand Down
95 changes: 73 additions & 22 deletions src/database/adapters/HybridStorageAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,12 @@ import { ConversationRepository } from '../repositories/ConversationRepository';
import { MessageRepository } from '../repositories/MessageRepository';
import { ProjectRepository } from '../repositories/ProjectRepository';
import { TaskRepository } from '../repositories/TaskRepository';
// Import services
import { ExportService } from '../services/ExportService';

type ExportServiceStateRepo = {
getStates(workspaceId: string, sessionId: string | undefined, options?: { pageSize?: number }): Promise<{ items: StateData[] }>;
};
// Import services
import { ExportService } from '../services/ExportService';
type ExportServiceStateRepo = {
getStates(workspaceId: string, sessionId: string | undefined, options?: { pageSize?: number }): Promise<{ items: StateData[] }>;
};

/**
* Configuration options for HybridStorageAdapter
Expand Down Expand Up @@ -158,16 +158,16 @@ export class HybridStorageAdapter implements IStorageAdapter {
this.taskRepo = new TaskRepository(deps);

// Initialize services
this.exportService = new ExportService({
app: this.app,
conversationRepo: this.conversationRepo,
messageRepo: this.messageRepo,
workspaceRepo: this.workspaceRepo,
sessionRepo: this.sessionRepo,
stateRepo: this.stateRepo as unknown as ExportServiceStateRepo,
traceRepo: this.traceRepo
});
}
this.exportService = new ExportService({
app: this.app,
conversationRepo: this.conversationRepo,
messageRepo: this.messageRepo,
workspaceRepo: this.workspaceRepo,
sessionRepo: this.sessionRepo,
stateRepo: this.stateRepo as unknown as ExportServiceStateRepo,
traceRepo: this.traceRepo
});
}

// ============================================================================
// Lifecycle Management
Expand Down Expand Up @@ -199,10 +199,10 @@ export class HybridStorageAdapter implements IStorageAdapter {
});

// Start initialization in background
this.performInitialization().catch((error: unknown) => {
this.initError = error instanceof Error ? error : new Error(String(error));
console.error('[HybridStorageAdapter] Background initialization failed:', error);
});
this.performInitialization().catch((error: unknown) => {
this.initError = error instanceof Error ? error : new Error(String(error));
console.error('[HybridStorageAdapter] Background initialization failed:', error);
});

// If blocking mode, wait for completion
if (blocking) {
Expand Down Expand Up @@ -248,6 +248,23 @@ export class HybridStorageAdapter implements IStorageAdapter {
// This can take a long time for large vaults (168MB+ JSONL files).
// The UI will show incrementally as data syncs in.
const syncState = await this.sqliteCache.getSyncState(this.jsonlWriter.getDeviceId());

// 5. Prune orphaned conversation JSONL files BEFORE sync/rebuild.
// Only safe when a prior session exists (syncState present), meaning SQLite
// accurately reflects what was alive at end of last session. Orphaned files
// (from the pre-fix delete bug) have no SQLite record at this point and can
// be safely deleted. Running BEFORE rebuild prevents fullRebuild from
// re-reading the orphaned files and resurrecting deleted conversations.
// Skip on first-ever startup (!syncState) — SQLite is empty and every file
// would look orphaned.
if (syncState) {
try {
await this.pruneOrphanedConversationFiles();
} catch (pruneError) {
console.error('[HybridStorageAdapter] Orphaned conversation file pruning failed:', pruneError);
}
}

if (!syncState || actuallyMigrated) {
try {
await this.syncCoordinator.fullRebuild();
Expand All @@ -261,14 +278,14 @@ export class HybridStorageAdapter implements IStorageAdapter {
console.error('[HybridStorageAdapter] Incremental sync failed:', syncError);
}

// 5. Reconcile JSONL workspaces missing from SQLite
// 6. Reconcile JSONL workspaces missing from SQLite
try {
await this.reconcileMissingWorkspaces();
} catch (reconcileError) {
console.error('[HybridStorageAdapter] Workspace reconciliation failed:', reconcileError);
}

// 6. Reconcile JSONL tasks missing from SQLite
// 7. Reconcile JSONL tasks missing from SQLite
try {
await this.reconcileMissingTasks();
} catch (reconcileError) {
Expand Down Expand Up @@ -390,6 +407,40 @@ export class HybridStorageAdapter implements IStorageAdapter {
}
}

/**
* Remove JSONL files for conversations that no longer exist in SQLite.
*
* Prior to the deleteConversation fix, ConversationRepository.delete() only
* removed the SQLite row and never deleted the JSONL file. This left orphaned
* files behind for every conversation that was ever deleted. This method runs
* once per startup (incremental sync path) to clean them up.
*/
private async pruneOrphanedConversationFiles(): Promise<void> {
const files = await this.jsonlWriter.listFiles('conversations');
if (files.length === 0) return;

let pruned = 0;
for (const file of files) {
const match = file.match(/conversations\/conv_(.+)\.jsonl$/);
if (!match) continue;

const conversationId = match[1];
const existing = await this.conversationRepo.getById(conversationId);
if (existing) continue;

try {
await this.jsonlWriter.deleteFile(file);
pruned++;
} catch (e) {
console.error(`[HybridStorageAdapter] Failed to prune orphaned conversation file ${file}:`, e);
}
}

if (pruned > 0) {
console.warn(`[HybridStorageAdapter] Pruned ${pruned} orphaned conversation JSONL file(s)`);
}
}

/**
* Check if the adapter is ready for use
*/
Expand Down
Loading