Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
1382892
docs: design workspace insights
the-long-ride Aug 27, 2026
0ec28c8
docs: revise workspace insights architecture
the-long-ride Aug 27, 2026
53bf9e0
docs: plan workspace insights implementation
the-long-ride Aug 27, 2026
dcf3642
test: define workspace insights contracts
the-long-ride Aug 27, 2026
f6ed06e
feat: define workspace insights contracts
the-long-ride Aug 27, 2026
934c5fa
test: register workspace insights coverage
the-long-ride Aug 27, 2026
de75ec8
test: define frontmatter and anchor semantics
the-long-ride Aug 27, 2026
c9f64ab
feat: share markdown metadata and anchor semantics
the-long-ride Aug 28, 2026
1fa91bc
feat: add wiki link resolution semantics
the-long-ride Aug 28, 2026
a18956e
feat: render wiki links and bounded transclusions
the-long-ride Aug 28, 2026
b156811
test: define insights host protocol contracts
the-long-ride Aug 28, 2026
456d2fb
feat: add insights host protocol
the-long-ride Aug 28, 2026
73e0969
fix: narrow insights host messages safely
the-long-ride Aug 28, 2026
0474d62
test: define insights filesystem host contracts
the-long-ride Aug 28, 2026
cea48b4
feat: add cross-runtime insights filesystem hosts
the-long-ride Aug 28, 2026
51c2bc8
fix: stabilize insights host contracts
the-long-ride Aug 28, 2026
95e5a55
fix: adapt vscode webview thenable
the-long-ride Aug 28, 2026
a9cd28f
test: define insights external link behavior
the-long-ride Aug 28, 2026
86abad5
feat: add secure external link checker
the-long-ride Aug 28, 2026
8272c52
fix: type external link queue
the-long-ride Aug 28, 2026
4fed275
feat: add tauri external link checker
the-long-ride Aug 28, 2026
5989fcd
feat: track tauri external link cancellation
the-long-ride Aug 28, 2026
2de9dc8
feat: wire tauri external link batches
the-long-ride Aug 28, 2026
28f4d5c
feat: expose tauri insights external modules
the-long-ride Aug 28, 2026
35680f4
feat: route tauri external link checks
the-long-ride Aug 28, 2026
b75be89
test: define document insights analysis
the-long-ride Aug 28, 2026
9e7d032
test: define configurable insights linting
the-long-ride Aug 28, 2026
44f777a
feat: analyze and lint workspace documents
the-long-ride Aug 28, 2026
3d2fbd2
test: define insights index and graph behavior
the-long-ride Aug 28, 2026
5f3ba54
feat: maintain insights backlinks and graph
the-long-ride Aug 28, 2026
2eb0e68
fix: preserve insights terminology tokens
the-long-ride Aug 28, 2026
297f0fc
test: cover insights analyzer and index modules
the-long-ride Aug 28, 2026
565dca2
feat: detect duplicate workspace content
the-long-ride Aug 28, 2026
8113606
feat: rank related workspace documents
the-long-ride Aug 28, 2026
bbafb3c
feat: run workspace insights in a worker
the-long-ride Aug 28, 2026
f6d6ee1
feat: cache derived workspace insights
the-long-ride Aug 28, 2026
b2ead63
feat: add workspace insights storage identifiers
the-long-ride Aug 28, 2026
0ffbcde
feat: resolve insights settings precedence
the-long-ride Aug 28, 2026
a7b0de1
feat: apply incremental insights deltas in worker client
the-long-ride Aug 28, 2026
a8c83e4
fix: stabilize insights duplicate and relationship analysis
the-long-ride Aug 28, 2026
d42748a
test: define workspace insights session lifecycle
the-long-ride Aug 28, 2026
2ef29bf
feat: orchestrate workspace insights sessions
the-long-ride Aug 28, 2026
7957881
test: define workspace insights panel shell
the-long-ride Aug 28, 2026
b2a4090
feat: add workspace insights panel
the-long-ride Aug 28, 2026
5ec9575
test: define insights gallery links lint and duplicates views
the-long-ride Aug 28, 2026
f67568d
feat: add insights media links lint and duplicates views
the-long-ride Aug 28, 2026
e4e4840
test: define insights graph related checks and reports
the-long-ride Aug 28, 2026
7787627
feat: add graph related checks and insights reports
the-long-ride Aug 28, 2026
9233fa8
fix: preserve external check call contract
the-long-ride Aug 28, 2026
57f3a5e
test: define workspace insights settings and localization
the-long-ride Aug 28, 2026
f811bb2
feat: persist and localize workspace insights settings
the-long-ride Aug 28, 2026
8512c76
test: require persisted insights controls
the-long-ride Aug 28, 2026
fc378e1
fix: keep audited translation base contract stable
the-long-ride Aug 28, 2026
31d688d
test: register workspace insights coverage
the-long-ride Aug 28, 2026
5c62acc
feat: wire persisted workspace insights controls
the-long-ride Aug 28, 2026
e5254db
fix: disambiguate insights settings types
the-long-ride Aug 28, 2026
50b0deb
fix: preserve settings import source contracts
the-long-ride Aug 28, 2026
7e96a00
fix: preserve audited locale source contract
the-long-ride Aug 28, 2026
4a4eca1
test: require insights backup store integration
the-long-ride Aug 28, 2026
61e9809
fix: use settings normalizer export
the-long-ride Aug 28, 2026
34dc6b9
fix: preserve sidebar sort normalization
the-long-ride Aug 28, 2026
5638a98
test: keep Insights settings import pure
the-long-ride Aug 28, 2026
c4b1862
feat: include persisted Insights settings in backups
the-long-ride Aug 28, 2026
ff1049e
feat: restore Insights settings from backups
the-long-ride Aug 28, 2026
93cac36
test: require Insights locale consumption
the-long-ride Aug 28, 2026
2eebb8f
test: require global and workspace Insights scopes
the-long-ride Aug 28, 2026
54635e8
feat: wire Insights locale and settings scopes
the-long-ride Aug 28, 2026
227bdb3
feat: localize Insights panel shell
the-long-ride Aug 28, 2026
aba38f6
feat: add localized Insights settings scopes
the-long-ride Aug 28, 2026
d6b5b4c
feat: localize Insights gallery
the-long-ride Aug 28, 2026
c32fa39
feat: localize Insights links
the-long-ride Aug 28, 2026
caa9c2d
feat: localize Insights lint controls
the-long-ride Aug 28, 2026
dff85b5
feat: localize Insights duplicates
the-long-ride Aug 28, 2026
e709f43
feat: localize Insights graph
the-long-ride Aug 28, 2026
0557121
feat: localize Insights relationships
the-long-ride Aug 28, 2026
059bdee
test: assert localized Insights actions
the-long-ride Aug 28, 2026
01846d1
test: use localized Insights action labels
the-long-ride Aug 28, 2026
5b1240d
test: use localized lint and duplicate actions
the-long-ride Aug 28, 2026
3b67831
test: require advanced Insights settings controls
the-long-ride Aug 28, 2026
e71e7e6
feat: expose Insights lint rule defaults
the-long-ride Aug 28, 2026
24dd366
feat: complete advanced Insights settings
the-long-ride Aug 28, 2026
f473ab7
fix: apply insights lint suppressions
the-long-ride Aug 29, 2026
a772e03
fix: stabilize insights UI contracts
the-long-ride Aug 29, 2026
67639b5
test: recycle UI workers before heap exhaustion
the-long-ride Aug 29, 2026
0e15ba2
test: shard UI suite across fresh forks
the-long-ride Aug 29, 2026
18bdec4
test: split UI suite into eight semantic-preserving shards
the-long-ride Aug 29, 2026
5d5c997
test: list final UI shard before execution
the-long-ride Aug 29, 2026
4bf899f
test: isolate sidebar render suite from UI shards
the-long-ride Aug 29, 2026
a97f6c8
chore: restore update contract test command
the-long-ride Aug 29, 2026
9e6b73a
test: isolate sidebar search-tab dispatch case
the-long-ride Aug 29, 2026
e4226a3
test: isolate sidebar rendering from Insights session lifecycle
the-long-ride Aug 29, 2026
d7b8af4
test: verify sidebar render isolation
the-long-ride Aug 29, 2026
39cee8d
test: restore four UI shards after isolating sidebar unit scope
the-long-ride Aug 29, 2026
75c1317
test: define insights cache lifecycle contracts
the-long-ride Aug 29, 2026
13121d7
feat: restore and validate workspace insights cache
the-long-ride Aug 29, 2026
e8e0d7b
refactor: split workspace insights session helpers
the-long-ride Aug 29, 2026
b1dd499
refactor: keep insights session within source budgets
the-long-ride Aug 29, 2026
ca058fd
refactor: centralize insights watch state
the-long-ride Aug 29, 2026
8263577
test: compare restored insight anchors by membership
the-long-ride Aug 29, 2026
3c3245c
test: define insights cache identity compatibility
the-long-ride Aug 29, 2026
85ea2c0
feat: invalidate insights cache by workspace identity and lint config
the-long-ride Aug 29, 2026
b244866
test: wait for Scope View link handlers before context menu
the-long-ride Aug 29, 2026
da9c45f
refactor: centralize insights cache compatibility lookup
the-long-ride Aug 29, 2026
6a75e10
chore: remove unrelated Vitest formatting diff
the-long-ride Aug 29, 2026
b515e75
fix: restore expanded Vitest project matrix
the-long-ride Aug 29, 2026
f0bb969
chore: align Vitest config with PR base
the-long-ride Aug 29, 2026
83fd6cd
test: define live lint cache invalidation
the-long-ride Aug 29, 2026
1ca827a
refactor: centralize insights cache persistence helpers
the-long-ride Aug 29, 2026
d34a9d9
fix: revalidate insights cache after lint settings change
the-long-ride Aug 29, 2026
c2b9b99
test: wait for Scope View click handlers before self-link
the-long-ride Aug 29, 2026
bda44e5
refactor: extract insights analysis cache invalidation
the-long-ride Aug 29, 2026
ec4aa60
refactor: keep insights hook within source budget
the-long-ride Aug 29, 2026
9405503
refactor: isolate insights settings lifecycle
the-long-ride Aug 29, 2026
42841a7
refactor: keep insights hook within source budget
the-long-ride Aug 29, 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
271 changes: 271 additions & 0 deletions chromium-xtension/src/chrome-host-insights.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
const DEFAULT_SOFT_LIMIT_BYTES = 10 * 1024 * 1024;
const DEFAULT_HARD_LIMIT_BYTES = 64 * 1024 * 1024;
const SCAN_BATCH_SIZE = 200;
const POLL_INTERVAL_MS = 2500;
const HARD_EXCLUDED = new Set(['.git', '.hg', '.svn']);
const DEFAULT_EXCLUDED = new Set(['node_modules', '.next', 'dist', 'build', 'coverage', '.cache']);

