diff --git a/apps/desktop/src/renderer/api.ts b/apps/desktop/src/renderer/api.ts
index 77e68503..4cdf138f 100644
--- a/apps/desktop/src/renderer/api.ts
+++ b/apps/desktop/src/renderer/api.ts
@@ -1090,6 +1090,18 @@ export async function fetchIndexStatus(options: {
embeddingError?: string;
lexicalReady?: boolean;
embeddingPhase?: string;
+ health?: {
+ overall: string;
+ pipelines: {
+ codeIndex: { status: string; reason?: string };
+ textFts: { status: string; reason?: string };
+ embeddings: { status: string; reason?: string; profileId?: string };
+ graph: { status: string };
+ map: { status: string };
+ treeSitter: { status: string };
+ };
+ native: { sqlite: string; lancedb: string; onnx: string };
+ };
}> {
const res = await fetch(`${options.baseUrl}/v1/index/status`, {
headers: authHeaders(options.token),
diff --git a/apps/desktop/src/renderer/styles.css b/apps/desktop/src/renderer/styles.css
index becf68c2..364ae48a 100644
--- a/apps/desktop/src/renderer/styles.css
+++ b/apps/desktop/src/renderer/styles.css
@@ -4447,6 +4447,15 @@ button.md-file-link.md-code-inline:hover {
line-height: 1.35;
}
+.index-status__pipelines {
+ font-family: var(--mono);
+ font-size: 10px;
+ color: var(--mitii-muted);
+ line-height: 1.45;
+ display: grid;
+ gap: 2px;
+}
+
.index-status__stream {
max-height: 160px;
overflow: auto;
diff --git a/apps/vscode/package.json b/apps/vscode/package.json
index 038f9741..5156a765 100644
--- a/apps/vscode/package.json
+++ b/apps/vscode/package.json
@@ -2,7 +2,7 @@
"name": "mitii-ai-agent",
"displayName": "Mitii AI Agent",
"description": "Local-first VS Code AI coding agent with repository-aware context and controlled execution",
- "version": "2.10.3",
+ "version": "2.10.4",
"publisher": "mitii",
"license": "AGPL-3.0-or-later",
"icon": "media/mitii-logo.png",
diff --git a/apps/vscode/src/protocol.ts b/apps/vscode/src/protocol.ts
index ec156957..56d9118b 100644
--- a/apps/vscode/src/protocol.ts
+++ b/apps/vscode/src/protocol.ts
@@ -251,6 +251,29 @@ export interface IndexStatusSnapshot {
embeddingSource?: SemanticIndexSource;
embeddingModel?: string;
embeddingEnabled?: boolean;
+ /** Shared Code/FTS/Embeddings pipeline board from @mitii/host. */
+ pipelineHealth?: {
+ overall: string;
+ running?: boolean;
+ pipelines: {
+ codeIndex: { status: string; reason?: string; revision?: string };
+ textFts: { status: string; reason?: string; revision?: string };
+ embeddings: {
+ status: string;
+ reason?: string;
+ profileId?: string;
+ };
+ graph: { status: string; reason?: string };
+ map: { status: string; reason?: string };
+ treeSitter: { status: string; reason?: string };
+ };
+ native: {
+ sqlite: string;
+ lancedb: string;
+ onnx: string;
+ };
+ lastError?: string;
+ };
}
export interface WorkspaceSnapshotInfo {
diff --git a/apps/vscode/src/sidebar.ts b/apps/vscode/src/sidebar.ts
index 60a53c77..f2238729 100644
--- a/apps/vscode/src/sidebar.ts
+++ b/apps/vscode/src/sidebar.ts
@@ -17,6 +17,7 @@ import {
IndexLockedError,
buildFixReviewFindingsAsk,
resolveModelCostRates,
+ readIndexPipelineHealth,
} from '@mitii/host';
import type { SkillDescriptor } from '@mitii/v8';
@@ -3417,11 +3418,26 @@ export class MitiiSidebarProvider implements vscode.WebviewViewProvider {
this.vs,
this.secrets,
);
+ const root = this.effectiveRoot();
+ const health = root
+ ? readIndexPipelineHealth({ workspaceRoot: root })
+ : undefined;
return {
...index,
embeddingSource: semantic.source ?? (semantic.enabled ? 'bundled' : 'disabled'),
embeddingModel: semantic.model,
embeddingEnabled: semantic.enabled,
+ ...(health
+ ? {
+ pipelineHealth: {
+ overall: health.overall,
+ running: health.running,
+ pipelines: health.pipelines,
+ native: health.native,
+ ...(health.lastError ? { lastError: health.lastError } : {}),
+ },
+ }
+ : {}),
};
} catch {
return index;
diff --git a/apps/vscode/webview-ui/src/components/IndexingStatusBar.tsx b/apps/vscode/webview-ui/src/components/IndexingStatusBar.tsx
index bfdac095..3ab7eebd 100644
--- a/apps/vscode/webview-ui/src/components/IndexingStatusBar.tsx
+++ b/apps/vscode/webview-ui/src/components/IndexingStatusBar.tsx
@@ -119,14 +119,27 @@ function detailTooltip(index: IndexStatusSnapshot): string {
`Mode: ${index.indexMode === 'full' ? 'full code/text' : 'host snapshot'}`,
);
}
- for (const capability of index.capabilities ?? []) {
- const label =
- CAPABILITY_LABELS[capability.capability] ?? capability.capability;
+ if (index.pipelineHealth) {
+ const p = index.pipelineHealth.pipelines;
+ parts.push(`Health: ${index.pipelineHealth.overall}`);
+ parts.push(`Code: ${p.codeIndex.status}`);
+ parts.push(`FTS: ${p.textFts.status}`);
parts.push(
- capability.capability === 'vectorIndex' && capability.status === 'degraded'
- ? `${label}: degraded — reindex to restore semantic search`
- : `${label}: ${capability.status}`,
+ `Embeddings: ${p.embeddings.status}${p.embeddings.reason ? ` (${p.embeddings.reason})` : ''}`,
);
+ parts.push(
+ `Native: sqlite=${index.pipelineHealth.native.sqlite} lancedb=${index.pipelineHealth.native.lancedb} onnx=${index.pipelineHealth.native.onnx}`,
+ );
+ } else {
+ for (const capability of index.capabilities ?? []) {
+ const label =
+ CAPABILITY_LABELS[capability.capability] ?? capability.capability;
+ parts.push(
+ capability.capability === 'vectorIndex' && capability.status === 'degraded'
+ ? `${label}: degraded — reindex to restore semantic search`
+ : `${label}: ${capability.status}`,
+ );
+ }
}
if (index.truncated) parts.push('Scan truncated');
if (index.message) parts.push(index.message);
diff --git a/apps/vscode/webview-ui/src/components/SettingsPanel.tsx b/apps/vscode/webview-ui/src/components/SettingsPanel.tsx
index e5ac6e12..3d715223 100644
--- a/apps/vscode/webview-ui/src/components/SettingsPanel.tsx
+++ b/apps/vscode/webview-ui/src/components/SettingsPanel.tsx
@@ -1142,6 +1142,34 @@ export function SettingsPanel(props: SettingsPanelProps) {
{ label: 'Readiness', value: index.readiness ?? '—' },
{ label: 'Scan', value: index.scanCompleteness ?? '—' },
{ label: 'Mode', value: formatIndexMode(index.indexMode) },
+ {
+ label: 'Pipeline health',
+ value: index.pipelineHealth?.overall ?? '—',
+ },
+ {
+ label: 'Code Index',
+ value: index.pipelineHealth
+ ? `${index.pipelineHealth.pipelines.codeIndex.status}${index.pipelineHealth.pipelines.codeIndex.reason ? ` (${index.pipelineHealth.pipelines.codeIndex.reason})` : ''}`
+ : '—',
+ },
+ {
+ label: 'Text FTS5',
+ value: index.pipelineHealth
+ ? `${index.pipelineHealth.pipelines.textFts.status}${index.pipelineHealth.pipelines.textFts.reason ? ` (${index.pipelineHealth.pipelines.textFts.reason})` : ''}`
+ : '—',
+ },
+ {
+ label: 'Embeddings',
+ value: index.pipelineHealth
+ ? `${index.pipelineHealth.pipelines.embeddings.status}${index.pipelineHealth.pipelines.embeddings.reason ? ` (${index.pipelineHealth.pipelines.embeddings.reason})` : ''}`
+ : '—',
+ },
+ {
+ label: 'Native modules',
+ value: index.pipelineHealth
+ ? `sqlite=${index.pipelineHealth.native.sqlite} · lancedb=${index.pipelineHealth.native.lancedb} · onnx=${index.pipelineHealth.native.onnx}`
+ : '—',
+ },
]}
/>
{capabilityDetails(index).length > 0 ? (
diff --git a/apps/vscode/webview-ui/src/protocol.ts b/apps/vscode/webview-ui/src/protocol.ts
index b6af41f2..076c0d84 100644
--- a/apps/vscode/webview-ui/src/protocol.ts
+++ b/apps/vscode/webview-ui/src/protocol.ts
@@ -249,6 +249,29 @@ export interface IndexStatusSnapshot {
embeddingSource?: SemanticIndexSource;
embeddingModel?: string;
embeddingEnabled?: boolean;
+ /** Shared Code/FTS/Embeddings pipeline board from @mitii/host. */
+ pipelineHealth?: {
+ overall: string;
+ running?: boolean;
+ pipelines: {
+ codeIndex: { status: string; reason?: string; revision?: string };
+ textFts: { status: string; reason?: string; revision?: string };
+ embeddings: {
+ status: string;
+ reason?: string;
+ profileId?: string;
+ };
+ graph: { status: string; reason?: string };
+ map: { status: string; reason?: string };
+ treeSitter: { status: string; reason?: string };
+ };
+ native: {
+ sqlite: string;
+ lancedb: string;
+ onnx: string;
+ };
+ lastError?: string;
+ };
}
export interface WorkspaceSnapshotInfo {
diff --git a/package.json b/package.json
index 2a61fa1d..a3cca8f7 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mitii-ai-agent",
"description": "Private Mitii monorepo workspace orchestrator. Product packages: @mitii/v8, @mitii/sdk, @mitii/automation, @mitii/search-kit, @mitii/mcp, @mitii/mcp-web, @mitii/mcp-sqlite, @mitii/mcp-postgres, @mitii/mcp-mongo, @mitii/mcp-sql, @mitii/host, @mitii/cli, @mitii/daemon, @mitii/acp, @mitii/desktop, apps/vscode.",
- "version": "2.10.3",
+ "version": "2.10.4",
"private": true,
"license": "AGPL-3.0-or-later",
"author": {
diff --git a/packages/automation/package.json b/packages/automation/package.json
index 24850418..c136eede 100644
--- a/packages/automation/package.json
+++ b/packages/automation/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/automation",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Mitii automation control plane: schedules, event ingress, claim/lease runner, webhooks (Phases 1–2).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/host/README.md b/packages/host/README.md
index db8ab9ab..be1256de 100644
--- a/packages/host/README.md
+++ b/packages/host/README.md
@@ -50,7 +50,7 @@ Apps still own environment-specific pieces: secrets, settings UI, MCP, diagnosti
src/
index.ts # public barrel - import from `@mitii/host`
sqlite/ # injection contract for openDatabase
- indexing/ # embeddings, full index, fingerprint snapshot
+ indexing/ # embeddings, full index, fingerprint snapshot, pipeline health
bundled-embedding/ # on-device MiniLM source (native ONNX + WASM)
treeSitter/ # web-tree-sitter runtime; V8 injects query text
repository-context/ # createHostRepositoryContext
@@ -73,6 +73,7 @@ Prefer importing from `@mitii/host`. Do not import `internal/`.
| `createBundledMiniLmEmbeddingProvider` | V8 `EmbeddingProvider` | On-device MiniLM (native ONNX, WASM fallback) |
| `createLanceDbConnection` | V8 `LanceDbConnectionPort` | Optional `@lancedb/lancedb` vector store |
| `runFullWorkspaceIndex` | Orchestrates V8 index runtime | Writes `.mitii/repository-index.sqlite`, LanceDB, graph/map |
+| `readIndexPipelineHealth` / `formatIndexPipelineHealthLines` | Index observability | Code / FTS5 / Embeddings / Graph / native status from `.mitii/` |
| `runCorpusIndex` / `CorpusRetrievalSource` | Optional corpus RAG | Indexes `.mitii/corpus/` markdown/text → `index.json`; hybrid `additionalSources` when `corpusEnabled` |
| `buildWorkspaceSnapshot` | Builds `PublishRepositoryStateInput` | Fingerprint-only; indexes marked unavailable. `roots[0].rootId` is the workspace directory basename. |
| `createHostRepositoryContext` | V8 `RepositoryContextPipeline` | Hybrid retrieve + file-map fallback. File-map fallback honors `folderPrefix`. Optional `corpusEnabled` (default false). |
@@ -148,6 +149,8 @@ await client.start({ /* ... */, projectRules });
**Indexing:** prefer `runFullWorkspaceIndex` -> publish repository state. If that has not run, fall back to `buildWorkspaceSnapshot` (honest fingerprint: indexes unavailable).
+**Index pipeline health:** call `readIndexPipelineHealth({ workspaceRoot })` (or CLI `mitii index --status`) to see which pipeline is ready vs failed: Code Index, Text FTS5, Embeddings, Graph/Map, Tree-sitter, and native sqlite / LanceDB / ONNX. Overall is `ready`, `lexical_only`, `running`, `failed`, or `missing`.
+
**Semantic retrieval** is off unless the host passes `semanticIndex.enabled` (and a ready embedding profile). When disabled, repository context logs `semantic_index_disabled` and falls back to path-based discovery.
**Memory** persists under `.mitii/memory/facts.json` when `createWorkspaceMemoryStore` is injected. The adapter accepts validated facts and returns validated facts through `query`/`list`; `delete` returns `{ id, deleted, message }`. Mutations read and validate the stored envelope and every row before replacing the file using a same-directory temporary file and rename. A per-instance mutation queue serializes calls through one adapter object; exclusive directory locks coordinate separate instances and processes on the same facts file. `transact` applies a scoped decision and writes its full change set under that same lock.
diff --git a/packages/host/package.json b/packages/host/package.json
index a7050748..bc18ffaf 100644
--- a/packages/host/package.json
+++ b/packages/host/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/host",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Shared host kit for Mitii apps: SQLite injection, workspace indexing, repository context, durable ports (checkpoints/memory/skills/search/network), project rules, provider presets. Web retrieval via @mitii/search-kit.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/host/src/index.ts b/packages/host/src/index.ts
index a7dc637d..0cfc9478 100644
--- a/packages/host/src/index.ts
+++ b/packages/host/src/index.ts
@@ -102,6 +102,19 @@ export type {
IndexLockInfo,
IndexProgressSnapshot,
} from './indexing/indexLock.js';
+export {
+ INDEX_PIPELINE_HEALTH_SCHEMA_VERSION,
+ readIndexPipelineHealth,
+ formatIndexPipelineHealthLines,
+} from './indexing/indexPipelineHealth.js';
+export type {
+ IndexOverallHealth,
+ IndexPipelineEntry,
+ IndexPipelineHealth,
+ IndexPipelineStatus,
+ IndexNativeHealth,
+ ReadIndexPipelineHealthOptions,
+} from './indexing/indexPipelineHealth.js';
export {
DEFAULT_MAXIMUM_INDEX_FILES,
MAXIMUM_INDEX_FILES,
diff --git a/packages/host/src/indexing/indexPipelineHealth.spec.ts b/packages/host/src/indexing/indexPipelineHealth.spec.ts
new file mode 100644
index 00000000..d23725e3
--- /dev/null
+++ b/packages/host/src/indexing/indexPipelineHealth.spec.ts
@@ -0,0 +1,279 @@
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+import {
+ formatIndexPipelineHealthLines,
+ readIndexPipelineHealth,
+} from './indexPipelineHealth.js';
+import type { IndexRuntimeMetadata } from './semanticIndex.js';
+
+function writeMeta(
+ mitiiDir: string,
+ partial: Partial
&
+ Pick,
+): void {
+ const sqlitePath = join(mitiiDir, 'repository-index.sqlite');
+ const lanceDbPath = join(mitiiDir, 'lancedb');
+ writeFileSync(sqlitePath, '');
+ mkdirSync(lanceDbPath, { recursive: true });
+ const meta: IndexRuntimeMetadata = {
+ schemaVersion: 1,
+ workspaceId: partial.workspaceId,
+ sqlitePath: partial.sqlitePath ?? sqlitePath,
+ lanceDbPath: partial.lanceDbPath ?? lanceDbPath,
+ generatedAt: partial.generatedAt ?? '2026-10-01T12:00:00.000Z',
+ fileCount: partial.fileCount ?? 3,
+ truncated: partial.truncated ?? false,
+ textIndexSchemaVersion: partial.textIndexSchemaVersion ?? 3,
+ treeSitterRuntime: partial.treeSitterRuntime ?? 'ready',
+ ...(partial.embeddingProfile
+ ? { embeddingProfile: partial.embeddingProfile }
+ : {}),
+ ...(partial.lastEmbeddingError
+ ? { lastEmbeddingError: partial.lastEmbeddingError }
+ : {}),
+ ...(partial.lastIndexingResult
+ ? { lastIndexingResult: partial.lastIndexingResult }
+ : {}),
+ ...(partial.graphRevisionByRoot
+ ? { graphRevisionByRoot: partial.graphRevisionByRoot }
+ : {}),
+ ...(partial.mapRevisionByRoot
+ ? { mapRevisionByRoot: partial.mapRevisionByRoot }
+ : {}),
+ ...(partial.catalogRevisionByRoot
+ ? { catalogRevisionByRoot: partial.catalogRevisionByRoot }
+ : {}),
+ };
+ writeFileSync(
+ join(mitiiDir, 'index-runtime.json'),
+ `${JSON.stringify(meta, null, 2)}\n`,
+ );
+}
+
+describe('readIndexPipelineHealth', () => {
+ it('reports missing when .mitii has no index artifacts', () => {
+ const root = mkdtempSync(join(tmpdir(), 'mitii-health-missing-'));
+ try {
+ const health = readIndexPipelineHealth({
+ workspaceRoot: root,
+ probeNative: false,
+ });
+ expect(health.overall).toBe('missing');
+ expect(health.pipelines.codeIndex.status).toBe('missing');
+ expect(health.pipelines.textFts.status).toBe('missing');
+ expect(health.pipelines.embeddings.status).toBe('missing');
+ expect(health.native.sqlite).toBe('unknown');
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ });
+
+ it('reports lexical_only when FTS is ready but embeddings are not', () => {
+ const root = mkdtempSync(join(tmpdir(), 'mitii-health-lexical-'));
+ const mitiiDir = join(root, '.mitii');
+ mkdirSync(mitiiDir, { recursive: true });
+ try {
+ writeMeta(mitiiDir, {
+ workspaceId: 'ws-1',
+ lastIndexingResult: {
+ schemaVersion: 1,
+ workspace: 'ws-1',
+ workspaceSnapshotId: 'snap-1',
+ status: 'complete',
+ indexedAt: Date.now(),
+ cleanupAllowed: true,
+ fileResultsTruncated: false,
+ rootResults: [
+ {
+ rootId: 'root',
+ status: 'complete',
+ cleanupPerformed: false,
+ codeIndexRemovedFiles: 0,
+ textIndexRemovedDocuments: 0,
+ textIndexRemovedChunks: 0,
+ codeIndexRevision: 7,
+ finalTextRevision: 4,
+ embeddedChunks: 0,
+ vectorsDeleted: 0,
+ warnings: [],
+ },
+ ],
+ fileResults: [],
+ warnings: [],
+ statistics: {
+ availableFiles: 3,
+ selectedFiles: 3,
+ skippedFiles: 0,
+ processedFiles: 3,
+ completeFiles: 3,
+ partialFiles: 0,
+ failedFiles: 0,
+ cancelledFiles: 0,
+ skippedByPolicy: 0,
+ },
+ } as IndexRuntimeMetadata['lastIndexingResult'],
+ graphRevisionByRoot: { root: 'g1' },
+ mapRevisionByRoot: { root: 'm1' },
+ catalogRevisionByRoot: { root: 'c1' },
+ });
+
+ const health = readIndexPipelineHealth({
+ workspaceRoot: root,
+ probeNative: false,
+ });
+ expect(health.pipelines.codeIndex.status).toBe('ready');
+ expect(health.pipelines.codeIndex.revision).toBe('7');
+ expect(health.pipelines.textFts.status).toBe('ready');
+ expect(health.pipelines.textFts.revision).toBe('4');
+ expect(health.pipelines.embeddings.status).toBe('unavailable');
+ expect(health.pipelines.graph.status).toBe('ready');
+ expect(health.pipelines.map.status).toBe('ready');
+ expect(health.pipelines.treeSitter.status).toBe('ready');
+ expect(health.overall).toBe('lexical_only');
+
+ const lines = formatIndexPipelineHealthLines(health);
+ expect(lines.some((line) => line.includes('Code Index=ready'))).toBe(
+ true,
+ );
+ expect(lines.some((line) => line.includes('Text FTS5=ready'))).toBe(true);
+ expect(lines.some((line) => line.includes('Embeddings=unavailable'))).toBe(
+ true,
+ );
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ });
+
+ it('reports ready when embedding profile and LanceDB path exist', () => {
+ const root = mkdtempSync(join(tmpdir(), 'mitii-health-ready-'));
+ const mitiiDir = join(root, '.mitii');
+ mkdirSync(mitiiDir, { recursive: true });
+ try {
+ writeMeta(mitiiDir, {
+ workspaceId: 'ws-2',
+ embeddingProfile: {
+ id: 'bundled-minilm',
+ providerId: 'bundled',
+ modelId: 'all-MiniLM-L6-v2',
+ dimensions: 384,
+ normalized: true,
+ },
+ lastIndexingResult: {
+ schemaVersion: 1,
+ workspace: 'ws-2',
+ workspaceSnapshotId: 'snap-2',
+ status: 'complete',
+ indexedAt: Date.now(),
+ cleanupAllowed: true,
+ fileResultsTruncated: false,
+ rootResults: [
+ {
+ rootId: 'root',
+ status: 'complete',
+ cleanupPerformed: false,
+ codeIndexRemovedFiles: 0,
+ textIndexRemovedDocuments: 0,
+ textIndexRemovedChunks: 0,
+ codeIndexRevision: 1,
+ finalTextRevision: 1,
+ embeddingStatus: 'complete',
+ embeddingProfileId: 'bundled-minilm',
+ embeddedChunks: 2,
+ vectorsDeleted: 0,
+ warnings: [],
+ },
+ ],
+ fileResults: [],
+ warnings: [],
+ statistics: {
+ availableFiles: 1,
+ selectedFiles: 1,
+ skippedFiles: 0,
+ processedFiles: 1,
+ completeFiles: 1,
+ partialFiles: 0,
+ failedFiles: 0,
+ cancelledFiles: 0,
+ skippedByPolicy: 0,
+ },
+ } as IndexRuntimeMetadata['lastIndexingResult'],
+ graphRevisionByRoot: { root: 'g1' },
+ mapRevisionByRoot: { root: 'm1' },
+ });
+
+ const health = readIndexPipelineHealth({
+ workspaceRoot: root,
+ probeNative: false,
+ });
+ expect(health.pipelines.embeddings.status).toBe('ready');
+ expect(health.pipelines.embeddings.profileId).toBe('bundled-minilm');
+ expect(health.overall).toBe('ready');
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ });
+
+ it('surfaces lastEmbeddingError as embeddings degraded', () => {
+ const root = mkdtempSync(join(tmpdir(), 'mitii-health-embed-err-'));
+ const mitiiDir = join(root, '.mitii');
+ mkdirSync(mitiiDir, { recursive: true });
+ try {
+ writeMeta(mitiiDir, {
+ workspaceId: 'ws-3',
+ lastEmbeddingError: 'onnx_load_failed',
+ lastIndexingResult: {
+ schemaVersion: 1,
+ workspace: 'ws-3',
+ workspaceSnapshotId: 'snap-3',
+ status: 'complete',
+ indexedAt: Date.now(),
+ cleanupAllowed: true,
+ fileResultsTruncated: false,
+ rootResults: [
+ {
+ rootId: 'root',
+ status: 'complete',
+ cleanupPerformed: false,
+ codeIndexRemovedFiles: 0,
+ textIndexRemovedDocuments: 0,
+ textIndexRemovedChunks: 0,
+ codeIndexRevision: 2,
+ finalTextRevision: 2,
+ embeddedChunks: 0,
+ vectorsDeleted: 0,
+ warnings: [],
+ },
+ ],
+ fileResults: [],
+ warnings: [],
+ statistics: {
+ availableFiles: 1,
+ selectedFiles: 1,
+ skippedFiles: 0,
+ processedFiles: 1,
+ completeFiles: 1,
+ partialFiles: 0,
+ failedFiles: 0,
+ cancelledFiles: 0,
+ skippedByPolicy: 0,
+ },
+ } as IndexRuntimeMetadata['lastIndexingResult'],
+ });
+
+ const health = readIndexPipelineHealth({
+ workspaceRoot: root,
+ probeNative: false,
+ });
+ expect(health.pipelines.embeddings.status).toBe('degraded');
+ expect(health.pipelines.embeddings.reason).toBe('onnx_load_failed');
+ expect(health.lastError).toBe('onnx_load_failed');
+ expect(health.overall).toBe('lexical_only');
+ } finally {
+ rmSync(root, { recursive: true, force: true });
+ }
+ });
+});
diff --git a/packages/host/src/indexing/indexPipelineHealth.ts b/packages/host/src/indexing/indexPipelineHealth.ts
new file mode 100644
index 00000000..e334583d
--- /dev/null
+++ b/packages/host/src/indexing/indexPipelineHealth.ts
@@ -0,0 +1,480 @@
+/**
+ * Shared Index Pipeline Health — Code / FTS / Embeddings / Graph / native.
+ *
+ * Reads `.mitii/index-runtime.json`, progress/lock, and light on-disk probes so
+ * CLI, Desktop, and VS Code can show which pipeline is ready vs failed.
+ */
+
+import { existsSync } from 'node:fs';
+import { createRequire } from 'node:module';
+import { join } from 'node:path';
+
+import { resolveRuntimeFilename } from '../internal/resolveRuntimeFilename.js';
+import {
+ isIndexLockHeld,
+ readIndexProgress,
+ type IndexProgressSnapshot,
+} from './indexLock.js';
+import {
+ readIndexRuntimeMetadata,
+ type IndexRuntimeMetadata,
+} from './semanticIndex.js';
+import {
+ ONNX_RUNTIME_NODE_PACKAGE,
+ ONNX_RUNTIME_WEB_PACKAGE,
+} from './bundled-embedding/constants.js';
+
+export const INDEX_PIPELINE_HEALTH_SCHEMA_VERSION = 1 as const;
+
+export type IndexPipelineStatus =
+ | 'ready'
+ | 'degraded'
+ | 'unavailable'
+ | 'running'
+ | 'pending'
+ | 'missing';
+
+export type IndexOverallHealth =
+ | 'ready'
+ | 'lexical_only'
+ | 'running'
+ | 'failed'
+ | 'missing';
+
+export interface IndexPipelineEntry {
+ status: IndexPipelineStatus;
+ reason?: string;
+ revision?: string;
+ profileId?: string;
+}
+
+export interface IndexNativeHealth {
+ sqlite: 'ready' | 'unavailable' | 'unknown';
+ lancedb: 'ready' | 'unavailable' | 'unknown';
+ onnx: 'native' | 'wasm' | 'unavailable' | 'unknown';
+}
+
+export interface IndexPipelineHealth {
+ schemaVersion: typeof INDEX_PIPELINE_HEALTH_SCHEMA_VERSION;
+ overall: IndexOverallHealth;
+ workspaceId?: string;
+ generatedAt?: string;
+ running: boolean;
+ progress?: {
+ stage: string;
+ message: string;
+ percent: number;
+ lexicalReady?: boolean;
+ embeddingPhase?: string;
+ };
+ pipelines: {
+ codeIndex: IndexPipelineEntry;
+ textFts: IndexPipelineEntry;
+ embeddings: IndexPipelineEntry;
+ graph: IndexPipelineEntry;
+ map: IndexPipelineEntry;
+ treeSitter: IndexPipelineEntry;
+ };
+ native: IndexNativeHealth;
+ counts: {
+ files: number;
+ truncated: boolean;
+ };
+ paths: {
+ mitiiDir: string;
+ sqlitePath?: string;
+ lanceDbPath?: string;
+ };
+ lastError?: string;
+}
+
+export interface ReadIndexPipelineHealthOptions {
+ workspaceRoot: string;
+ /** Default `.mitii` under workspaceRoot. */
+ mitiiDir?: string;
+ /**
+ * Probe optional native packages (LanceDB / ONNX). Default true.
+ * Disable in hot UI loops if needed.
+ */
+ probeNative?: boolean;
+}
+
+const INDEX_RUNTIME_FILE = 'index-runtime.json';
+const INDEX_DB_FILE = 'repository-index.sqlite';
+const LANCEDB_DIR = 'lancedb';
+
+function entry(
+ status: IndexPipelineStatus,
+ extras: Omit = {},
+): IndexPipelineEntry {
+ return {
+ status,
+ ...extras,
+ };
+}
+
+function firstRoot(meta: IndexRuntimeMetadata | undefined) {
+ return meta?.lastIndexingResult?.rootResults?.[0];
+}
+
+function deriveCodeIndex(
+ meta: IndexRuntimeMetadata | undefined,
+ progress: IndexProgressSnapshot | undefined,
+ running: boolean,
+): IndexPipelineEntry {
+ if (running && progress && !progress.lexicalReady) {
+ return entry('running', { reason: progress.message });
+ }
+ const root = firstRoot(meta);
+ const revision =
+ root?.codeIndexRevision !== undefined
+ ? String(root.codeIndexRevision)
+ : meta?.catalogRevisionByRoot
+ ? Object.values(meta.catalogRevisionByRoot)[0]
+ : undefined;
+ if (root?.codeIndexRevision !== undefined) {
+ if (root.status === 'partial') {
+ return entry('degraded', {
+ reason: 'code_index_partial',
+ revision: String(root.codeIndexRevision),
+ });
+ }
+ return entry('ready', { revision: String(root.codeIndexRevision) });
+ }
+ if (meta && existsSync(meta.sqlitePath)) {
+ return entry('ready', {
+ reason: 'sqlite_present',
+ ...(revision ? { revision } : {}),
+ });
+ }
+ if (!meta) return entry('missing', { reason: 'index_runtime_missing' });
+ return entry('unavailable', { reason: 'code_index_revision_missing' });
+}
+
+function deriveTextFts(
+ meta: IndexRuntimeMetadata | undefined,
+ progress: IndexProgressSnapshot | undefined,
+ running: boolean,
+): IndexPipelineEntry {
+ if (running && progress && !progress.lexicalReady) {
+ return entry('running', { reason: progress.message });
+ }
+ const root = firstRoot(meta);
+ const revision =
+ root?.finalTextRevision ??
+ root?.latestTextRevision ??
+ root?.initialTextRevision;
+ if (revision !== undefined) {
+ if (root?.status === 'partial') {
+ return entry('degraded', {
+ reason: 'text_index_partial',
+ revision: String(revision),
+ });
+ }
+ return entry('ready', { revision: String(revision) });
+ }
+ if (meta?.textIndexSchemaVersion !== undefined && existsSync(meta.sqlitePath)) {
+ return entry('ready', {
+ reason: 'fts_schema_present',
+ revision: String(meta.textIndexSchemaVersion),
+ });
+ }
+ if (!meta) return entry('missing', { reason: 'index_runtime_missing' });
+ return entry('unavailable', { reason: 'text_index_revision_missing' });
+}
+
+function deriveEmbeddings(
+ meta: IndexRuntimeMetadata | undefined,
+ progress: IndexProgressSnapshot | undefined,
+ running: boolean,
+): IndexPipelineEntry {
+ const phase = progress?.embeddingPhase;
+ if (running && (phase === 'running' || phase === 'pending')) {
+ return entry(phase === 'running' ? 'running' : 'pending', {
+ reason: progress?.message,
+ ...(meta?.embeddingProfile?.id
+ ? { profileId: meta.embeddingProfile.id }
+ : {}),
+ });
+ }
+ if (meta?.lastEmbeddingError) {
+ return entry('degraded', {
+ reason: meta.lastEmbeddingError,
+ ...(meta.embeddingProfile?.id
+ ? { profileId: meta.embeddingProfile.id }
+ : {}),
+ });
+ }
+ const root = firstRoot(meta);
+ if (
+ root?.embeddingStatus === 'complete' ||
+ root?.embeddingStatus === 'unchanged'
+ ) {
+ return entry('ready', {
+ ...(root.embeddingProfileId
+ ? { profileId: root.embeddingProfileId }
+ : meta?.embeddingProfile?.id
+ ? { profileId: meta.embeddingProfile.id }
+ : {}),
+ });
+ }
+ if (root?.embeddingStatus === 'partial') {
+ return entry('degraded', {
+ reason: 'embedding_partial',
+ ...(root.embeddingProfileId ? { profileId: root.embeddingProfileId } : {}),
+ });
+ }
+ if (meta?.embeddingProfile?.id && meta.lanceDbPath && existsSync(meta.lanceDbPath)) {
+ return entry('ready', { profileId: meta.embeddingProfile.id });
+ }
+ if (!meta) return entry('missing', { reason: 'index_runtime_missing' });
+ if (phase === 'unavailable' || !meta.embeddingProfile) {
+ return entry('unavailable', {
+ reason: meta.lastEmbeddingError ?? 'embeddings_not_configured_or_not_built',
+ });
+ }
+ return entry('unavailable', {
+ reason: 'embedding_profile_or_lancedb_missing',
+ ...(meta.embeddingProfile?.id
+ ? { profileId: meta.embeddingProfile.id }
+ : {}),
+ });
+}
+
+function deriveGraph(meta: IndexRuntimeMetadata | undefined): IndexPipelineEntry {
+ const revision = meta?.graphRevisionByRoot
+ ? Object.values(meta.graphRevisionByRoot)[0]
+ : undefined;
+ if (revision) return entry('ready', { revision });
+ if (!meta) return entry('missing');
+ return entry('unavailable', { reason: 'graph_revision_missing' });
+}
+
+function deriveMap(meta: IndexRuntimeMetadata | undefined): IndexPipelineEntry {
+ const revision = meta?.mapRevisionByRoot
+ ? Object.values(meta.mapRevisionByRoot)[0]
+ : undefined;
+ if (revision) return entry('ready', { revision });
+ if (!meta) return entry('missing');
+ return entry('unavailable', { reason: 'map_revision_missing' });
+}
+
+function deriveTreeSitter(
+ meta: IndexRuntimeMetadata | undefined,
+): IndexPipelineEntry {
+ if (meta?.treeSitterRuntime === 'ready') {
+ return entry('ready');
+ }
+ if (meta?.treeSitterRuntime === 'unavailable') {
+ return entry('unavailable', { reason: 'tree_sitter_unavailable' });
+ }
+ if (!meta) return entry('missing');
+ return entry('unavailable', { reason: 'tree_sitter_status_unknown' });
+}
+
+function canResolvePackage(packageId: string): boolean {
+ try {
+ createRequire(resolveRuntimeFilename()).resolve(packageId);
+ return true;
+ } catch {
+ try {
+ createRequire(join(process.cwd(), 'package.json')).resolve(packageId);
+ return true;
+ } catch {
+ return false;
+ }
+ }
+}
+
+function probeNativeModules(options: {
+ sqlitePath?: string;
+ lanceDbPath?: string;
+}): IndexNativeHealth {
+ const sqlite =
+ options.sqlitePath && existsSync(options.sqlitePath)
+ ? 'ready'
+ : canResolvePackage('better-sqlite3')
+ ? 'ready'
+ : 'unavailable';
+
+ let lancedb: IndexNativeHealth['lancedb'] = 'unknown';
+ if (options.lanceDbPath && existsSync(options.lanceDbPath)) {
+ lancedb = 'ready';
+ } else if (canResolvePackage('@lancedb/lancedb')) {
+ lancedb = 'ready';
+ } else {
+ lancedb = 'unavailable';
+ }
+
+ let onnx: IndexNativeHealth['onnx'] = 'unavailable';
+ if (canResolvePackage(ONNX_RUNTIME_NODE_PACKAGE)) {
+ onnx = 'native';
+ } else if (canResolvePackage(ONNX_RUNTIME_WEB_PACKAGE)) {
+ onnx = 'wasm';
+ }
+
+ return { sqlite, lancedb, onnx };
+}
+
+function resolveOverall(input: {
+ running: boolean;
+ meta: IndexRuntimeMetadata | undefined;
+ sqliteExists: boolean;
+ code: IndexPipelineEntry;
+ text: IndexPipelineEntry;
+ embeddings: IndexPipelineEntry;
+}): IndexOverallHealth {
+ if (input.running) return 'running';
+ if (!input.meta && !input.sqliteExists) return 'missing';
+
+ const lexicalOk =
+ (input.code.status === 'ready' || input.code.status === 'degraded') &&
+ (input.text.status === 'ready' || input.text.status === 'degraded');
+
+ if (!lexicalOk) {
+ if (
+ input.code.status === 'missing' &&
+ input.text.status === 'missing' &&
+ !input.sqliteExists
+ ) {
+ return 'missing';
+ }
+ return 'failed';
+ }
+
+ if (input.embeddings.status === 'ready') return 'ready';
+ if (
+ input.embeddings.status === 'running' ||
+ input.embeddings.status === 'pending'
+ ) {
+ return 'lexical_only';
+ }
+ // Embeddings disabled / not built / degraded — agent can still use FTS.
+ return 'lexical_only';
+}
+
+/**
+ * Read durable + live index pipeline health for a workspace.
+ */
+export function readIndexPipelineHealth(
+ options: ReadIndexPipelineHealthOptions,
+): IndexPipelineHealth {
+ const mitiiDir = options.mitiiDir ?? join(options.workspaceRoot, '.mitii');
+ const runtimePath = join(mitiiDir, INDEX_RUNTIME_FILE);
+ const meta = readIndexRuntimeMetadata(runtimePath);
+ const lock = isIndexLockHeld(mitiiDir);
+ const progress = lock.held ? readIndexProgress(mitiiDir) : undefined;
+ const running = lock.held;
+
+ const sqlitePath =
+ meta?.sqlitePath ?? join(mitiiDir, INDEX_DB_FILE);
+ const lanceDbPath = meta?.lanceDbPath ?? join(mitiiDir, LANCEDB_DIR);
+ const sqliteExists = existsSync(sqlitePath);
+
+ const codeIndex = deriveCodeIndex(meta, progress, running);
+ const textFts = deriveTextFts(meta, progress, running);
+ const embeddings = deriveEmbeddings(meta, progress, running);
+ const graph = deriveGraph(meta);
+ const map = deriveMap(meta);
+ const treeSitter = deriveTreeSitter(meta);
+
+ const probeNative = options.probeNative !== false;
+ const native = probeNative
+ ? probeNativeModules({ sqlitePath, lanceDbPath })
+ : { sqlite: 'unknown' as const, lancedb: 'unknown' as const, onnx: 'unknown' as const };
+
+ const overall = resolveOverall({
+ running,
+ meta,
+ sqliteExists,
+ code: codeIndex,
+ text: textFts,
+ embeddings,
+ });
+
+ const lastError = meta?.lastEmbeddingError;
+
+ return {
+ schemaVersion: INDEX_PIPELINE_HEALTH_SCHEMA_VERSION,
+ overall,
+ ...(meta?.workspaceId ? { workspaceId: meta.workspaceId } : {}),
+ ...(meta?.generatedAt ? { generatedAt: meta.generatedAt } : {}),
+ running,
+ ...(progress
+ ? {
+ progress: {
+ stage: progress.stage,
+ message: progress.message,
+ percent: progress.percent,
+ ...(progress.lexicalReady ? { lexicalReady: true } : {}),
+ ...(progress.embeddingPhase
+ ? { embeddingPhase: progress.embeddingPhase }
+ : {}),
+ },
+ }
+ : {}),
+ pipelines: {
+ codeIndex,
+ textFts,
+ embeddings,
+ graph,
+ map,
+ treeSitter,
+ },
+ native,
+ counts: {
+ files: meta?.fileCount ?? progress?.fileCount ?? 0,
+ truncated: Boolean(meta?.truncated),
+ },
+ paths: {
+ mitiiDir,
+ ...(sqliteExists || meta ? { sqlitePath } : {}),
+ ...(existsSync(lanceDbPath) || meta ? { lanceDbPath } : {}),
+ },
+ ...(lastError ? { lastError } : {}),
+ };
+}
+
+const PIPELINE_LABELS: Record = {
+ codeIndex: 'Code Index',
+ textFts: 'Text FTS5',
+ embeddings: 'Embeddings',
+ graph: 'Graph',
+ map: 'Map',
+ treeSitter: 'Tree-sitter',
+};
+
+function formatEntry(label: string, pipeline: IndexPipelineEntry): string {
+ const bits = [
+ pipeline.revision ? `revision=${pipeline.revision}` : undefined,
+ pipeline.profileId ? `profile=${pipeline.profileId}` : undefined,
+ pipeline.reason ? `reason=${pipeline.reason}` : undefined,
+ ].filter(Boolean);
+ return `${label}=${pipeline.status}${bits.length ? ` ${bits.join(' ')}` : ''}`;
+}
+
+/** Human-readable lines for CLI / logs. */
+export function formatIndexPipelineHealthLines(
+ health: IndexPipelineHealth,
+): string[] {
+ const lines: string[] = [
+ `indexHealth overall=${health.overall} files=${health.counts.files}${health.counts.truncated ? ' truncated' : ''}${health.running ? ' running' : ''}`,
+ ];
+ if (health.progress) {
+ lines.push(
+ `progress stage=${health.progress.stage} percent=${health.progress.percent}${health.progress.lexicalReady ? ' lexicalReady' : ''}${health.progress.embeddingPhase ? ` embedding=${health.progress.embeddingPhase}` : ''} — ${health.progress.message}`,
+ );
+ }
+ for (const key of Object.keys(PIPELINE_LABELS) as Array<
+ keyof typeof PIPELINE_LABELS
+ >) {
+ lines.push(formatEntry(PIPELINE_LABELS[key], health.pipelines[key]));
+ }
+ lines.push(
+ `native sqlite=${health.native.sqlite} lancedb=${health.native.lancedb} onnx=${health.native.onnx}`,
+ );
+ if (health.lastError) {
+ lines.push(`lastError=${health.lastError}`);
+ }
+ return lines;
+}
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index ba1776f8..e60a7b3a 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Mitii MCP client kit: connect to MCP servers (stdio/SSE/streamable-HTTP) and register tools into V8 ToolRegistry. Does not expose Mitii as an MCP server.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/web/package.json b/packages/mcp/web/package.json
index f19e77c1..2a9e8bbc 100644
--- a/packages/mcp/web/package.json
+++ b/packages/mcp/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp-web",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Mitii MCP stdio server under packages/mcp/web: web_search, fetch_url, optional memory_search via search-kit (no v8).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index e6c84015..ed98d1ca 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/sdk",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Host-neutral Mitii programmatic API over @mitii/v8 Agent Engine.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/search-kit/package.json b/packages/search-kit/package.json
index 10568c86..8932b0d2 100644
--- a/packages/search-kit/package.json
+++ b/packages/search-kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/search-kit",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Mitii web retrieval kit: pluggable search providers, content resolvers, and URL safety. Host-neutral; no V8 dependency.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/v8/package.json b/packages/v8/package.json
index e1eb6c2a..a8b6a147 100644
--- a/packages/v8/package.json
+++ b/packages/v8/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/v8",
- "version": "2.10.3",
+ "version": "2.10.4",
"description": "Host-neutral Mitii V8 agent runtime (modules + engine).",
"license": "AGPL-3.0-or-later",
"type": "module",
From fcb6cdf53e1fb367a889b4a22590ff80e8ee5704 Mon Sep 17 00:00:00 2001
From: codewithshinde
Date: Thu, 1 Oct 2026 16:29:26 -0500
Subject: [PATCH 20/22] feat(desktop): add index pipeline health logging and
settings error surfacing
Co-authored-by: Cursor
---
README.md | 2 +-
apps/acp/package.json | 2 +-
apps/cli/package.json | 6 +-
apps/cli/scripts/build-cli.cjs | 3 +
apps/daemon/package.json | 2 +-
apps/desktop/package.json | 6 +-
apps/desktop/scripts/build-engine.cjs | 3 +
.../src/engine/explorer/incrementalIndex.ts | 23 +-
apps/desktop/src/engine/index-status.ts | 98 ++++++-
apps/desktop/src/engine/server.ts | 44 +++-
apps/desktop/src/main/index.ts | 244 ++++++++++++++----
apps/desktop/src/renderer/App.tsx | 3 +
apps/desktop/src/renderer/SettingsPanel.tsx | 5 +-
apps/desktop/src/shared/project-logs.ts | 62 +++++
apps/desktop/tests/project-logs.spec.ts | 54 ++++
apps/vscode/package.json | 2 +-
package.json | 2 +-
packages/automation/package.json | 2 +-
packages/host/package.json | 2 +-
packages/mcp/package.json | 2 +-
packages/mcp/web/package.json | 2 +-
packages/sdk/package.json | 2 +-
packages/search-kit/package.json | 2 +-
packages/v8/package.json | 2 +-
.../adapters/sqlite/SqliteCodeIndexAdapter.ts | 76 +++++-
pnpm-lock.yaml | 18 +-
26 files changed, 567 insertions(+), 102 deletions(-)
create mode 100644 apps/desktop/tests/project-logs.spec.ts
diff --git a/README.md b/README.md
index 0727d4b6..5ead6e62 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-
+
diff --git a/apps/acp/package.json b/apps/acp/package.json
index dc5cab9b..69dacb3a 100644
--- a/apps/acp/package.json
+++ b/apps/acp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/acp",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii ACP-lite stdio bridge (Phase 3). Decision Policy remains authority; V8 does not import ACP.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 803b1605..ae91400d 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/cli",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii headless CLI over @mitii/sdk. Phase 0: --origin/--autonomy/--agent for CI automation.",
"license": "AGPL-3.0-or-later",
"publishConfig": {
@@ -33,7 +33,9 @@
"@mitii/mcp": "workspace:*",
"@mitii/sdk": "workspace:*",
"@mitii/v8": "workspace:*",
- "better-sqlite3": "^12.11.1"
+ "better-sqlite3": "^12.11.1",
+ "tree-sitter-wasms": "^0.1.13",
+ "web-tree-sitter": "^0.24.7"
},
"devDependencies": {
"@types/node": "^20.14.0",
diff --git a/apps/cli/scripts/build-cli.cjs b/apps/cli/scripts/build-cli.cjs
index d57e070d..a5574c43 100644
--- a/apps/cli/scripts/build-cli.cjs
+++ b/apps/cli/scripts/build-cli.cjs
@@ -15,6 +15,9 @@ const builtins = new Set([
const externals = new Set([
'@lancedb/lancedb',
'better-sqlite3',
+ // web-tree-sitter uses __dirname in Parser.init; bundling into ESM breaks it.
+ 'web-tree-sitter',
+ 'tree-sitter-wasms',
'typescript',
'vscode',
]);
diff --git a/apps/daemon/package.json b/apps/daemon/package.json
index a7f3ec1b..256d0c66 100644
--- a/apps/daemon/package.json
+++ b/apps/daemon/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/daemon",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii automation daemon process entry (Phase 1). Long-lived schedule runner.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 2362e6fc..14379825 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/desktop",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii Desktop — local coding agent with chat, settings, and repository index.",
"license": "AGPL-3.0-or-later",
"private": true,
@@ -45,7 +45,9 @@
"highlight.js": "^11.12.0",
"mermaid": "^11.17.2",
"react-markdown": "^9.0.3",
- "remark-gfm": "^4.0.0"
+ "remark-gfm": "^4.0.0",
+ "tree-sitter-wasms": "^0.1.13",
+ "web-tree-sitter": "^0.24.7"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.12",
diff --git a/apps/desktop/scripts/build-engine.cjs b/apps/desktop/scripts/build-engine.cjs
index 0b8e7d02..31684eb6 100644
--- a/apps/desktop/scripts/build-engine.cjs
+++ b/apps/desktop/scripts/build-engine.cjs
@@ -21,6 +21,9 @@ const builtins = new Set([
const externals = new Set([
'@lancedb/lancedb',
'better-sqlite3',
+ // web-tree-sitter uses __dirname in Parser.init; bundling into ESM breaks it.
+ 'web-tree-sitter',
+ 'tree-sitter-wasms',
'typescript',
'vscode',
'electron',
diff --git a/apps/desktop/src/engine/explorer/incrementalIndex.ts b/apps/desktop/src/engine/explorer/incrementalIndex.ts
index 9aedaa1e..8dbab5bf 100644
--- a/apps/desktop/src/engine/explorer/incrementalIndex.ts
+++ b/apps/desktop/src/engine/explorer/incrementalIndex.ts
@@ -11,6 +11,11 @@ import { isIndexLockHeld, type SemanticIndexSettings } from '@mitii/host';
import type { WorkspaceChangeEvent, WorkspaceWatcher } from './workspaceWatch.js';
import { reindexWorkspace } from '../index-status.js';
+import {
+ appendDesktopLog,
+ errorMessage,
+ resolveLogsDir,
+} from '../../shared/project-logs.js';
const INCREMENTAL_INDEX_DEBOUNCE_MS = 750;
const MAX_PATHS_PER_FLUSH = 400;
@@ -65,8 +70,22 @@ export function startIncrementalWorkspaceIndex(options: {
semanticIndex: options.resolveSemanticIndex(),
onProgress: options.onProgress,
});
- } catch {
- /* lock contention / transient — next FS event retries */
+ } catch (error) {
+ const message = errorMessage(error);
+ // Lock contention is expected; only log unexpected failures.
+ if (!/already running|index\.lock|IndexLocked/i.test(message)) {
+ appendDesktopLog(
+ resolveLogsDir(undefined, process.env) ??
+ join(options.workspaceRoot, '.mitii', 'logs'),
+ 'indexing',
+ `incremental_failed ${message}`,
+ {
+ level: 'error',
+ mirrorRuns: true,
+ extra: { pathCount: filePaths.length },
+ },
+ );
+ }
}
};
diff --git a/apps/desktop/src/engine/index-status.ts b/apps/desktop/src/engine/index-status.ts
index c401f09c..d500dde0 100644
--- a/apps/desktop/src/engine/index-status.ts
+++ b/apps/desktop/src/engine/index-status.ts
@@ -20,9 +20,21 @@ import {
} from '@mitii/host';
import Database from 'better-sqlite3';
+import {
+ appendDesktopLog,
+ errorMessage,
+ resolveLogsDir,
+} from '../shared/project-logs.js';
import { workspaceIdFromRoot } from './workspace-id.js';
import { openDesktopStore } from './desktop-store.js';
+function indexLogsDir(workspaceRoot: string): string | undefined {
+ return (
+ resolveLogsDir(undefined, process.env) ??
+ join(workspaceRoot, '.mitii', 'logs')
+ );
+}
+
export interface DesktopIndexStatus {
indexed: boolean;
fileCount: number;
@@ -202,6 +214,19 @@ export async function reindexWorkspace(options: {
try {
const workspaceId = workspaceIdFromRoot(options.workspaceRoot);
+ const logsDir = indexLogsDir(options.workspaceRoot);
+ const scoped = Boolean(options.filePaths?.length);
+ appendDesktopLog(logsDir, 'indexing', 'start', {
+ mirrorRuns: true,
+ extra: {
+ force: options.force === true,
+ scoped,
+ pathCount: options.filePaths?.length ?? 0,
+ maximumFiles: options.maximumFiles,
+ semanticEnabled: options.semanticIndex?.enabled === true,
+ },
+ });
+
const result = await runFullWorkspaceIndex({
mitiiDir,
workspaceRoot: options.workspaceRoot,
@@ -226,7 +251,18 @@ export async function reindexWorkspace(options: {
openDatabase: ((
filename: string,
openOptions?: { readonly?: boolean; fileMustExist?: boolean },
- ) => new Database(filename, openOptions)) as never,
+ ) => {
+ try {
+ return new Database(filename, openOptions);
+ } catch (error) {
+ appendDesktopLog(logsDir, 'sqlite', `open_failed ${errorMessage(error)}`, {
+ level: 'error',
+ mirrorRuns: true,
+ extra: { path: filename },
+ });
+ throw error;
+ }
+ }) as never,
});
// Record index meta on the Desktop-owned multi-repo store when available.
@@ -245,29 +281,54 @@ export async function reindexWorkspace(options: {
} finally {
store.close();
}
- } catch {
- /* non-fatal */
+ } catch (error) {
+ appendDesktopLog(
+ logsDir,
+ 'store',
+ `index_meta_write_failed ${errorMessage(error)}`,
+ {
+ level: 'warn',
+ extra: { storePath, workspaceRoot: options.workspaceRoot },
+ },
+ );
}
}
+ const message =
+ result.status === 'indexed'
+ ? `Indexed ${result.fileCount} files`
+ : result.status === 'unchanged'
+ ? 'Index unchanged'
+ : result.status === 'skipped'
+ ? `Skipped${result.skipReason ? ` (${result.skipReason})` : ''}`
+ : result.status === 'cancelled'
+ ? 'Index paused'
+ : `Index ${result.status}`;
+
+ appendDesktopLog(logsDir, 'indexing', `finished ${result.status}`, {
+ level: result.status === 'cancelled' ? 'warn' : 'info',
+ mirrorRuns: true,
+ extra: {
+ fileCount: result.fileCount,
+ truncated: result.truncated,
+ message,
+ },
+ });
+
return {
status: result.status,
fileCount: result.fileCount,
truncated: result.truncated,
- message:
- result.status === 'indexed'
- ? `Indexed ${result.fileCount} files`
- : result.status === 'unchanged'
- ? 'Index unchanged'
- : result.status === 'skipped'
- ? `Skipped${result.skipReason ? ` (${result.skipReason})` : ''}`
- : result.status === 'cancelled'
- ? 'Index paused'
- : `Index ${result.status}`,
+ message,
};
} catch (error) {
+ const logsDir = indexLogsDir(options.workspaceRoot);
if (error instanceof IndexLockedError) {
const status = getIndexStatus(options.workspaceRoot);
+ appendDesktopLog(logsDir, 'indexing', 'skipped_locked', {
+ level: 'warn',
+ mirrorRuns: true,
+ });
return {
status: 'skipped',
fileCount: status.fileCount,
@@ -277,8 +338,12 @@ export async function reindexWorkspace(options: {
'Indexing already running — watch the header icon for progress.',
};
}
- const message = error instanceof Error ? error.message : String(error);
+ const message = errorMessage(error);
if (controller.signal.aborted || /cancell?ed/i.test(message)) {
+ appendDesktopLog(logsDir, 'indexing', 'cancelled', {
+ level: 'warn',
+ mirrorRuns: true,
+ });
return {
status: 'cancelled',
fileCount: 0,
@@ -286,6 +351,11 @@ export async function reindexWorkspace(options: {
message: 'Index paused',
};
}
+ appendDesktopLog(logsDir, 'indexing', `failed ${message}`, {
+ level: 'error',
+ mirrorRuns: true,
+ extra: { workspaceRoot: options.workspaceRoot },
+ });
throw error;
} finally {
if (options.abortSignal) {
diff --git a/apps/desktop/src/engine/server.ts b/apps/desktop/src/engine/server.ts
index 556205ed..fbda8663 100644
--- a/apps/desktop/src/engine/server.ts
+++ b/apps/desktop/src/engine/server.ts
@@ -83,7 +83,9 @@ import {
import { generateEngineToken } from '../shared/engine-token.js';
import { isAllowedEngineBaseUrl } from '../shared/window-url-policy.js';
import {
+ appendDesktopLog,
appendRunLog,
+ errorMessage,
resolveLogsDir,
} from '../shared/project-logs.js';
import {
@@ -2361,10 +2363,17 @@ export async function startEngineServer(
statusSnapshot: getIndexStatus(cwd),
});
} catch (error) {
+ const message = errorMessage(error);
+ appendDesktopLog(
+ resolveLogsDir(undefined, process.env) ??
+ join(cwd, '.mitii', 'logs'),
+ 'indexing',
+ `reindex_stream_failed ${message}`,
+ { level: 'error', mirrorRuns: true },
+ );
writeLine({
type: 'error',
- message:
- error instanceof Error ? error.message : String(error),
+ message,
statusSnapshot: getIndexStatus(cwd),
});
}
@@ -2383,8 +2392,14 @@ export async function startEngineServer(
});
sendJson(res, 200, { ...result, statusSnapshot: getIndexStatus(cwd) });
} catch (error) {
- const message =
- error instanceof Error ? error.message : String(error);
+ const message = errorMessage(error);
+ appendDesktopLog(
+ resolveLogsDir(undefined, process.env) ??
+ join(cwd, '.mitii', 'logs'),
+ 'indexing',
+ `reindex_failed ${message}`,
+ { level: 'error', mirrorRuns: true },
+ );
sendJson(res, 500, {
op: 'error',
error: 'internal',
@@ -2495,7 +2510,26 @@ export async function startEngineServer(
sendJson(res, 404, { op: 'error', error: 'not_found' });
})().catch((error) => {
- const message = error instanceof Error ? error.message : String(error);
+ const message = errorMessage(error);
+ const logsDir =
+ resolveLogsDir(undefined, process.env) ?? join(cwd, '.mitii', 'logs');
+ const failedMethod = req.method ?? 'GET';
+ let failedPath = '/';
+ try {
+ failedPath = new URL(req.url ?? '/', `http://${host}`).pathname;
+ } catch {
+ failedPath = req.url ?? '/';
+ }
+ appendDesktopLog(
+ logsDir,
+ 'engine',
+ `route_failed ${failedMethod} ${failedPath} ${message}`,
+ {
+ level: 'error',
+ mirrorRuns: true,
+ extra: { method: failedMethod, path: failedPath },
+ },
+ );
if (!res.headersSent) {
sendJson(res, 500, { op: 'error', error: 'internal', message });
} else {
diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts
index 2fc9a0cb..d8c15ef3 100644
--- a/apps/desktop/src/main/index.ts
+++ b/apps/desktop/src/main/index.ts
@@ -72,6 +72,10 @@ import {
reconcileMcpSettingsFromDisk,
writeWorkspaceCompatFiles,
} from './workspace-config.js';
+import {
+ appendDesktopLog,
+ errorMessage,
+} from '../shared/project-logs.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const distRoot = join(__dirname, '..');
@@ -84,6 +88,24 @@ let state: DesktopPersistedState;
let userDataPath = '';
let store: DesktopStoreClient;
+function desktopLogsDir(workspaceRoot?: string): string {
+ const root =
+ workspaceRoot?.trim() ||
+ state?.workspaceRoot?.trim() ||
+ process.env.MITII_DESKTOP_CWD?.trim() ||
+ process.cwd();
+ return getStorageInfo(root).logsPath;
+}
+
+function logDesktop(
+ category: Parameters[1],
+ message: string,
+ options?: Parameters[3],
+ workspaceRoot?: string,
+): void {
+ appendDesktopLog(desktopLogsDir(workspaceRoot), category, message, options);
+}
+
async function stopEngine(): Promise {
if (engine) {
await engine.stop();
@@ -107,15 +129,27 @@ async function startEngine(): Promise {
if (forceEcho) env.MITII_FORCE_ECHO = '1';
env.MITII_DESKTOP_STORE_PATH = store.dbPath;
- const logsPath = getStorageInfo(state.workspaceRoot || process.cwd()).logsPath;
- engine = await spawnDesktopEngine({
- cwd: state.workspaceRoot,
- forceEcho,
- token: engineToken,
- env,
- logsPath,
- });
- hostMode = forceEcho ? 'echo' : 'host';
+ const logsPath = desktopLogsDir();
+ try {
+ engine = await spawnDesktopEngine({
+ cwd: state.workspaceRoot,
+ forceEcho,
+ token: engineToken,
+ env,
+ logsPath,
+ });
+ hostMode = forceEcho ? 'echo' : 'host';
+ logDesktop('engine', `started url=${engine.url} mode=${hostMode}`, {
+ extra: { cwd: state.workspaceRoot },
+ });
+ } catch (error) {
+ logDesktop('engine', `start_failed ${errorMessage(error)}`, {
+ level: 'error',
+ extra: { cwd: state.workspaceRoot, dbPath: store.dbPath },
+ mirrorRuns: true,
+ });
+ throw error;
+ }
}
function snapshot(): DesktopShellSnapshot {
@@ -210,18 +244,23 @@ function registerIpc(): void {
try {
if (path === null || path === '') {
writeAppDataRedirect(null);
+ logDesktop('storage', 'app_data_redirect_cleared');
return { ok: true, restartRequired: true };
}
if (typeof path !== 'string' || !path.trim()) {
return { ok: false, reason: 'invalid_path' };
}
writeAppDataRedirect(path.trim());
+ logDesktop('storage', 'app_data_redirect_set', {
+ extra: { path: path.trim() },
+ });
return { ok: true, restartRequired: true };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop('storage', `app_data_redirect_failed ${reason}`, {
+ level: 'error',
+ });
+ return { ok: false, reason };
}
});
@@ -232,6 +271,7 @@ function registerIpc(): void {
if (state.workspaceRoot) {
ensureWorkspaceStorageLink(state.workspaceRoot);
}
+ logDesktop('storage', 'root_storage_cleared');
return { ok: true };
}
if (typeof path !== 'string' || !path.trim()) {
@@ -241,12 +281,16 @@ function registerIpc(): void {
if (state.workspaceRoot) {
ensureWorkspaceStorageLink(state.workspaceRoot);
}
+ logDesktop('storage', 'root_storage_set', {
+ extra: { path: path.trim() },
+ });
return { ok: true };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop('storage', `root_storage_failed ${reason}`, {
+ level: 'error',
+ });
+ return { ok: false, reason };
}
});
@@ -357,17 +401,22 @@ function registerIpc(): void {
writeWorkspaceCompatFiles(root, defaults, { replaceMcp: true });
ensureWorkspaceStorageLink(root);
await startEngine();
+ logDesktop('storage', 'workspace_cache_cleared', {
+ extra: { workspaceRoot: root, removed: cleared.removed },
+ });
return { ok: true, removed: cleared.removed };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop('storage', `clear_cache_failed ${reason}`, {
+ level: 'error',
+ });
+ return { ok: false, reason };
}
});
ipcMain.handle('mitii:save-settings', async (_event, input: unknown) => {
if (!input || typeof input !== 'object') {
+ logDesktop('settings', 'save_failed invalid_payload', { level: 'error' });
return { ok: false, reason: 'invalid_payload' };
}
const record = input as {
@@ -377,7 +426,10 @@ function registerIpc(): void {
searchApiKey?: string;
clearSearchApiKey?: boolean;
};
- if (!record.settings) return { ok: false, reason: 'missing_settings' };
+ if (!record.settings) {
+ logDesktop('settings', 'save_failed missing_settings', { level: 'error' });
+ return { ok: false, reason: 'missing_settings' };
+ }
try {
const previous = state.settings;
let next = mergeDesktopSettings(record.settings);
@@ -400,7 +452,15 @@ function registerIpc(): void {
next = reconcileMcpSettingsFromDisk(state.workspaceRoot, next);
state.settings = next;
store.saveSettings(state.workspaceRoot, state.settings);
- writeWorkspaceCompatFiles(state.workspaceRoot, state.settings);
+ try {
+ writeWorkspaceCompatFiles(state.workspaceRoot, state.settings);
+ } catch (compatError) {
+ logDesktop(
+ 'settings',
+ `compat_write_failed ${errorMessage(compatError)}`,
+ { level: 'warn', extra: { workspaceRoot: state.workspaceRoot } },
+ );
+ }
if (record.clearApiKey) clearStoredApiKey(userDataPath);
else if (typeof record.apiKey === 'string' && record.apiKey.trim()) {
writeStoredApiKey(userDataPath, record.apiKey);
@@ -415,12 +475,24 @@ function registerIpc(): void {
if (restart) {
await startEngine();
}
+ logDesktop('settings', 'save_ok', {
+ extra: {
+ workspaceRoot: state.workspaceRoot,
+ restarted: restart,
+ providerType: next.provider.type,
+ },
+ });
return { ok: true, restarted: restart };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop('settings', `save_failed ${reason}`, {
+ level: 'error',
+ extra: {
+ workspaceRoot: state.workspaceRoot,
+ dbPath: store.dbPath,
+ },
+ });
+ return { ok: false, reason };
}
});
@@ -429,10 +501,9 @@ function registerIpc(): void {
await startEngine();
return { ok: true };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop('engine', `restart_failed ${reason}`, { level: 'error' });
+ return { ok: false, reason };
}
});
}
@@ -451,8 +522,13 @@ async function applyWorkspace(
store.saveSettings(workspaceRoot, settings);
try {
writeWorkspaceCompatFiles(workspaceRoot, settings);
- } catch {
- /* ignore */
+ } catch (compatError) {
+ logDesktop(
+ 'workspace',
+ `compat_write_failed ${errorMessage(compatError)}`,
+ { level: 'warn' },
+ workspaceRoot,
+ );
}
syncIndexMetaFromDisk(workspaceRoot);
state = stateFromStoreSnapshot(
@@ -460,12 +536,22 @@ async function applyWorkspace(
workspaceRoot,
);
await startEngine();
+ logDesktop('workspace', 'applied', {
+ extra: { workspaceRoot },
+ }, workspaceRoot);
return { ok: true, workspaceRoot };
} catch (error) {
- return {
- ok: false,
- reason: error instanceof Error ? error.message : String(error),
- };
+ const reason = errorMessage(error);
+ logDesktop(
+ 'workspace',
+ `apply_failed ${reason}`,
+ {
+ level: 'error',
+ extra: { workspaceRoot, dbPath: store.dbPath },
+ },
+ workspaceRoot,
+ );
+ return { ok: false, reason };
}
}
@@ -485,8 +571,13 @@ function syncIndexMetaFromDisk(workspaceRoot: string): void {
? raw.generatedAt
: new Date().toISOString(),
});
- } catch {
- /* ignore */
+ } catch (error) {
+ logDesktop(
+ 'sqlite',
+ `index_meta_sync_failed ${errorMessage(error)}`,
+ { level: 'warn', extra: { workspaceRoot } },
+ workspaceRoot,
+ );
}
}
@@ -503,29 +594,54 @@ async function boot(): Promise {
workspaceRoot: fallbackCwd,
});
state = stateFromStoreSnapshot(store.snapshot(fallbackCwd), fallbackCwd);
+ logDesktop('store', 'migrate_ok', {
+ extra: { dbPath: store.dbPath, workspaceRoot: state.workspaceRoot },
+ }, fallbackCwd);
} catch (error) {
+ const message = errorMessage(error);
console.error(
- `[mitii-desktop] store migrate failed, using defaults: ${
- error instanceof Error ? error.message : String(error)
- }`,
+ `[mitii-desktop] store migrate failed, using defaults: ${message}`,
+ );
+ logDesktop(
+ 'store',
+ `migrate_failed ${message}`,
+ {
+ level: 'error',
+ extra: { dbPath: store.dbPath, workspaceRoot: fallbackCwd },
+ },
+ fallbackCwd,
);
state = defaultDesktopState(fallbackCwd);
}
if (state.workspaceRoot) {
- let settings = store.getWorkspaceSettings(state.workspaceRoot);
- if (!settings) {
- settings = loadWorkspaceSettings(state.workspaceRoot, state.settings);
- }
- settings = reconcileMcpSettingsFromDisk(state.workspaceRoot, settings);
- store.saveSettings(state.workspaceRoot, settings);
- state.settings = settings;
try {
- writeWorkspaceCompatFiles(state.workspaceRoot, state.settings);
- } catch {
- /* ignore */
+ let settings = store.getWorkspaceSettings(state.workspaceRoot);
+ if (!settings) {
+ settings = loadWorkspaceSettings(state.workspaceRoot, state.settings);
+ }
+ settings = reconcileMcpSettingsFromDisk(state.workspaceRoot, settings);
+ store.saveSettings(state.workspaceRoot, settings);
+ state.settings = settings;
+ try {
+ writeWorkspaceCompatFiles(state.workspaceRoot, state.settings);
+ } catch (compatError) {
+ logDesktop(
+ 'settings',
+ `compat_write_failed ${errorMessage(compatError)}`,
+ { level: 'warn' },
+ );
+ }
+ syncIndexMetaFromDisk(state.workspaceRoot);
+ } catch (error) {
+ logDesktop('sqlite', `workspace_load_failed ${errorMessage(error)}`, {
+ level: 'error',
+ extra: {
+ workspaceRoot: state.workspaceRoot,
+ dbPath: store.dbPath,
+ },
+ });
}
- syncIndexMetaFromDisk(state.workspaceRoot);
}
await startEngine();
@@ -538,6 +654,14 @@ async function boot(): Promise {
devServerUrl: process.env.MITII_DESKTOP_DEV_SERVER,
});
+ logDesktop('boot', 'ready', {
+ extra: {
+ store: store.dbPath,
+ engine: engine?.url,
+ cwd: state.workspaceRoot,
+ logsPath: desktopLogsDir(),
+ },
+ });
console.error(
`[mitii-desktop] store=${store.dbPath} engine=${engine?.url} cwd=${state.workspaceRoot}`,
);
@@ -567,8 +691,20 @@ if (!gotLock) {
app.whenReady().then(() => {
void boot().catch((error) => {
- const message = error instanceof Error ? error.message : String(error);
+ const message = errorMessage(error);
console.error(`[mitii-desktop] boot failed: ${message}`);
+ try {
+ appendDesktopLog(
+ getStorageInfo(
+ process.env.MITII_DESKTOP_CWD?.trim() || process.cwd(),
+ ).logsPath,
+ 'boot',
+ `failed ${message}`,
+ { level: 'error' },
+ );
+ } catch {
+ /* ignore */
+ }
app.exit(1);
});
});
diff --git a/apps/desktop/src/renderer/App.tsx b/apps/desktop/src/renderer/App.tsx
index 59f54e0d..9a42b548 100644
--- a/apps/desktop/src/renderer/App.tsx
+++ b/apps/desktop/src/renderer/App.tsx
@@ -1804,6 +1804,9 @@ export function App() {
);
await wait;
}
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ setError(`Settings save failed: ${message}`);
} finally {
setSettingsBusy(false);
}
diff --git a/apps/desktop/src/renderer/SettingsPanel.tsx b/apps/desktop/src/renderer/SettingsPanel.tsx
index b50900d6..a18022b0 100644
--- a/apps/desktop/src/renderer/SettingsPanel.tsx
+++ b/apps/desktop/src/renderer/SettingsPanel.tsx
@@ -1613,7 +1613,10 @@ export function SettingsPanel(props: SettingsPanelProps) {
Chat session JSONL (same as VS Code):{' '}
MM-DD-YYYY-HH-MM-thread_….jsonl. Also{' '}
- engine.log / runs.log.
+ engine.log / runs.log, plus
+ dated product errors in{' '}
+ desktop-YYYY-MM-DD.log (settings, SQLite,
+ indexing, boot).
{storage?.logsPath ?? '…'}
diff --git a/apps/desktop/src/shared/project-logs.ts b/apps/desktop/src/shared/project-logs.ts
index a172278c..ffc7d8b5 100644
--- a/apps/desktop/src/shared/project-logs.ts
+++ b/apps/desktop/src/shared/project-logs.ts
@@ -1,10 +1,30 @@
/**
* Append-only project logs under layout.logsPath (Root/projects/…/logs).
+ *
+ * Files:
+ * - engine.log — engine process stdout/stderr
+ * - runs.log — agent run lifecycle
+ * - desktop-YYYY-MM-DD.log — product failures (store, settings, index, boot)
*/
import { appendFileSync, mkdirSync } from 'node:fs';
import { join } from 'node:path';
+export type DesktopLogLevel = 'info' | 'warn' | 'error';
+
+/** Categories for desktop product events (date-stamped log). */
+export type DesktopLogCategory =
+ | 'boot'
+ | 'store'
+ | 'sqlite'
+ | 'settings'
+ | 'indexing'
+ | 'engine'
+ | 'ipc'
+ | 'workspace'
+ | 'storage'
+ | 'general';
+
export function resolveLogsDir(
explicit?: string,
env: NodeJS.ProcessEnv = process.env,
@@ -15,6 +35,14 @@ export function resolveLogsDir(
return fromEnv || undefined;
}
+/** `desktop-2026-10-01.log` — one file per calendar day (UTC date). */
+export function desktopLogFileName(date: Date = new Date()): string {
+ const y = date.getUTCFullYear();
+ const m = String(date.getUTCMonth() + 1).padStart(2, '0');
+ const d = String(date.getUTCDate()).padStart(2, '0');
+ return `desktop-${y}-${m}-${d}.log`;
+}
+
export function appendProjectLog(
logsDir: string | undefined,
fileName: string,
@@ -55,3 +83,37 @@ export function appendRunLog(
: message;
appendProjectLog(logsDir, 'runs.log', payload);
}
+
+/**
+ * Date-stamped desktop product log for store/settings/index/boot failures.
+ * Also mirrored lightly into `runs.log` for indexing so the run timeline stays complete.
+ */
+export function appendDesktopLog(
+ logsDir: string | undefined,
+ category: DesktopLogCategory,
+ message: string,
+ options?: {
+ level?: DesktopLogLevel;
+ extra?: Record;
+ /** Also append a short line to runs.log (indexing / engine). */
+ mirrorRuns?: boolean;
+ },
+): void {
+ const level = options?.level ?? 'info';
+ const extra = options?.extra;
+ const payload =
+ extra && Object.keys(extra).length > 0
+ ? `${message} ${JSON.stringify(extra)}`
+ : message;
+ const line = `${level.toUpperCase()} [${category}] ${payload}`;
+ appendProjectLog(logsDir, desktopLogFileName(), line);
+ if (options?.mirrorRuns) {
+ appendRunLog(logsDir, `desktop_${category} ${payload}`);
+ }
+}
+
+/** Resolve error message without throwing. */
+export function errorMessage(error: unknown): string {
+ if (error instanceof Error) return error.message;
+ return String(error);
+}
diff --git a/apps/desktop/tests/project-logs.spec.ts b/apps/desktop/tests/project-logs.spec.ts
new file mode 100644
index 00000000..32e81d69
--- /dev/null
+++ b/apps/desktop/tests/project-logs.spec.ts
@@ -0,0 +1,54 @@
+import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+
+import { afterEach, describe, expect, it } from 'vitest';
+
+import {
+ appendDesktopLog,
+ desktopLogFileName,
+ errorMessage,
+} from '../src/shared/project-logs.js';
+
+const dirs: string[] = [];
+
+afterEach(() => {
+ for (const dir of dirs.splice(0)) {
+ rmSync(dir, { recursive: true, force: true });
+ }
+});
+
+describe('project-logs desktop date files', () => {
+ it('names files by UTC calendar day', () => {
+ expect(desktopLogFileName(new Date('2026-10-01T15:30:00.000Z'))).toBe(
+ 'desktop-2026-10-01.log',
+ );
+ });
+
+ it('appends categorized lines into desktop-YYYY-MM-DD.log', () => {
+ const dir = mkdtempSync(join(tmpdir(), 'mitii-desktop-logs-'));
+ dirs.push(dir);
+
+ appendDesktopLog(dir, 'settings', 'save_failed boom', {
+ level: 'error',
+ extra: { workspaceRoot: '/tmp/demo' },
+ });
+ appendDesktopLog(dir, 'indexing', 'start', {
+ mirrorRuns: true,
+ extra: { force: true },
+ });
+
+ const desktop = readFileSync(join(dir, desktopLogFileName()), 'utf8');
+ expect(desktop).toMatch(/ERROR \[settings\] save_failed boom/);
+ expect(desktop).toMatch(/\/tmp\/demo/);
+ expect(desktop).toMatch(/INFO \[indexing\] start/);
+
+ const runs = readFileSync(join(dir, 'runs.log'), 'utf8');
+ expect(runs).toMatch(/desktop_indexing start/);
+ });
+
+ it('errorMessage handles Error and primitives', () => {
+ expect(errorMessage(new Error('x'))).toBe('x');
+ expect(errorMessage('y')).toBe('y');
+ });
+});
diff --git a/apps/vscode/package.json b/apps/vscode/package.json
index 5156a765..84e445a8 100644
--- a/apps/vscode/package.json
+++ b/apps/vscode/package.json
@@ -2,7 +2,7 @@
"name": "mitii-ai-agent",
"displayName": "Mitii AI Agent",
"description": "Local-first VS Code AI coding agent with repository-aware context and controlled execution",
- "version": "2.10.4",
+ "version": "2.10.5",
"publisher": "mitii",
"license": "AGPL-3.0-or-later",
"icon": "media/mitii-logo.png",
diff --git a/package.json b/package.json
index a3cca8f7..eb3408a1 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mitii-ai-agent",
"description": "Private Mitii monorepo workspace orchestrator. Product packages: @mitii/v8, @mitii/sdk, @mitii/automation, @mitii/search-kit, @mitii/mcp, @mitii/mcp-web, @mitii/mcp-sqlite, @mitii/mcp-postgres, @mitii/mcp-mongo, @mitii/mcp-sql, @mitii/host, @mitii/cli, @mitii/daemon, @mitii/acp, @mitii/desktop, apps/vscode.",
- "version": "2.10.4",
+ "version": "2.10.5",
"private": true,
"license": "AGPL-3.0-or-later",
"author": {
diff --git a/packages/automation/package.json b/packages/automation/package.json
index c136eede..3d3df0c3 100644
--- a/packages/automation/package.json
+++ b/packages/automation/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/automation",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii automation control plane: schedules, event ingress, claim/lease runner, webhooks (Phases 1–2).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/host/package.json b/packages/host/package.json
index bc18ffaf..d78bfa91 100644
--- a/packages/host/package.json
+++ b/packages/host/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/host",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Shared host kit for Mitii apps: SQLite injection, workspace indexing, repository context, durable ports (checkpoints/memory/skills/search/network), project rules, provider presets. Web retrieval via @mitii/search-kit.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index e60a7b3a..af718435 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii MCP client kit: connect to MCP servers (stdio/SSE/streamable-HTTP) and register tools into V8 ToolRegistry. Does not expose Mitii as an MCP server.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/web/package.json b/packages/mcp/web/package.json
index 2a9e8bbc..7ad7f31e 100644
--- a/packages/mcp/web/package.json
+++ b/packages/mcp/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp-web",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii MCP stdio server under packages/mcp/web: web_search, fetch_url, optional memory_search via search-kit (no v8).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index ed98d1ca..44ac9638 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/sdk",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Host-neutral Mitii programmatic API over @mitii/v8 Agent Engine.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/search-kit/package.json b/packages/search-kit/package.json
index 8932b0d2..d289ce32 100644
--- a/packages/search-kit/package.json
+++ b/packages/search-kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/search-kit",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Mitii web retrieval kit: pluggable search providers, content resolvers, and URL safety. Host-neutral; no V8 dependency.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/v8/package.json b/packages/v8/package.json
index a8b6a147..c4b68525 100644
--- a/packages/v8/package.json
+++ b/packages/v8/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/v8",
- "version": "2.10.4",
+ "version": "2.10.5",
"description": "Host-neutral Mitii V8 agent runtime (modules + engine).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/v8/src/modules/repository-state/internal/code-index/adapters/sqlite/SqliteCodeIndexAdapter.ts b/packages/v8/src/modules/repository-state/internal/code-index/adapters/sqlite/SqliteCodeIndexAdapter.ts
index 3a17bec1..cbf44de6 100644
--- a/packages/v8/src/modules/repository-state/internal/code-index/adapters/sqlite/SqliteCodeIndexAdapter.ts
+++ b/packages/v8/src/modules/repository-state/internal/code-index/adapters/sqlite/SqliteCodeIndexAdapter.ts
@@ -18,6 +18,7 @@ import {
codeIndexFileQueryResultSchema,
codeIndexImportSchema,
codeIndexReferenceSchema,
+ codeIndexRelativePathSchema,
codeIndexSymbolQuerySchema,
codeIndexSymbolSchema,
} from "../../schema";
@@ -507,11 +508,9 @@ export class SqliteCodeIndexAdapter
}
const candidatePath =
- row.targetRelativePath
- ? this.normalizePath(
- row.targetRelativePath,
- )
- : undefined;
+ this._toCanonicalRelativePath(
+ row.targetRelativePath,
+ );
const targetIsCurrent =
row.targetFileId !== null &&
@@ -1144,6 +1143,30 @@ export class SqliteCodeIndexAdapter
.replace(/\/+$/, "");
}
+ /**
+ * Returns a schema-safe workspace-relative path, or undefined when the
+ * stored value is absolute / non-canonical (e.g. `/tmp/foo` shell scripts).
+ * Graph build must not fail the whole index on those rows.
+ */
+ private _toCanonicalRelativePath(
+ value: string | null | undefined,
+ ): string | undefined {
+ if (!value) {
+ return undefined;
+ }
+
+ const normalized =
+ this.normalizePath(value);
+ const parsed =
+ codeIndexRelativePathSchema.safeParse(
+ normalized,
+ );
+
+ return parsed.success
+ ? parsed.data
+ : undefined;
+ }
+
private isWithinFolder(
relativePath: string,
folderPrefix: string,
@@ -1225,8 +1248,12 @@ export class SqliteCodeIndexAdapter
CodeIndexError["operation"],
cause: unknown,
): CodeIndexError {
+ const causeMessage =
+ this.formatCauseMessage(cause);
return new CodeIndexError(
- message,
+ causeMessage
+ ? `${message} ${causeMessage}`
+ : message,
{
operation,
adapterId: this.id,
@@ -1235,6 +1262,43 @@ export class SqliteCodeIndexAdapter
);
}
+ private formatCauseMessage(
+ cause: unknown,
+ ): string | undefined {
+ if (!cause) {
+ return undefined;
+ }
+
+ if (cause instanceof Error) {
+ const zodIssues = (
+ cause as Error & {
+ issues?: ReadonlyArray<{
+ path: ReadonlyArray<
+ string | number
+ >;
+ message: string;
+ }>;
+ }
+ ).issues;
+
+ if (
+ Array.isArray(zodIssues) &&
+ zodIssues.length > 0
+ ) {
+ const first = zodIssues[0]!;
+ const path =
+ first.path.length > 0
+ ? `${first.path.join(".")}: `
+ : "";
+ return `(${path}${first.message})`;
+ }
+
+ return `(${cause.message})`;
+ }
+
+ return `(${String(cause)})`;
+ }
+
private isAbortError(
error: unknown,
): error is Error {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fb02e667..6c0ef149 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -72,6 +72,12 @@ importers:
better-sqlite3:
specifier: ^12.11.1
version: 12.11.1
+ tree-sitter-wasms:
+ specifier: ^0.1.13
+ version: 0.1.13
+ web-tree-sitter:
+ specifier: ^0.24.7
+ version: 0.24.7
devDependencies:
'@types/node':
specifier: ^20.14.0
@@ -150,6 +156,12 @@ importers:
remark-gfm:
specifier: ^4.0.0
version: 4.0.1
+ tree-sitter-wasms:
+ specifier: ^0.1.13
+ version: 0.1.13
+ web-tree-sitter:
+ specifier: ^0.24.7
+ version: 0.24.7
devDependencies:
'@types/better-sqlite3':
specifier: ^7.6.12
@@ -9340,8 +9352,7 @@ snapshots:
dependencies:
punycode: 2.3.1
- tree-sitter-wasms@0.1.13:
- optional: true
+ tree-sitter-wasms@0.1.13: {}
trim-lines@3.0.1: {}
@@ -9563,8 +9574,7 @@ snapshots:
web-streams-polyfill@4.0.0-beta.3:
optional: true
- web-tree-sitter@0.24.7:
- optional: true
+ web-tree-sitter@0.24.7: {}
webidl-conversions@3.0.1:
optional: true
From 9412ea4b164e89194aed9d24729a9aeda75dfff0 Mon Sep 17 00:00:00 2001
From: codewithshinde
Date: Thu, 1 Oct 2026 16:29:56 -0500
Subject: [PATCH 21/22] docs: point orchestration ownership at v8-engine
Co-authored-by: Cursor
---
README.md | 2 +-
apps/acp/package.json | 2 +-
apps/cli/package.json | 2 +-
apps/daemon/package.json | 2 +-
apps/desktop/package.json | 2 +-
apps/vscode/package.json | 2 +-
package.json | 2 +-
packages/automation/package.json | 2 +-
packages/host/package.json | 2 +-
packages/mcp/package.json | 2 +-
packages/mcp/web/package.json | 2 +-
packages/sdk/package.json | 2 +-
packages/search-kit/package.json | 2 +-
packages/v8/ARCHITECTURE.md | 4 ++--
packages/v8/package.json | 2 +-
15 files changed, 16 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index 6fd1fd51..e9ba2c16 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-
+
diff --git a/apps/acp/package.json b/apps/acp/package.json
index 92396e19..100aaf51 100644
--- a/apps/acp/package.json
+++ b/apps/acp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/acp",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii ACP-lite stdio bridge (Phase 3). Decision Policy remains authority; V8 does not import ACP.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/cli/package.json b/apps/cli/package.json
index e06c0008..7d2e856c 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/cli",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii headless CLI over @mitii/sdk. Phase 0: --origin/--autonomy/--agent for CI automation.",
"license": "AGPL-3.0-or-later",
"publishConfig": {
diff --git a/apps/daemon/package.json b/apps/daemon/package.json
index 1bc86987..7501a95e 100644
--- a/apps/daemon/package.json
+++ b/apps/daemon/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/daemon",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii automation daemon process entry (Phase 1). Long-lived schedule runner.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index ed11278f..8e17a8a8 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/desktop",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii Desktop — local coding agent with chat, settings, and repository index.",
"license": "AGPL-3.0-or-later",
"private": true,
diff --git a/apps/vscode/package.json b/apps/vscode/package.json
index d26acef6..ed4b46c9 100644
--- a/apps/vscode/package.json
+++ b/apps/vscode/package.json
@@ -2,7 +2,7 @@
"name": "mitii-ai-agent",
"displayName": "Mitii AI Agent",
"description": "Local-first VS Code AI coding agent with repository-aware context and controlled execution",
- "version": "2.10.6",
+ "version": "2.10.7",
"publisher": "mitii",
"license": "AGPL-3.0-or-later",
"icon": "media/mitii-logo.png",
diff --git a/package.json b/package.json
index 3fd3a5ef..fffd7525 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mitii-ai-agent",
"description": "Private Mitii monorepo workspace orchestrator. Product packages: @mitii/v8, @mitii/sdk, @mitii/automation, @mitii/search-kit, @mitii/mcp, @mitii/mcp-web, @mitii/mcp-sqlite, @mitii/mcp-postgres, @mitii/mcp-mongo, @mitii/mcp-sql, @mitii/host, @mitii/cli, @mitii/daemon, @mitii/acp, @mitii/desktop, apps/vscode.",
- "version": "2.10.6",
+ "version": "2.10.7",
"private": true,
"license": "AGPL-3.0-or-later",
"author": {
diff --git a/packages/automation/package.json b/packages/automation/package.json
index 7a2176bb..b9bf5a0f 100644
--- a/packages/automation/package.json
+++ b/packages/automation/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/automation",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii automation control plane: schedules, event ingress, claim/lease runner, webhooks (Phases 1–2).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/host/package.json b/packages/host/package.json
index 3fcb2de9..020a1c08 100644
--- a/packages/host/package.json
+++ b/packages/host/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/host",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Shared host kit for Mitii apps: SQLite injection, workspace indexing, repository context, durable ports (checkpoints/memory/skills/search/network), project rules, provider presets. Web retrieval via @mitii/search-kit.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index 8748f510..b1fe36b2 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii MCP client kit: connect to MCP servers (stdio/SSE/streamable-HTTP) and register tools into V8 ToolRegistry. Does not expose Mitii as an MCP server.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/web/package.json b/packages/mcp/web/package.json
index c8192ba0..f4fe1a6f 100644
--- a/packages/mcp/web/package.json
+++ b/packages/mcp/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp-web",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii MCP stdio server under packages/mcp/web: web_search, fetch_url, optional memory_search via search-kit (no v8).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index a90fc136..f0968579 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/sdk",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Host-neutral Mitii programmatic API over @mitii/v8 Agent Engine.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/search-kit/package.json b/packages/search-kit/package.json
index 6b171deb..402aaa37 100644
--- a/packages/search-kit/package.json
+++ b/packages/search-kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/search-kit",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Mitii web retrieval kit: pluggable search providers, content resolvers, and URL safety. Host-neutral; no V8 dependency.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/v8/ARCHITECTURE.md b/packages/v8/ARCHITECTURE.md
index 09e11bcd..bcef82d8 100644
--- a/packages/v8/ARCHITECTURE.md
+++ b/packages/v8/ARCHITECTURE.md
@@ -76,7 +76,7 @@ Authoritative packaging layout: `docs/REPO_LAYOUT.md`.
Dependency direction MUST remain `apps -> host -> sdk -> v8` (apps may also import sdk/v8 types carefully). V8 MUST NOT import host or SDK packages.
-Runtime orchestration belongs to `packages/v8/src/engine/agent-engine/`. Tool execution
+Runtime orchestration belongs to `packages/v8/src/engine/v8-engine/`. Tool execution
belongs to the tool-runtime engine package path. Business facades remain under
`modules/`.
@@ -93,7 +93,7 @@ belongs to the tool-runtime engine package path. Business facades remain under
| `model-gateway` | Model invocation -> model-event stream | Provider selection, capability negotiation, normalized streaming, usage, retry classification | Tool execution or run policy |
| `tool-runtime` | Authorized tool call -> tool result | Tool catalog, schema validation, permissions, path/command/network enforcement, timeout, audit, mutation transaction | Choosing the task route |
| `verification` | Change/result + state + policy -> verification result | Affected-project selection, applicable checks, diagnostics/diff evidence, completion recommendation | Direct shell bypass |
-| `agent-engine` | Start/resume request -> run handle | State machine, sequencing, model/tool loop, cancellation, suspension/resume, checkpoints, events, terminal result | Internals owned by other modules |
+| `v8-engine` | Start/resume request -> run handle | State machine, sequencing, model/tool loop, cancellation, suspension/resume, checkpoints, events, terminal result | Internals owned by other modules |
| `skills` | Task evidence + budget -> selected instructions | Selection, conflicts, provenance, instruction budgeting | General prompt construction |
| `memory` | Scoped query/commit -> memory result | Retrieval, relevance, retention, provenance, privacy | Run orchestration |
| `planning` | Task evidence + decision depth (+ optional skills/process hints) -> `PlanArtifact` | Dimension-driven plan drafting, validation, compaction, serialization | Route authority, tool execution, hard-coded plan types |
diff --git a/packages/v8/package.json b/packages/v8/package.json
index 262fd8c4..464abb2b 100644
--- a/packages/v8/package.json
+++ b/packages/v8/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/v8",
- "version": "2.10.6",
+ "version": "2.10.7",
"description": "Host-neutral Mitii V8 agent runtime (modules + engine).",
"license": "AGPL-3.0-or-later",
"type": "module",
From cc1eef669421cbec447e0f8df6d1f21e5169cdc5 Mon Sep 17 00:00:00 2001
From: codewithshinde
Date: Thu, 1 Oct 2026 16:30:34 -0500
Subject: [PATCH 22/22] fix(desktop): restore MCP persist enabled and vitest
import for contextWindow
Co-authored-by: Cursor
---
README.md | 2 +-
apps/acp/package.json | 2 +-
apps/cli/package.json | 2 +-
apps/daemon/package.json | 2 +-
apps/desktop/package.json | 2 +-
apps/desktop/src/main/workspace-config.ts | 3 +++
apps/desktop/tests/contextWindow.spec.ts | 2 ++
apps/vscode/package.json | 2 +-
package.json | 2 +-
packages/automation/package.json | 2 +-
packages/host/package.json | 2 +-
packages/mcp/package.json | 2 +-
packages/mcp/web/package.json | 2 +-
packages/sdk/package.json | 2 +-
packages/search-kit/package.json | 2 +-
packages/v8/package.json | 2 +-
16 files changed, 19 insertions(+), 14 deletions(-)
diff --git a/README.md b/README.md
index e9ba2c16..1da37b6e 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
-
+
diff --git a/apps/acp/package.json b/apps/acp/package.json
index 100aaf51..f83ad4d2 100644
--- a/apps/acp/package.json
+++ b/apps/acp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/acp",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii ACP-lite stdio bridge (Phase 3). Decision Policy remains authority; V8 does not import ACP.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/cli/package.json b/apps/cli/package.json
index 7d2e856c..bbda1af2 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/cli",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii headless CLI over @mitii/sdk. Phase 0: --origin/--autonomy/--agent for CI automation.",
"license": "AGPL-3.0-or-later",
"publishConfig": {
diff --git a/apps/daemon/package.json b/apps/daemon/package.json
index 7501a95e..25d077b1 100644
--- a/apps/daemon/package.json
+++ b/apps/daemon/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/daemon",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii automation daemon process entry (Phase 1). Long-lived schedule runner.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 8e17a8a8..4900a9fb 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/desktop",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii Desktop — local coding agent with chat, settings, and repository index.",
"license": "AGPL-3.0-or-later",
"private": true,
diff --git a/apps/desktop/src/main/workspace-config.ts b/apps/desktop/src/main/workspace-config.ts
index 81830c10..b1843658 100644
--- a/apps/desktop/src/main/workspace-config.ts
+++ b/apps/desktop/src/main/workspace-config.ts
@@ -215,8 +215,11 @@ export function writeWorkspaceCompatFiles(
if (replaceMcp) {
toWrite = fromSettings;
} else if (disk && disk.servers.length > 0) {
+ // Empty settings.mcp is a stale mirror — keep disk servers and enabled.
+ // Only apply settings.enabled when settings also lists servers.
toWrite = {
enabled:
+ fromSettings.servers.length > 0 &&
typeof settings.mcp?.enabled === 'boolean'
? Boolean(settings.mcp.enabled)
: disk.enabled,
diff --git a/apps/desktop/tests/contextWindow.spec.ts b/apps/desktop/tests/contextWindow.spec.ts
index 2d511c18..c160305a 100644
--- a/apps/desktop/tests/contextWindow.spec.ts
+++ b/apps/desktop/tests/contextWindow.spec.ts
@@ -1,3 +1,5 @@
+import { describe, expect, it } from 'vitest';
+
import {
inferContextWindowFromModelId,
inferMaximumOutputTokensFromModelId,
diff --git a/apps/vscode/package.json b/apps/vscode/package.json
index ed4b46c9..1a5c7407 100644
--- a/apps/vscode/package.json
+++ b/apps/vscode/package.json
@@ -2,7 +2,7 @@
"name": "mitii-ai-agent",
"displayName": "Mitii AI Agent",
"description": "Local-first VS Code AI coding agent with repository-aware context and controlled execution",
- "version": "2.10.7",
+ "version": "2.10.8",
"publisher": "mitii",
"license": "AGPL-3.0-or-later",
"icon": "media/mitii-logo.png",
diff --git a/package.json b/package.json
index fffd7525..cef2f95a 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mitii-ai-agent",
"description": "Private Mitii monorepo workspace orchestrator. Product packages: @mitii/v8, @mitii/sdk, @mitii/automation, @mitii/search-kit, @mitii/mcp, @mitii/mcp-web, @mitii/mcp-sqlite, @mitii/mcp-postgres, @mitii/mcp-mongo, @mitii/mcp-sql, @mitii/host, @mitii/cli, @mitii/daemon, @mitii/acp, @mitii/desktop, apps/vscode.",
- "version": "2.10.7",
+ "version": "2.10.8",
"private": true,
"license": "AGPL-3.0-or-later",
"author": {
diff --git a/packages/automation/package.json b/packages/automation/package.json
index b9bf5a0f..02b93973 100644
--- a/packages/automation/package.json
+++ b/packages/automation/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/automation",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii automation control plane: schedules, event ingress, claim/lease runner, webhooks (Phases 1–2).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/host/package.json b/packages/host/package.json
index 020a1c08..1cffaa4b 100644
--- a/packages/host/package.json
+++ b/packages/host/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/host",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Shared host kit for Mitii apps: SQLite injection, workspace indexing, repository context, durable ports (checkpoints/memory/skills/search/network), project rules, provider presets. Web retrieval via @mitii/search-kit.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/package.json b/packages/mcp/package.json
index b1fe36b2..ae9ee132 100644
--- a/packages/mcp/package.json
+++ b/packages/mcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii MCP client kit: connect to MCP servers (stdio/SSE/streamable-HTTP) and register tools into V8 ToolRegistry. Does not expose Mitii as an MCP server.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/mcp/web/package.json b/packages/mcp/web/package.json
index f4fe1a6f..5b36c7f8 100644
--- a/packages/mcp/web/package.json
+++ b/packages/mcp/web/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/mcp-web",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii MCP stdio server under packages/mcp/web: web_search, fetch_url, optional memory_search via search-kit (no v8).",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/sdk/package.json b/packages/sdk/package.json
index f0968579..eb11931d 100644
--- a/packages/sdk/package.json
+++ b/packages/sdk/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/sdk",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Host-neutral Mitii programmatic API over @mitii/v8 Agent Engine.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/search-kit/package.json b/packages/search-kit/package.json
index 402aaa37..23e47399 100644
--- a/packages/search-kit/package.json
+++ b/packages/search-kit/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/search-kit",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Mitii web retrieval kit: pluggable search providers, content resolvers, and URL safety. Host-neutral; no V8 dependency.",
"license": "AGPL-3.0-or-later",
"type": "module",
diff --git a/packages/v8/package.json b/packages/v8/package.json
index 464abb2b..ceb34def 100644
--- a/packages/v8/package.json
+++ b/packages/v8/package.json
@@ -1,6 +1,6 @@
{
"name": "@mitii/v8",
- "version": "2.10.7",
+ "version": "2.10.8",
"description": "Host-neutral Mitii V8 agent runtime (modules + engine).",
"license": "AGPL-3.0-or-later",
"type": "module",