@4onstudios/iris-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/api/acp/acpServer.d.ts +23 -0
- package/dist/api/acp/acpServer.js +814 -0
- package/dist/api/acp/index.d.ts +2 -0
- package/dist/api/acp/index.js +2 -0
- package/dist/api/acp/irisClient.d.ts +58 -0
- package/dist/api/acp/irisClient.js +214 -0
- package/dist/api/agent.d.ts +14 -0
- package/dist/api/agent.js +4171 -0
- package/dist/api/backendService.d.ts +4 -0
- package/dist/api/backendService.js +14 -0
- package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
- package/dist/api/core/agent/host/AgentContract.js +1 -0
- package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
- package/dist/api/core/agent/host/AgentRegistry.js +125 -0
- package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
- package/dist/api/core/agent/host/defaultRegistry.js +46 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
- package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
- package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
- package/dist/api/core/agent/host/hostSessionManager.js +352 -0
- package/dist/api/core/agent/host/index.d.ts +5 -0
- package/dist/api/core/agent/host/index.js +5 -0
- package/dist/api/core/agent/index.d.ts +87 -0
- package/dist/api/core/agent/index.js +1024 -0
- package/dist/api/core/agent/publicContracts.d.ts +35 -0
- package/dist/api/core/agent/publicContracts.js +103 -0
- package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
- package/dist/api/core/agent/tools/applyDiff.js +131 -0
- package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
- package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
- package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
- package/dist/api/core/agent/tools/createDirectory.js +77 -0
- package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
- package/dist/api/core/agent/tools/deleteFile.js +66 -0
- package/dist/api/core/agent/tools/editFile.d.ts +73 -0
- package/dist/api/core/agent/tools/editFile.js +173 -0
- package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
- package/dist/api/core/agent/tools/executeCommand.js +250 -0
- package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
- package/dist/api/core/agent/tools/fileContent.js +227 -0
- package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
- package/dist/api/core/agent/tools/findDefinition.js +109 -0
- package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
- package/dist/api/core/agent/tools/findReferences.js +135 -0
- package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
- package/dist/api/core/agent/tools/formatDocument.js +90 -0
- package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeActions.js +97 -0
- package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
- package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
- package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
- package/dist/api/core/agent/tools/getCodeContext.js +282 -0
- package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
- package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
- package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
- package/dist/api/core/agent/tools/getSymbols.js +164 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
- package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
- package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
- package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
- package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
- package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
- package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
- package/dist/api/core/agent/tools/grepSearch.js +211 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
- package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
- package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
- package/dist/api/core/agent/tools/listDirectory.js +161 -0
- package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
- package/dist/api/core/agent/tools/mcpTools.js +442 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
- package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
- package/dist/api/core/agent/tools/readFile.d.ts +92 -0
- package/dist/api/core/agent/tools/readFile.js +337 -0
- package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
- package/dist/api/core/agent/tools/renameFile.js +86 -0
- package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
- package/dist/api/core/agent/tools/renameSymbol.js +106 -0
- package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
- package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
- package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
- package/dist/api/core/agent/tools/searchFiles.js +280 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
- package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
- package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
- package/dist/api/core/agent/tools/treeTraversal.js +217 -0
- package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
- package/dist/api/core/agent/tools/webSearch.js +80 -0
- package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
- package/dist/api/core/agent/tools/writeFile.js +97 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
- package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
- package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
- package/dist/api/core/agent/utils/diffUtils.js +39 -0
- package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
- package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
- package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
- package/dist/api/core/agent/utils/errorRecovery.js +229 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
- package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
- package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
- package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
- package/dist/api/core/agent/utils/pathRecovery.js +29 -0
- package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
- package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
- package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
- package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
- package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
- package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
- package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
- package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
- package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
- package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
- package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
- package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
- package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
- package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
- package/dist/api/core/library/BrowserManager.d.ts +11 -0
- package/dist/api/core/library/BrowserManager.js +66 -0
- package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
- package/dist/api/core/library/desktopWorkspace.js +3 -0
- package/dist/api/core/library/extensionManager.d.ts +10 -0
- package/dist/api/core/library/extensionManager.js +27 -0
- package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
- package/dist/api/core/library/knowledgeGraph.js +989 -0
- package/dist/api/core/library/languageModelTools.d.ts +141 -0
- package/dist/api/core/library/languageModelTools.js +207 -0
- package/dist/api/core/library/localRuntime.d.ts +23 -0
- package/dist/api/core/library/localRuntime.js +144 -0
- package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
- package/dist/api/core/library/lsp/coreLsp.js +2076 -0
- package/dist/api/core/library/lsp/protocol.d.ts +25 -0
- package/dist/api/core/library/lsp/protocol.js +1 -0
- package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
- package/dist/api/core/library/lsp/serverManager.js +427 -0
- package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
- package/dist/api/core/library/mcpServerProvider.js +152 -0
- package/dist/api/core/library/mcpSettings.d.ts +20 -0
- package/dist/api/core/library/mcpSettings.js +131 -0
- package/dist/api/core/library/patternMatcher.d.ts +66 -0
- package/dist/api/core/library/patternMatcher.js +284 -0
- package/dist/api/core/library/regexEscape.d.ts +4 -0
- package/dist/api/core/library/regexEscape.js +4 -0
- package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
- package/dist/api/core/library/runtimeEventBus.js +78 -0
- package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
- package/dist/api/core/library/safetyMiddleware.js +215 -0
- package/dist/api/core/library/tauri.d.ts +71 -0
- package/dist/api/core/library/tauri.js +222 -0
- package/dist/api/core/library/tauriImport.d.ts +5 -0
- package/dist/api/core/library/tauriImport.js +7 -0
- package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
- package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
- package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
- package/dist/api/core/library/workspaceIdentity.js +49 -0
- package/dist/api/core/library/workspaceSummary.d.ts +19 -0
- package/dist/api/core/library/workspaceSummary.js +35 -0
- package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
- package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
- package/dist/api/core/skills/create-pr/SKILL.md +32 -0
- package/dist/api/core/skills/dev-server/SKILL.md +20 -0
- package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
- package/dist/api/core/skills/github-search/SKILL.md +21 -0
- package/dist/api/core/skills/github-summary/SKILL.md +25 -0
- package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
- package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
- package/dist/api/core/skills/project-setup/SKILL.md +20 -0
- package/dist/api/core/skills/search-integration/SKILL.md +19 -0
- package/dist/api/core/skills/search-results/SKILL.md +19 -0
- package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
- package/dist/api/data/runStore.d.ts +46 -0
- package/dist/api/data/runStore.js +244 -0
- package/dist/api/helpers/agentUtils.d.ts +11 -0
- package/dist/api/helpers/agentUtils.js +23 -0
- package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
- package/dist/api/helpers/modelTokenLimits.js +158 -0
- package/dist/api/helpers/observationalMemory.d.ts +10 -0
- package/dist/api/helpers/observationalMemory.js +36 -0
- package/dist/api/helpers/promptBudget.d.ts +30 -0
- package/dist/api/helpers/promptBudget.js +216 -0
- package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
- package/dist/api/helpers/resolveImageMessageParts.js +140 -0
- package/dist/api/helpers/slashCommands.d.ts +31 -0
- package/dist/api/helpers/slashCommands.js +129 -0
- package/dist/api/helpers/tokenUsage.d.ts +19 -0
- package/dist/api/helpers/tokenUsage.js +81 -0
- package/dist/api/routes/fileRoutes.d.ts +2 -0
- package/dist/api/routes/fileRoutes.js +108 -0
- package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspDocumentRoutes.js +84 -0
- package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
- package/dist/api/routes/lspHierarchyRoutes.js +122 -0
- package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
- package/dist/api/routes/lspPositionRoutes.js +263 -0
- package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
- package/dist/api/routes/lspQueryRoutes.js +679 -0
- package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
- package/dist/api/routes/lspResolveRoutes.js +143 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
- package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
- package/dist/api/routes/semanticRoutes.d.ts +2 -0
- package/dist/api/routes/semanticRoutes.js +126 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +160 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +38 -0
- package/package.json +100 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
class RuntimeEventBus {
|
|
2
|
+
listeners = new Map();
|
|
3
|
+
buffer = new Map();
|
|
4
|
+
eventHistory = [];
|
|
5
|
+
maxHistorySize = 100;
|
|
6
|
+
subscribe(event, listener) {
|
|
7
|
+
if (!this.listeners.has(event)) {
|
|
8
|
+
this.listeners.set(event, new Set());
|
|
9
|
+
}
|
|
10
|
+
this.listeners.get(event).add(listener);
|
|
11
|
+
if (this.buffer.has(event)) {
|
|
12
|
+
try {
|
|
13
|
+
listener(this.buffer.get(event));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.error(`Error in buffered event delivery for ${event}:`, error);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return () => {
|
|
20
|
+
const listeners = this.listeners.get(event);
|
|
21
|
+
if (listeners) {
|
|
22
|
+
listeners.delete(listener);
|
|
23
|
+
if (listeners.size === 0) {
|
|
24
|
+
this.listeners.delete(event);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
emit(event, data = undefined) {
|
|
30
|
+
this.trackEvent(event, data);
|
|
31
|
+
const listeners = this.listeners.get(event);
|
|
32
|
+
if (!listeners || listeners.size === 0) {
|
|
33
|
+
this.buffer.set(event, data);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.buffer.delete(event);
|
|
37
|
+
listeners.forEach((listener) => {
|
|
38
|
+
try {
|
|
39
|
+
listener(data);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error(`Error in listener for event ${event}:`, error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
trackEvent(event, data) {
|
|
47
|
+
this.eventHistory.push({
|
|
48
|
+
event,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
dataSize: data ? JSON.stringify(data).length : 0,
|
|
51
|
+
});
|
|
52
|
+
if (this.eventHistory.length > this.maxHistorySize) {
|
|
53
|
+
this.eventHistory.shift();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getStatistics() {
|
|
57
|
+
const stats = {};
|
|
58
|
+
this.eventHistory.forEach(({ event }) => {
|
|
59
|
+
stats[event] = (stats[event] || 0) + 1;
|
|
60
|
+
});
|
|
61
|
+
return stats;
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
this.listeners.clear();
|
|
65
|
+
this.buffer.clear();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export const runtimeEventBus = new RuntimeEventBus();
|
|
69
|
+
export const RuntimeEvents = {
|
|
70
|
+
FILE_OPEN: "file:open",
|
|
71
|
+
CODE_CHANGE: "code:change",
|
|
72
|
+
CONTEXT_UPDATE: "kg:context:update",
|
|
73
|
+
CONTEXT_QUERY: "kg:context:query",
|
|
74
|
+
TOKEN_USAGE: "kg:token:usage",
|
|
75
|
+
CACHE_HIT: "kg:cache:hit",
|
|
76
|
+
CACHE_MISS: "kg:cache:miss",
|
|
77
|
+
};
|
|
78
|
+
export default RuntimeEventBus;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety Middleware for LLM Requests
|
|
3
|
+
* Detects and redacts PII and secrets before passing to LLM providers
|
|
4
|
+
*/
|
|
5
|
+
type SafetyPattern = {
|
|
6
|
+
name: string;
|
|
7
|
+
regex: RegExp;
|
|
8
|
+
};
|
|
9
|
+
type Redaction = {
|
|
10
|
+
type: string;
|
|
11
|
+
value: string;
|
|
12
|
+
position: number;
|
|
13
|
+
length: number;
|
|
14
|
+
};
|
|
15
|
+
export type SafetyMessage = {
|
|
16
|
+
role: string;
|
|
17
|
+
content: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
export type SafetyRequest = {
|
|
21
|
+
messages: SafetyMessage[];
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
type AuditEvent = {
|
|
26
|
+
timestamp: string;
|
|
27
|
+
redactionCount: number;
|
|
28
|
+
redactionTypes: string[];
|
|
29
|
+
hasSecrets: boolean;
|
|
30
|
+
hasPII: boolean;
|
|
31
|
+
processingTime?: number;
|
|
32
|
+
};
|
|
33
|
+
type BlockEvent = {
|
|
34
|
+
reason: string;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
redactionCount: number;
|
|
37
|
+
redactionTypes: string[];
|
|
38
|
+
};
|
|
39
|
+
type SafetyOptions = {
|
|
40
|
+
blockOnSecrets?: boolean;
|
|
41
|
+
blockOnPII?: boolean;
|
|
42
|
+
onAudit?: ((event: AuditEvent) => void) | null;
|
|
43
|
+
onBlock?: ((event: BlockEvent) => void) | null;
|
|
44
|
+
};
|
|
45
|
+
export declare const PII_PATTERNS: SafetyPattern[];
|
|
46
|
+
export declare const SECRET_PATTERNS: SafetyPattern[];
|
|
47
|
+
/**
|
|
48
|
+
* Redact sensitive information from text
|
|
49
|
+
* @param {string} text - Text to scan and redact
|
|
50
|
+
* @returns {{ output: string, redactions: Array<{type: string, value: string, position: number}> }}
|
|
51
|
+
*/
|
|
52
|
+
export declare function redact(text: string): {
|
|
53
|
+
output: string;
|
|
54
|
+
redactions: Redaction[];
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Redact sensitive information from messages array
|
|
58
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
59
|
+
* @returns {{safeMessages: Array, totalRedactions: Array}}
|
|
60
|
+
*/
|
|
61
|
+
export declare function redactMessages(messages: SafetyMessage[]): {
|
|
62
|
+
safeMessages: {
|
|
63
|
+
content: string;
|
|
64
|
+
role: string;
|
|
65
|
+
}[];
|
|
66
|
+
totalRedactions: {
|
|
67
|
+
messageIndex: number;
|
|
68
|
+
role: string;
|
|
69
|
+
redactions: Redaction[];
|
|
70
|
+
}[];
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Higher-order function to wrap LLM handlers with safety middleware
|
|
74
|
+
* @param {Function} handler - Original LLM handler function
|
|
75
|
+
* @param {Object} options - Configuration options
|
|
76
|
+
* @param {boolean} options.blockOnSecrets - Block requests containing secrets
|
|
77
|
+
* @param {boolean} options.blockOnPII - Block requests containing PII
|
|
78
|
+
* @param {Function} options.onAudit - Audit callback function
|
|
79
|
+
* @param {Function} options.onBlock - Called when request is blocked
|
|
80
|
+
* @returns {Function} - Wrapped handler function
|
|
81
|
+
*/
|
|
82
|
+
export declare function withSafetyMiddleware(handler: (req: SafetyRequest) => Promise<unknown>, options?: SafetyOptions): (req: SafetyRequest) => Promise<unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* Create a safe LLM wrapper with default audit logging
|
|
85
|
+
* @param {Function} handler - Original LLM handler
|
|
86
|
+
* @param {Object} options - Options
|
|
87
|
+
* @returns {Function} - Safe wrapped handler
|
|
88
|
+
*/
|
|
89
|
+
export declare function createSafeLLM(handler: (req: SafetyRequest) => Promise<unknown>, options?: SafetyOptions): (req: SafetyRequest) => Promise<unknown>;
|
|
90
|
+
declare const _default: {
|
|
91
|
+
redact: typeof redact;
|
|
92
|
+
redactMessages: typeof redactMessages;
|
|
93
|
+
withSafetyMiddleware: typeof withSafetyMiddleware;
|
|
94
|
+
createSafeLLM: typeof createSafeLLM;
|
|
95
|
+
PII_PATTERNS: SafetyPattern[];
|
|
96
|
+
SECRET_PATTERNS: SafetyPattern[];
|
|
97
|
+
};
|
|
98
|
+
export default _default;
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety Middleware for LLM Requests
|
|
3
|
+
* Detects and redacts PII and secrets before passing to LLM providers
|
|
4
|
+
*/
|
|
5
|
+
// PII Patterns - can be upgraded with stronger libraries like zod-validation or microsoft-presidio
|
|
6
|
+
export const PII_PATTERNS = [
|
|
7
|
+
{ name: "email", regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi },
|
|
8
|
+
{ name: "phone", regex: /\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b/g },
|
|
9
|
+
{ name: "credit_card", regex: /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/g },
|
|
10
|
+
{ name: "ssn", regex: /\b\d{3}-\d{2}-\d{4}\b/g },
|
|
11
|
+
{ name: "ip_address", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
|
|
12
|
+
];
|
|
13
|
+
// Secret Patterns - API keys, tokens, etc.
|
|
14
|
+
export const SECRET_PATTERNS = [
|
|
15
|
+
{ name: "openai_key", regex: /sk-[a-zA-Z0-9]{20,}/g },
|
|
16
|
+
{ name: "anthropic_key", regex: /sk-ant-[a-zA-Z0-9-_]{20,}/g },
|
|
17
|
+
{ name: "aws_key", regex: /AKIA[0-9A-Z]{16}/g },
|
|
18
|
+
{
|
|
19
|
+
name: "aws_secret",
|
|
20
|
+
regex: /(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY)\s*[:=]\s*['"]?([A-Za-z0-9/+=]{40})['"]?/g,
|
|
21
|
+
},
|
|
22
|
+
{ name: "jwt", regex: /eyJ[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+/g },
|
|
23
|
+
{ name: "github_token", regex: /gh[pousr]_[A-Za-z0-9_]{36,}/g },
|
|
24
|
+
{ name: "stripe_key", regex: /sk_(live|test)_[0-9a-zA-Z]{24,}/g },
|
|
25
|
+
{ name: "huggingface_key", regex: /hf_[a-zA-Z0-9]{20,}/g },
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Redact sensitive information from text
|
|
29
|
+
* @param {string} text - Text to scan and redact
|
|
30
|
+
* @returns {{ output: string, redactions: Array<{type: string, value: string, position: number}> }}
|
|
31
|
+
*/
|
|
32
|
+
export function redact(text) {
|
|
33
|
+
if (!text || typeof text !== "string") {
|
|
34
|
+
return { output: text, redactions: [] };
|
|
35
|
+
}
|
|
36
|
+
let redactions = [];
|
|
37
|
+
let output = text;
|
|
38
|
+
const allPatterns = [...PII_PATTERNS, ...SECRET_PATTERNS];
|
|
39
|
+
for (const pattern of allPatterns) {
|
|
40
|
+
const matches = [
|
|
41
|
+
...text.matchAll(new RegExp(pattern.regex.source, pattern.regex.flags)),
|
|
42
|
+
];
|
|
43
|
+
for (const match of matches) {
|
|
44
|
+
const originalValue = match[0];
|
|
45
|
+
const position = match.index;
|
|
46
|
+
redactions.push({
|
|
47
|
+
type: pattern.name,
|
|
48
|
+
value: originalValue,
|
|
49
|
+
position: position,
|
|
50
|
+
length: originalValue.length,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Sort by position to replace from end to start (prevents offset issues)
|
|
55
|
+
redactions.sort((a, b) => b.position - a.position);
|
|
56
|
+
// Replace all matches
|
|
57
|
+
for (const redaction of redactions) {
|
|
58
|
+
const placeholder = `[REDACTED:${redaction.type}]`;
|
|
59
|
+
output =
|
|
60
|
+
output.substring(0, redaction.position) +
|
|
61
|
+
placeholder +
|
|
62
|
+
output.substring(redaction.position + redaction.length);
|
|
63
|
+
}
|
|
64
|
+
// Reverse to chronological order for logging
|
|
65
|
+
redactions.reverse();
|
|
66
|
+
return { output, redactions };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Redact sensitive information from messages array
|
|
70
|
+
* @param {Array<{role: string, content: string}>} messages
|
|
71
|
+
* @returns {{safeMessages: Array, totalRedactions: Array}}
|
|
72
|
+
*/
|
|
73
|
+
export function redactMessages(messages) {
|
|
74
|
+
let totalRedactions = [];
|
|
75
|
+
const safeMessages = messages.map((msg, index) => {
|
|
76
|
+
const { output, redactions } = redact(msg.content);
|
|
77
|
+
if (redactions.length > 0) {
|
|
78
|
+
totalRedactions.push({
|
|
79
|
+
messageIndex: index,
|
|
80
|
+
role: msg.role,
|
|
81
|
+
redactions: redactions,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return { ...msg, content: output };
|
|
85
|
+
});
|
|
86
|
+
return { safeMessages, totalRedactions };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Higher-order function to wrap LLM handlers with safety middleware
|
|
90
|
+
* @param {Function} handler - Original LLM handler function
|
|
91
|
+
* @param {Object} options - Configuration options
|
|
92
|
+
* @param {boolean} options.blockOnSecrets - Block requests containing secrets
|
|
93
|
+
* @param {boolean} options.blockOnPII - Block requests containing PII
|
|
94
|
+
* @param {Function} options.onAudit - Audit callback function
|
|
95
|
+
* @param {Function} options.onBlock - Called when request is blocked
|
|
96
|
+
* @returns {Function} - Wrapped handler function
|
|
97
|
+
*/
|
|
98
|
+
export function withSafetyMiddleware(
|
|
99
|
+
// eslint-disable-next-line no-unused-vars
|
|
100
|
+
handler, options = {}) {
|
|
101
|
+
const { blockOnSecrets = false, blockOnPII = false, onAudit = null, onBlock = null, } = options;
|
|
102
|
+
return async (req) => {
|
|
103
|
+
const startTime = Date.now();
|
|
104
|
+
let totalRedactions = [];
|
|
105
|
+
// Redact messages
|
|
106
|
+
const safeMessages = req.messages.map((msg) => {
|
|
107
|
+
const { output, redactions } = redact(msg.content);
|
|
108
|
+
totalRedactions.push(...redactions);
|
|
109
|
+
return { ...msg, content: output };
|
|
110
|
+
});
|
|
111
|
+
// Check for blocking conditions
|
|
112
|
+
const secretTypes = [
|
|
113
|
+
"openai_key",
|
|
114
|
+
"anthropic_key",
|
|
115
|
+
"aws_key",
|
|
116
|
+
"aws_secret",
|
|
117
|
+
"jwt",
|
|
118
|
+
"github_token",
|
|
119
|
+
"stripe_key",
|
|
120
|
+
"huggingface_key",
|
|
121
|
+
];
|
|
122
|
+
const piiTypes = ["email", "phone", "credit_card", "ssn", "ip_address"];
|
|
123
|
+
const hasSecrets = totalRedactions.some((r) => secretTypes.includes(r.type));
|
|
124
|
+
const hasPII = totalRedactions.some((r) => piiTypes.includes(r.type));
|
|
125
|
+
// 🚨 Fail-closed if secrets detected
|
|
126
|
+
if (blockOnSecrets && hasSecrets) {
|
|
127
|
+
const error = new Error("Request blocked: secrets detected in prompt");
|
|
128
|
+
error.code = "SECURITY_BLOCK_SECRETS";
|
|
129
|
+
error.redactionTypes = [...new Set(totalRedactions.map((r) => r.type))];
|
|
130
|
+
if (onBlock) {
|
|
131
|
+
onBlock({
|
|
132
|
+
reason: "secrets_detected",
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
redactionCount: totalRedactions.length,
|
|
135
|
+
redactionTypes: error.redactionTypes,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
// 🚨 Fail-closed if PII detected
|
|
141
|
+
if (blockOnPII && hasPII) {
|
|
142
|
+
const error = new Error("Request blocked: PII detected in prompt");
|
|
143
|
+
error.code = "SECURITY_BLOCK_PII";
|
|
144
|
+
error.redactionTypes = [...new Set(totalRedactions.map((r) => r.type))];
|
|
145
|
+
if (onBlock) {
|
|
146
|
+
onBlock({
|
|
147
|
+
reason: "pii_detected",
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
redactionCount: totalRedactions.length,
|
|
150
|
+
redactionTypes: error.redactionTypes,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
// 🔍 Audit (no raw PII/secrets logged!)
|
|
156
|
+
if (onAudit && totalRedactions.length > 0) {
|
|
157
|
+
onAudit({
|
|
158
|
+
timestamp: new Date().toISOString(),
|
|
159
|
+
redactionCount: totalRedactions.length,
|
|
160
|
+
redactionTypes: [...new Set(totalRedactions.map((r) => r.type))],
|
|
161
|
+
hasSecrets,
|
|
162
|
+
hasPII,
|
|
163
|
+
processingTime: Date.now() - startTime,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Call the wrapped handler with safe messages
|
|
167
|
+
const response = await handler({
|
|
168
|
+
...req,
|
|
169
|
+
messages: safeMessages,
|
|
170
|
+
metadata: {
|
|
171
|
+
...req.metadata,
|
|
172
|
+
redacted: totalRedactions.length > 0,
|
|
173
|
+
redactionCount: totalRedactions.length,
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
return response;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Create a safe LLM wrapper with default audit logging
|
|
181
|
+
* @param {Function} handler - Original LLM handler
|
|
182
|
+
* @param {Object} options - Options
|
|
183
|
+
* @returns {Function} - Safe wrapped handler
|
|
184
|
+
*/
|
|
185
|
+
export function createSafeLLM(
|
|
186
|
+
// eslint-disable-next-line no-unused-vars
|
|
187
|
+
handler, options = {}) {
|
|
188
|
+
const defaultAudit = (event) => {
|
|
189
|
+
if (event.redactionCount > 0) {
|
|
190
|
+
console.warn("[Safety Middleware]", {
|
|
191
|
+
redacted: event.redactionCount,
|
|
192
|
+
types: event.redactionTypes,
|
|
193
|
+
time: event.timestamp,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
return withSafetyMiddleware(handler, {
|
|
198
|
+
blockOnSecrets: true,
|
|
199
|
+
blockOnPII: false,
|
|
200
|
+
onAudit: options.onAudit || defaultAudit,
|
|
201
|
+
onBlock: options.onBlock ||
|
|
202
|
+
((event) => {
|
|
203
|
+
console.error("[Safety Middleware] BLOCKED:", event);
|
|
204
|
+
}),
|
|
205
|
+
...options,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
export default {
|
|
209
|
+
redact,
|
|
210
|
+
redactMessages,
|
|
211
|
+
withSafetyMiddleware,
|
|
212
|
+
createSafeLLM,
|
|
213
|
+
PII_PATTERNS,
|
|
214
|
+
SECRET_PATTERNS,
|
|
215
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
type FileSystemOptions = {
|
|
2
|
+
recursive?: boolean;
|
|
3
|
+
};
|
|
4
|
+
type DialogOpenOptions = {
|
|
5
|
+
directory?: boolean;
|
|
6
|
+
multiple?: boolean;
|
|
7
|
+
title?: string;
|
|
8
|
+
defaultPath?: string;
|
|
9
|
+
recursive?: boolean;
|
|
10
|
+
canCreateDirectories?: boolean;
|
|
11
|
+
fileAccessMode?: "copy" | "scoped";
|
|
12
|
+
};
|
|
13
|
+
type FileSystemApi = {
|
|
14
|
+
readFile: (path: string) => Promise<string>;
|
|
15
|
+
writeFile: (path: string, content: string) => Promise<void>;
|
|
16
|
+
readDir: (path: string, options?: FileSystemOptions) => Promise<any>;
|
|
17
|
+
exists: (path: string) => Promise<boolean>;
|
|
18
|
+
mkdir: (path: string, options?: FileSystemOptions) => Promise<any>;
|
|
19
|
+
remove: (path: string, options?: FileSystemOptions) => Promise<any>;
|
|
20
|
+
rename: (oldPath: string, newPath: string) => Promise<any>;
|
|
21
|
+
};
|
|
22
|
+
type DialogApi = {
|
|
23
|
+
open: (options?: DialogOpenOptions) => Promise<any>;
|
|
24
|
+
save: (options?: Record<string, unknown>) => Promise<any>;
|
|
25
|
+
message: (message: string, options?: Record<string, unknown>) => Promise<any>;
|
|
26
|
+
confirm: (message: string, options?: Record<string, unknown>) => Promise<any>;
|
|
27
|
+
};
|
|
28
|
+
type ShellApi = {
|
|
29
|
+
open: (url: string) => Promise<any>;
|
|
30
|
+
};
|
|
31
|
+
type ClipboardApi = {
|
|
32
|
+
writeText: (text: unknown) => Promise<boolean>;
|
|
33
|
+
readText: () => Promise<string>;
|
|
34
|
+
};
|
|
35
|
+
type NotificationsApi = {
|
|
36
|
+
send: (title: string, body?: string) => Promise<any>;
|
|
37
|
+
requestPermission: () => Promise<"granted" | "denied" | NotificationPermission>;
|
|
38
|
+
};
|
|
39
|
+
export declare const isTauri: () => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* File system operations.
|
|
42
|
+
*/
|
|
43
|
+
export declare const fileSystem: FileSystemApi;
|
|
44
|
+
/**
|
|
45
|
+
* Dialog operations.
|
|
46
|
+
*/
|
|
47
|
+
export declare const dialog: DialogApi;
|
|
48
|
+
export declare const tauriPath: {
|
|
49
|
+
homeDir: () => Promise<any>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Shell operations.
|
|
53
|
+
*/
|
|
54
|
+
export declare const shell: ShellApi;
|
|
55
|
+
/**
|
|
56
|
+
* Clipboard operations.
|
|
57
|
+
*/
|
|
58
|
+
export declare const clipboard: ClipboardApi;
|
|
59
|
+
/**
|
|
60
|
+
* Notifications.
|
|
61
|
+
*/
|
|
62
|
+
export declare const notifications: NotificationsApi;
|
|
63
|
+
declare const _default: {
|
|
64
|
+
isTauri: () => boolean;
|
|
65
|
+
fileSystem: FileSystemApi;
|
|
66
|
+
dialog: DialogApi;
|
|
67
|
+
shell: ShellApi;
|
|
68
|
+
clipboard: ClipboardApi;
|
|
69
|
+
notifications: NotificationsApi;
|
|
70
|
+
};
|
|
71
|
+
export default _default;
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tauri integration utilities.
|
|
3
|
+
*/
|
|
4
|
+
import { tauriImport } from "./tauriImport.js";
|
|
5
|
+
export const isTauri = () => {
|
|
6
|
+
if (typeof window === "undefined") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
// In Node.js backend service, Tauri is never available
|
|
10
|
+
if (typeof window === "undefined") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const hostname = window.location?.hostname || "";
|
|
14
|
+
const protocol = window.location?.protocol || "";
|
|
15
|
+
const userAgent = window.navigator?.userAgent || "";
|
|
16
|
+
return (window.__TAURI__ !== undefined ||
|
|
17
|
+
window.__TAURI_IPC__ !== undefined ||
|
|
18
|
+
hostname === "tauri.localhost" ||
|
|
19
|
+
hostname.endsWith(".tauri.localhost") ||
|
|
20
|
+
protocol === "tauri:" ||
|
|
21
|
+
/\bTauri\b/i.test(userAgent));
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* File system operations.
|
|
25
|
+
*/
|
|
26
|
+
export const fileSystem = {
|
|
27
|
+
readFile: async (path) => {
|
|
28
|
+
if (!isTauri()) {
|
|
29
|
+
throw new Error("File system operations only available in desktop app");
|
|
30
|
+
}
|
|
31
|
+
const { readTextFile } = await tauriImport("@tauri-apps/api/fs");
|
|
32
|
+
return await readTextFile(path);
|
|
33
|
+
},
|
|
34
|
+
writeFile: async (path, content) => {
|
|
35
|
+
if (!isTauri()) {
|
|
36
|
+
throw new Error("File system operations only available in desktop app");
|
|
37
|
+
}
|
|
38
|
+
const { writeTextFile } = await tauriImport("@tauri-apps/api/fs");
|
|
39
|
+
return await writeTextFile(path, content);
|
|
40
|
+
},
|
|
41
|
+
readDir: async (path, options = {}) => {
|
|
42
|
+
if (!isTauri()) {
|
|
43
|
+
throw new Error("File system operations only available in desktop app");
|
|
44
|
+
}
|
|
45
|
+
const { readDir } = await tauriImport("@tauri-apps/api/fs");
|
|
46
|
+
return await readDir(path, options);
|
|
47
|
+
},
|
|
48
|
+
exists: async (path) => {
|
|
49
|
+
if (!isTauri())
|
|
50
|
+
return false;
|
|
51
|
+
const { exists } = await tauriImport("@tauri-apps/api/fs");
|
|
52
|
+
return await exists(path);
|
|
53
|
+
},
|
|
54
|
+
mkdir: async (path, options = {}) => {
|
|
55
|
+
if (!isTauri()) {
|
|
56
|
+
throw new Error("File system operations only available in desktop app");
|
|
57
|
+
}
|
|
58
|
+
const { mkdir } = await tauriImport("@tauri-apps/api/fs");
|
|
59
|
+
return await mkdir(path, options);
|
|
60
|
+
},
|
|
61
|
+
remove: async (path, options = {}) => {
|
|
62
|
+
if (!isTauri()) {
|
|
63
|
+
throw new Error("File system operations only available in desktop app");
|
|
64
|
+
}
|
|
65
|
+
const { remove } = await tauriImport("@tauri-apps/api/fs");
|
|
66
|
+
return await remove(path, options);
|
|
67
|
+
},
|
|
68
|
+
rename: async (oldPath, newPath) => {
|
|
69
|
+
if (!isTauri()) {
|
|
70
|
+
throw new Error("File system operations only available in desktop app");
|
|
71
|
+
}
|
|
72
|
+
const { rename } = await tauriImport("@tauri-apps/api/fs");
|
|
73
|
+
return await rename(oldPath, newPath);
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Dialog operations.
|
|
78
|
+
*/
|
|
79
|
+
export const dialog = {
|
|
80
|
+
open: async (options = {}) => {
|
|
81
|
+
if (!isTauri()) {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
const input = document.createElement("input");
|
|
84
|
+
input.type = "file";
|
|
85
|
+
if (options.directory) {
|
|
86
|
+
input.webkitdirectory = true;
|
|
87
|
+
input.setAttribute("directory", "");
|
|
88
|
+
}
|
|
89
|
+
input.multiple = options.multiple || false;
|
|
90
|
+
input.onchange = (e) => {
|
|
91
|
+
const target = e.target;
|
|
92
|
+
const files = Array.from(target?.files || []);
|
|
93
|
+
resolve(files.length === 1 && !options.multiple ? files[0] : files);
|
|
94
|
+
};
|
|
95
|
+
input.click();
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const { open } = await tauriImport("@tauri-apps/api/dialog");
|
|
99
|
+
return await open(options);
|
|
100
|
+
},
|
|
101
|
+
save: async (options = {}) => {
|
|
102
|
+
if (!isTauri()) {
|
|
103
|
+
throw new Error("Save dialog only available in desktop app");
|
|
104
|
+
}
|
|
105
|
+
const { save } = await tauriImport("@tauri-apps/api/dialog");
|
|
106
|
+
return await save(options);
|
|
107
|
+
},
|
|
108
|
+
message: async (message, options = {}) => {
|
|
109
|
+
if (!isTauri()) {
|
|
110
|
+
alert(message);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const { message: showMessage } = await tauriImport("@tauri-apps/api/dialog");
|
|
114
|
+
return await showMessage(message, options);
|
|
115
|
+
},
|
|
116
|
+
confirm: async (message, options = {}) => {
|
|
117
|
+
if (!isTauri()) {
|
|
118
|
+
return confirm(message);
|
|
119
|
+
}
|
|
120
|
+
const { confirm: showConfirm } = await tauriImport("@tauri-apps/api/dialog");
|
|
121
|
+
return await showConfirm(message, options);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
export const tauriPath = {
|
|
125
|
+
homeDir: async () => {
|
|
126
|
+
if (!isTauri()) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
const { homeDir } = await tauriImport("@tauri-apps/api/path");
|
|
130
|
+
return await homeDir();
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Shell operations.
|
|
135
|
+
*/
|
|
136
|
+
export const shell = {
|
|
137
|
+
open: async (url) => {
|
|
138
|
+
if (!isTauri()) {
|
|
139
|
+
window.open(url, "_blank");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const { open } = await tauriImport("@tauri-apps/api/shell");
|
|
143
|
+
return await open(url);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* Clipboard operations.
|
|
148
|
+
*/
|
|
149
|
+
export const clipboard = {
|
|
150
|
+
writeText: async (text) => {
|
|
151
|
+
const value = typeof text === "string" ? text : String(text ?? "");
|
|
152
|
+
// 1) Prefer native Tauri clipboard in desktop builds.
|
|
153
|
+
if (isTauri()) {
|
|
154
|
+
try {
|
|
155
|
+
const { writeText } = await tauriImport("@tauri-apps/api/clipboard");
|
|
156
|
+
await writeText(value);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.warn("Tauri clipboard write failed, trying web fallback", error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// 2) Standard async clipboard API.
|
|
164
|
+
try {
|
|
165
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
166
|
+
await navigator.clipboard.writeText(value);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.warn("navigator.clipboard.writeText failed", error);
|
|
172
|
+
}
|
|
173
|
+
throw new Error("Clipboard write is unavailable in this environment");
|
|
174
|
+
},
|
|
175
|
+
readText: async () => {
|
|
176
|
+
if (!isTauri()) {
|
|
177
|
+
if (navigator.clipboard) {
|
|
178
|
+
return await navigator.clipboard.readText();
|
|
179
|
+
}
|
|
180
|
+
return "";
|
|
181
|
+
}
|
|
182
|
+
const { readText } = await tauriImport("@tauri-apps/api/clipboard");
|
|
183
|
+
return await readText();
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Notifications.
|
|
188
|
+
*/
|
|
189
|
+
export const notifications = {
|
|
190
|
+
send: async (title, body) => {
|
|
191
|
+
if (!isTauri()) {
|
|
192
|
+
if ("Notification" in window && Notification.permission === "granted") {
|
|
193
|
+
new Notification(title, { body });
|
|
194
|
+
}
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const { sendNotification } = await tauriImport("@tauri-apps/api/notification");
|
|
198
|
+
return await sendNotification({ title, body });
|
|
199
|
+
},
|
|
200
|
+
requestPermission: async () => {
|
|
201
|
+
if (!isTauri()) {
|
|
202
|
+
if ("Notification" in window) {
|
|
203
|
+
return await Notification.requestPermission();
|
|
204
|
+
}
|
|
205
|
+
return "denied";
|
|
206
|
+
}
|
|
207
|
+
const { isPermissionGranted, requestPermission } = await tauriImport("@tauri-apps/api/notification");
|
|
208
|
+
let permission = await isPermissionGranted();
|
|
209
|
+
if (!permission) {
|
|
210
|
+
permission = await requestPermission();
|
|
211
|
+
}
|
|
212
|
+
return permission ? "granted" : "denied";
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
export default {
|
|
216
|
+
isTauri,
|
|
217
|
+
fileSystem,
|
|
218
|
+
dialog,
|
|
219
|
+
shell,
|
|
220
|
+
clipboard,
|
|
221
|
+
notifications,
|
|
222
|
+
};
|