const MIME_TYPES: Record<string, string> = {
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
'.webp': 'image/webp', '.svg': 'image/svg+xml', '.avif': 'image/avif',
'.mp4': 'video/mp4', '.webm': 'video/webm', '.mov': 'video/quicktime',
'.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.ogg': 'audio/ogg', '.m4a': 'audio/mp4',
'.md': 'text/markdown', '.mdx': 'text/mdx', '.pdf': 'application/pdf',
};

interface ChromeInsightsHostDeps {
getActiveHandle: () => FileSystemDirectoryHandle | null;
send: (message: any) => void;
setInterval?: typeof window.setInterval;
clearInterval?: typeof window.clearInterval;
pollIntervalMs?: number;
}

function normalizePath(value: string): string | null {
const parts: string[] = [];
for (const part of String(value || '').replace(/\\/g, '/').split('/')) {
if (!part || part === '.') continue;
if (part === '..') {
if (!parts.length) return null;
parts.pop();
continue;
}
parts.push(part);
}
return parts.join('/');
}

function resolveReference(documentPath: string, resourcePath: string): string | null {
const raw = String(resourcePath || '').split(/[?#]/, 1)[0];
if (!raw || /^(?:https?:|data:|blob:|javascript:|file:\/\/)/i.test(raw)) return null;
if (raw.startsWith('/')) return normalizePath(raw.slice(1));
const base = String(documentPath || '').replace(/\\/g, '/').split('/').slice(0, -1).join('/');
return normalizePath(`${base}/${raw}`);
}

function extension(path: string): string {
const file = path.split('/').pop() || '';
const index = file.lastIndexOf('.');
return index > 0 ? file.slice(index).toLowerCase() : '';
}

function isExcluded(relativePath: string, userPatterns: readonly string[] = []): boolean {
const segments = relativePath.split('/').filter(Boolean);
if (segments.some(segment => HARD_EXCLUDED.has(segment))) return true;
let result = segments.some(segment => DEFAULT_EXCLUDED.has(segment));
for (const raw of userPatterns) {
const value = String(raw || '').trim();
if (!value || value.startsWith('#')) continue;
const negated = value.startsWith('!');
const needle = (negated ? value.slice(1) : value).replace(/^\//, '').replace(/\/$/, '');
if (relativePath === needle || relativePath.startsWith(`${needle}/`) || (!needle.includes('/') && segments.includes(needle))) {
result = !negated;
}
}
return result;
}

async function getFileHandle(root: FileSystemDirectoryHandle, relativePath: string): Promise<FileSystemFileHandle | null> {
const normalized = normalizePath(relativePath);
if (!normalized) return null;
const parts = normalized.split('/');
let directory = root;
try {
for (let index = 0; index < parts.length - 1; index += 1) {
directory = await directory.getDirectoryHandle(parts[index]);
}
return await directory.getFileHandle(parts.at(-1)!);
} catch {
return null;
}
}

async function getAnyHandle(root: FileSystemDirectoryHandle, relativePath: string): Promise<FileSystemHandle | null> {
const normalized = normalizePath(relativePath);
if (!normalized) return null;
const parts = normalized.split('/');
let directory = root;
try {
for (let index = 0; index < parts.length - 1; index += 1) directory = await directory.getDirectoryHandle(parts[index]);
const name = parts.at(-1)!;
try { return await directory.getFileHandle(name); }
catch { return await directory.getDirectoryHandle(name); }
} catch {
return null;
}
}

async function sha256(text: string): Promise<string | undefined> {
try {
if (!globalThis.crypto?.subtle) return undefined;
const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text));
return Array.from(new Uint8Array(digest), value => value.toString(16).padStart(2, '0')).join('');
} catch {
return undefined;
}
}

export function createChromeInsightsHost(deps: ChromeInsightsHostDeps) {
const cancelled = new Set<string>();
const setIntervalImpl = deps.setInterval ?? window.setInterval.bind(window);
const clearIntervalImpl = deps.clearInterval ?? window.clearInterval.bind(window);
let pollTimer: number | null = null;
let pollGeneration = 0;
let previousPoll = new Map<string, { sizeBytes: number; mtimeMs: number; extension?: string }>();

async function collectEntries(root: FileSystemDirectoryHandle, userPatterns: readonly string[] = [], requestId?: string) {
const results: any[] = [];
let excludedEntries = 0;
let skippedEntries = 0;
const walk = async (directory: FileSystemDirectoryHandle, prefix = ''): Promise<void> => {
try {
for await (const [name, handle] of (directory as any).entries()) {
if (requestId && cancelled.has(requestId)) break;
const relativePath = prefix ? `${prefix}/${name}` : name;
if (isExcluded(relativePath, userPatterns)) { excludedEntries += 1; continue; }
if (handle.kind === 'directory') {
await walk(handle as FileSystemDirectoryHandle, relativePath);
continue;
}
if (handle.kind !== 'file') continue;
try {
const file = await (handle as FileSystemFileHandle).getFile();
results.push({
relativePath,
canonicalRelativePath: relativePath,
kind: 'file',
sizeBytes: file.size,
mtimeMs: file.lastModified || 0,
extension: extension(relativePath) || undefined,
isSymlink: false,
});
} catch { skippedEntries += 1; }
}
} catch { skippedEntries += 1; }
};
await walk(root);
return { entries: results, excludedEntries, skippedEntries };
}

async function probeWorkspaceResource(message: any): Promise<void> {
const root = deps.getActiveHandle();
const requestId = String(message.requestId || '');
if (!root) { deps.send({ command: 'workspaceResourceProbeResult', requestId, status: 'missing' }); return; }
const relativePath = resolveReference(String(message.documentPath || ''), String(message.resourcePath || ''));
if (!relativePath) { deps.send({ command: 'workspaceResourceProbeResult', requestId, status: 'outside-workspace' }); return; }
const handle = await getAnyHandle(root, relativePath);
if (!handle) { deps.send({ command: 'workspaceResourceProbeResult', requestId, status: 'missing' }); return; }
if (handle.kind === 'directory') {
deps.send({ command: 'workspaceResourceProbeResult', requestId, status: 'exists', relativePath, kind: 'directory' });
return;
}
try {
const file = await (handle as FileSystemFileHandle).getFile();
deps.send({
command: 'workspaceResourceProbeResult', requestId, status: 'exists', relativePath, kind: 'file',
sizeBytes: file.size, mimeType: file.type || MIME_TYPES[extension(relativePath)] || 'application/octet-stream',
});
} catch {
deps.send({ command: 'workspaceResourceProbeResult', requestId, status: 'unreadable' });
}
}

async function readInsightsDocumentSource(message: any): Promise<void> {
const root = deps.getActiveHandle();
const requestId = String(message.requestId || '');
const relativePath = normalizePath(String(message.relativePath || '')) || '';
const respond = (payload: any) => deps.send({ command: 'insightsDocumentSourceResult', requestId, relativePath, ...payload });
if (!root || !relativePath) { respond({ status: 'missing' }); return; }
if (!/\.mdx?$/i.test(relativePath)) { respond({ status: 'unsupported' }); return; }
const handle = await getFileHandle(root, relativePath);
if (!handle) { respond({ status: 'missing' }); return; }
try {
const file = await handle.getFile();
const soft = Math.max(1, Number(message.softLimitBytes) || DEFAULT_SOFT_LIMIT_BYTES);
const hard = Math.min(Math.max(1, Number(message.hardLimitBytes) || DEFAULT_HARD_LIMIT_BYTES), DEFAULT_HARD_LIMIT_BYTES);
if (file.size > hard) { respond({ status: 'too-large', sizeBytes: file.size, mtimeMs: file.lastModified || 0, hardLimit: true }); return; }
if (file.size > soft) { respond({ status: 'too-large', sizeBytes: file.size, mtimeMs: file.lastModified || 0, hardLimit: false }); return; }
const source = await file.text();
respond({ status: 'ok', source, sizeBytes: file.size, mtimeMs: file.lastModified || 0, contentHash: await sha256(source) });
} catch {
respond({ status: 'unreadable' });
}
}

async function scanInsightsWorkspace(message: any): Promise<void> {
const root = deps.getActiveHandle();
const requestId = String(message.requestId || '');
cancelled.delete(requestId);
if (!root) {
deps.send({ command: 'insightsScanComplete', requestId, totalEntries: 0, excludedEntries: 0, skippedEntries: 0, truncated: false });
return;
}
const { entries, excludedEntries, skippedEntries } = await collectEntries(root, Array.isArray(message.userPatterns) ? message.userPatterns : [], requestId);
for (let index = 0; index < entries.length; index += SCAN_BATCH_SIZE) {
if (cancelled.has(requestId)) break;
const batch = entries.slice(index, index + SCAN_BATCH_SIZE);
deps.send({ command: 'insightsScanBatch', requestId, entries: batch, scannedEntries: Math.min(index + batch.length, entries.length), excludedEntries });
}
const wasCancelled = cancelled.delete(requestId);
deps.send({
command: 'insightsScanComplete', requestId, totalEntries: entries.length, excludedEntries, skippedEntries,
truncated: false, ...(wasCancelled ? { cancelled: true } : {}),
});
}

function cancelInsightsScan(message: any): void {
const requestId = String(message.requestId || '');
if (requestId) cancelled.add(requestId);
}

function stopPolling(): void {
pollGeneration += 1;
if (pollTimer !== null) clearIntervalImpl(pollTimer);
pollTimer = null;
previousPoll = new Map();
}

async function pollOnce(message: any, generation: number): Promise<void> {
const root = deps.getActiveHandle();
if (!root || generation !== pollGeneration) return;
const { entries } = await collectEntries(root);
if (generation !== pollGeneration) return;
const next = new Map(entries.map(entry => [entry.relativePath, entry]));
const deltas: any[] = [];
for (const [relativePath, entry] of next) {
const previous = previousPoll.get(relativePath);
if (!previous || previous.sizeBytes !== entry.sizeBytes || previous.mtimeMs !== entry.mtimeMs) {
deltas.push({ kind: previous ? 'update' : 'add', entry });
}
}
for (const relativePath of previousPoll.keys()) if (!next.has(relativePath)) deltas.push({ kind: 'delete', relativePath });
previousPoll = next;
if (deltas.length) deps.send({ command: 'insightsFsDelta', requestId: message.requestId, workspaceOperationId: message.workspaceOperationId, deltas });
}

function setInsightsWatchState(message: any): void {
stopPolling();
deps.send({
command: 'insightsRuntimeCapabilities', requestId: message.requestId,
capabilities: { fileChanges: 'polling', externalLinkChecking: false, documentPreviewReuse: true },
});
if (message.active !== true || message.visible !== true || !deps.getActiveHandle()) return;
const generation = pollGeneration;
void pollOnce(message, generation);
pollTimer = setIntervalImpl(() => { void pollOnce(message, generation); }, deps.pollIntervalMs ?? POLL_INTERVAL_MS);
}

function dispose(): void { stopPolling(); cancelled.clear(); }

return {
capabilities: { fileChanges: 'polling' as const, externalLinkChecking: false, documentPreviewReuse: true },
probeWorkspaceResource,
readInsightsDocumentSource,
scanInsightsWorkspace,
cancelInsightsScan,
setInsightsWatchState,
dispose,
};
}

export { DEFAULT_SOFT_LIMIT_BYTES, DEFAULT_HARD_LIMIT_BYTES, SCAN_BATCH_SIZE, POLL_INTERVAL_MS, resolveReference };
6 changes: 6 additions & 0 deletions chromium-xtension/src/chrome-host-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { FolderNode, MdFile } from '../../ui/src/types';
import type { BrowserSearchIndex } from './search-index';
import { handleChromeExportHostCommand } from './chrome-host-export';
import { filterSearchIndexTabs, isValidExternalUrl, normalizeSearchQuery, resolveWorkspaceTextResourcePath } from './chrome-host-utils';
import { handleBrowserInsightsHostCommand } from './insights-host-router';
import { resolveWorkspaceSearchItems } from './workspace-search-items';

interface ChromeHostSearchContext {
Expand All @@ -15,6 +16,11 @@ interface ChromeHostSearchContext {
}

export async function handleChromeHostUtilityCommand(message: any, context: ChromeHostSearchContext): Promise<boolean> {
if (await handleBrowserInsightsHostCommand(message, {
activeHandle: context.activeHandle,
send: context.send,
})) return true;

if (await handleChromeExportHostCommand(message, {
activeHandle: context.activeHandle,
send: context.send,
Expand Down
62 changes: 62 additions & 0 deletions chromium-xtension/src/insights-host-router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createChromeInsightsHost } from './chrome-host-insights';

interface InsightsHostRouterContext {
activeHandle: FileSystemDirectoryHandle | null;
send: (message: any) => void;
}

let currentHandle: FileSystemDirectoryHandle | null = null;
let currentSend: (message: any) => void = () => {};

const host = createChromeInsightsHost({
getActiveHandle: () => currentHandle,
send: (message) => currentSend(message),
});

export async function handleBrowserInsightsHostCommand(
message: any,
context: InsightsHostRouterContext,
): Promise<boolean> {
currentHandle = context.activeHandle;
currentSend = context.send;

switch (message.command) {
case 'scanInsightsWorkspace':
await host.scanInsightsWorkspace(message);
return true;
case 'cancelInsightsScan':
host.cancelInsightsScan(message);
return true;
case 'readInsightsDocumentSource':
await host.readInsightsDocumentSource(message);
return true;
case 'probeWorkspaceResource':
await host.probeWorkspaceResource(message);
return true;
case 'setInsightsWatchState':
host.setInsightsWatchState(message);
return true;
case 'checkExternalLinks':
for (const url of Array.isArray(message.urls) ? message.urls : []) {
context.send({
command: 'externalLinkCheckResult',
requestId: message.requestId,
url,
status: 'unsupported',
reason: 'Browser runtime cannot provide DNS/IP-pinned HTTP status checks.',
});
}
context.send({ command: 'externalLinkCheckComplete', requestId: message.requestId, cancelled: false });
return true;
case 'cancelExternalLinkChecks':
context.send({ command: 'externalLinkCheckComplete', requestId: message.requestId, cancelled: true });
return true;
default:
return false;
}
}

export function disposeBrowserInsightsHost(): void {
host.dispose();
currentHandle = null;
}
Loading