@shykaruu/jarvis-brain 0.4.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 +153 -0
- package/README.md +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +261 -0
- package/src/actions/browser/session.ts +506 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +363 -0
- package/src/actions/tools/builtin.ts +950 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +301 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/dashboard-auth.ts +75 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/dashboard-auth.test.ts +170 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +407 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +120 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +218 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +195 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileWatcher - Monitors file system changes
|
|
3
|
+
*
|
|
4
|
+
* Watches specified directories recursively and emits events when files change.
|
|
5
|
+
* Includes debouncing to avoid duplicate rapid-fire events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { watch, type FSWatcher } from 'node:fs';
|
|
9
|
+
import type { Observer, ObserverEvent, ObserverEventHandler } from './index';
|
|
10
|
+
|
|
11
|
+
type DebounceEntry = {
|
|
12
|
+
path: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class FileWatcher implements Observer {
|
|
17
|
+
name = 'file-watcher';
|
|
18
|
+
private watchers: FSWatcher[] = [];
|
|
19
|
+
private paths: string[];
|
|
20
|
+
private handler: ObserverEventHandler | null = null;
|
|
21
|
+
private running = false;
|
|
22
|
+
private recentChanges: Map<string, number> = new Map();
|
|
23
|
+
private debounceMs = 100; // Ignore duplicate events within 100ms
|
|
24
|
+
|
|
25
|
+
constructor(paths: string[]) {
|
|
26
|
+
this.paths = paths;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async start(): Promise<void> {
|
|
30
|
+
if (this.running) {
|
|
31
|
+
console.log('[file-watcher] Already running');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log('[file-watcher] Starting file system monitoring...');
|
|
36
|
+
|
|
37
|
+
for (const path of this.paths) {
|
|
38
|
+
try {
|
|
39
|
+
const watcher = watch(path, { recursive: true }, (eventType, filename) => {
|
|
40
|
+
this.handleFileEvent(path, eventType, filename);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
watcher.on('error', (error) => {
|
|
44
|
+
console.error(`[file-watcher] Error watching ${path}:`, error);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.watchers.push(watcher);
|
|
48
|
+
console.log(`[file-watcher] Watching: ${path}`);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(`[file-watcher] Failed to watch ${path}:`, error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.running = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async stop(): Promise<void> {
|
|
58
|
+
if (!this.running) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log('[file-watcher] Stopping file system monitoring...');
|
|
63
|
+
|
|
64
|
+
for (const watcher of this.watchers) {
|
|
65
|
+
watcher.close();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.watchers = [];
|
|
69
|
+
this.recentChanges.clear();
|
|
70
|
+
this.running = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isRunning(): boolean {
|
|
74
|
+
return this.running;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
78
|
+
this.handler = handler;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private handleFileEvent(
|
|
82
|
+
basePath: string,
|
|
83
|
+
eventType: 'rename' | 'change',
|
|
84
|
+
filename: string | null
|
|
85
|
+
): void {
|
|
86
|
+
if (!filename || !this.handler) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const fullPath = `${basePath}/${filename}`;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
|
|
93
|
+
// Debounce: skip if same file changed within debounceMs
|
|
94
|
+
const lastChange = this.recentChanges.get(fullPath);
|
|
95
|
+
if (lastChange && now - lastChange < this.debounceMs) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.recentChanges.set(fullPath, now);
|
|
100
|
+
|
|
101
|
+
// Clean up old entries to prevent memory leak
|
|
102
|
+
if (this.recentChanges.size > 1000) {
|
|
103
|
+
const cutoff = now - this.debounceMs * 2;
|
|
104
|
+
for (const [path, timestamp] of this.recentChanges.entries()) {
|
|
105
|
+
if (timestamp < cutoff) {
|
|
106
|
+
this.recentChanges.delete(path);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const event: ObserverEvent = {
|
|
112
|
+
type: 'file_change',
|
|
113
|
+
data: {
|
|
114
|
+
path: fullPath,
|
|
115
|
+
eventType,
|
|
116
|
+
filename,
|
|
117
|
+
basePath,
|
|
118
|
+
},
|
|
119
|
+
timestamp: now,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
this.handler(event);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observer Layer - Monitors system events and emits observations to the Vault
|
|
3
|
+
*
|
|
4
|
+
* All observers implement a common interface and emit standardized events.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Common Observer Interface
|
|
8
|
+
export type ObserverEvent = {
|
|
9
|
+
type: string;
|
|
10
|
+
data: Record<string, unknown>;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type ObserverEventHandler = (event: ObserverEvent) => void;
|
|
15
|
+
|
|
16
|
+
export interface Observer {
|
|
17
|
+
name: string;
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
stop(): Promise<void>;
|
|
20
|
+
isRunning(): boolean;
|
|
21
|
+
onEvent(handler: ObserverEventHandler): void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Export all observers
|
|
25
|
+
export { FileWatcher } from './file-watcher';
|
|
26
|
+
export { ClipboardMonitor } from './clipboard';
|
|
27
|
+
export { NotificationListener } from './notifications';
|
|
28
|
+
export { ProcessMonitor } from './processes';
|
|
29
|
+
export { CalendarSync } from './calendar';
|
|
30
|
+
export { EmailSync } from './email';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ObserverManager - Centralized coordinator for all observers
|
|
34
|
+
*/
|
|
35
|
+
export class ObserverManager {
|
|
36
|
+
private observers: Map<string, Observer> = new Map();
|
|
37
|
+
private eventHandler: ObserverEventHandler | null = null;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Register a new observer
|
|
41
|
+
*/
|
|
42
|
+
register(observer: Observer): void {
|
|
43
|
+
this.observers.set(observer.name, observer);
|
|
44
|
+
|
|
45
|
+
// If we already have a handler, apply it to the new observer
|
|
46
|
+
if (this.eventHandler) {
|
|
47
|
+
observer.onEvent(this.eventHandler);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`[ObserverManager] Registered observer: ${observer.name}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Set the global event handler for all observers
|
|
55
|
+
* This is typically the Vault's observation ingestion function
|
|
56
|
+
*/
|
|
57
|
+
setEventHandler(handler: ObserverEventHandler): void {
|
|
58
|
+
this.eventHandler = handler;
|
|
59
|
+
|
|
60
|
+
// Apply handler to all registered observers
|
|
61
|
+
for (const observer of this.observers.values()) {
|
|
62
|
+
observer.onEvent(handler);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log(`[ObserverManager] Event handler configured for ${this.observers.size} observers`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Start all registered observers
|
|
70
|
+
*/
|
|
71
|
+
async startAll(): Promise<void> {
|
|
72
|
+
console.log('[ObserverManager] Starting all observers...');
|
|
73
|
+
|
|
74
|
+
const promises = Array.from(this.observers.values()).map(async (observer) => {
|
|
75
|
+
try {
|
|
76
|
+
await observer.start();
|
|
77
|
+
console.log(`[ObserverManager] ✓ Started ${observer.name}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error(`[ObserverManager] ✗ Failed to start ${observer.name}:`, error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await Promise.all(promises);
|
|
84
|
+
console.log('[ObserverManager] All observers started');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Stop all registered observers
|
|
89
|
+
*/
|
|
90
|
+
async stopAll(): Promise<void> {
|
|
91
|
+
console.log('[ObserverManager] Stopping all observers...');
|
|
92
|
+
|
|
93
|
+
const promises = Array.from(this.observers.values()).map(async (observer) => {
|
|
94
|
+
try {
|
|
95
|
+
await observer.stop();
|
|
96
|
+
console.log(`[ObserverManager] ✓ Stopped ${observer.name}`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
console.error(`[ObserverManager] ✗ Failed to stop ${observer.name}:`, error);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await Promise.all(promises);
|
|
103
|
+
console.log('[ObserverManager] All observers stopped');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Start a specific observer by name
|
|
108
|
+
*/
|
|
109
|
+
async startObserver(name: string): Promise<void> {
|
|
110
|
+
const observer = this.observers.get(name);
|
|
111
|
+
if (!observer) {
|
|
112
|
+
throw new Error(`Observer not found: ${name}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (observer.isRunning()) {
|
|
116
|
+
console.log(`[ObserverManager] Observer ${name} is already running`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
await observer.start();
|
|
121
|
+
console.log(`[ObserverManager] Started ${name}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Stop a specific observer by name
|
|
126
|
+
*/
|
|
127
|
+
async stopObserver(name: string): Promise<void> {
|
|
128
|
+
const observer = this.observers.get(name);
|
|
129
|
+
if (!observer) {
|
|
130
|
+
throw new Error(`Observer not found: ${name}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!observer.isRunning()) {
|
|
134
|
+
console.log(`[ObserverManager] Observer ${name} is not running`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await observer.stop();
|
|
139
|
+
console.log(`[ObserverManager] Stopped ${name}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get running status of all observers
|
|
144
|
+
*/
|
|
145
|
+
getStatus(): Record<string, boolean> {
|
|
146
|
+
const status: Record<string, boolean> = {};
|
|
147
|
+
for (const [name, observer] of this.observers) {
|
|
148
|
+
status[name] = observer.isRunning();
|
|
149
|
+
}
|
|
150
|
+
return status;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* List all registered observer names
|
|
155
|
+
*/
|
|
156
|
+
listObservers(): string[] {
|
|
157
|
+
return Array.from(this.observers.keys());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationListener — D-Bus Notification Monitor (Linux/WSL2)
|
|
3
|
+
*
|
|
4
|
+
* Monitors system notifications by watching D-Bus for
|
|
5
|
+
* org.freedesktop.Notifications.Notify method calls.
|
|
6
|
+
* Parses notification fields: app_name, summary, body, urgency.
|
|
7
|
+
*
|
|
8
|
+
* Graceful: if dbus-monitor is not found, logs warning and stays no-op.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Observer, ObserverEventHandler } from './index';
|
|
12
|
+
import type { Subprocess } from 'bun';
|
|
13
|
+
|
|
14
|
+
type NotificationData = {
|
|
15
|
+
app: string;
|
|
16
|
+
title: string;
|
|
17
|
+
body: string;
|
|
18
|
+
urgency: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export class NotificationListener implements Observer {
|
|
22
|
+
name = 'notifications';
|
|
23
|
+
private running = false;
|
|
24
|
+
private handler: ObserverEventHandler | null = null;
|
|
25
|
+
private process: Subprocess | null = null;
|
|
26
|
+
private available = false;
|
|
27
|
+
|
|
28
|
+
async start(): Promise<void> {
|
|
29
|
+
this.running = true;
|
|
30
|
+
|
|
31
|
+
// Check if dbus-monitor exists
|
|
32
|
+
try {
|
|
33
|
+
const check = Bun.spawnSync(['which', 'dbus-monitor']);
|
|
34
|
+
if (check.exitCode !== 0) {
|
|
35
|
+
console.log('[notifications] dbus-monitor not found — notification monitoring disabled');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.available = true;
|
|
39
|
+
} catch {
|
|
40
|
+
console.log('[notifications] Cannot check for dbus-monitor — notification monitoring disabled');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Spawn dbus-monitor watching for Notify signals
|
|
45
|
+
try {
|
|
46
|
+
this.process = Bun.spawn(
|
|
47
|
+
[
|
|
48
|
+
'dbus-monitor',
|
|
49
|
+
'--session',
|
|
50
|
+
"interface='org.freedesktop.Notifications',member='Notify'",
|
|
51
|
+
],
|
|
52
|
+
{
|
|
53
|
+
stdout: 'pipe',
|
|
54
|
+
stderr: 'ignore',
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
console.log('[notifications] Observer started — monitoring D-Bus notifications');
|
|
59
|
+
|
|
60
|
+
// Parse stdout in background
|
|
61
|
+
this.readOutput();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
console.error('[notifications] Failed to start dbus-monitor:', err);
|
|
64
|
+
this.available = false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async stop(): Promise<void> {
|
|
69
|
+
this.running = false;
|
|
70
|
+
|
|
71
|
+
if (this.process) {
|
|
72
|
+
try {
|
|
73
|
+
this.process.kill();
|
|
74
|
+
} catch {
|
|
75
|
+
// Ignore
|
|
76
|
+
}
|
|
77
|
+
this.process = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('[notifications] Observer stopped');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isRunning(): boolean {
|
|
84
|
+
return this.running;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
onEvent(handler: ObserverEventHandler): void {
|
|
88
|
+
this.handler = handler;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read dbus-monitor output and parse notification blocks.
|
|
93
|
+
*
|
|
94
|
+
* D-Bus notification format (method_call):
|
|
95
|
+
* method call ... dest=org.freedesktop.Notifications ... member=Notify
|
|
96
|
+
* string "app_name"
|
|
97
|
+
* uint32 ...
|
|
98
|
+
* string "icon"
|
|
99
|
+
* string "summary"
|
|
100
|
+
* string "body"
|
|
101
|
+
* array [ ... ]
|
|
102
|
+
* ...
|
|
103
|
+
*/
|
|
104
|
+
private async readOutput(): Promise<void> {
|
|
105
|
+
if (!this.process?.stdout) return;
|
|
106
|
+
|
|
107
|
+
const reader = (this.process.stdout as ReadableStream<Uint8Array>).getReader();
|
|
108
|
+
const decoder = new TextDecoder();
|
|
109
|
+
let buffer = '';
|
|
110
|
+
|
|
111
|
+
// State machine for parsing method_call blocks
|
|
112
|
+
let inMethodCall = false;
|
|
113
|
+
let stringIndex = 0;
|
|
114
|
+
let currentNotification: Partial<NotificationData> = {};
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
while (this.running) {
|
|
118
|
+
const { done, value } = await reader.read();
|
|
119
|
+
if (done) break;
|
|
120
|
+
|
|
121
|
+
buffer += decoder.decode(value, { stream: true });
|
|
122
|
+
const lines = buffer.split('\n');
|
|
123
|
+
buffer = lines.pop() ?? '';
|
|
124
|
+
|
|
125
|
+
for (const line of lines) {
|
|
126
|
+
const trimmed = line.trim();
|
|
127
|
+
|
|
128
|
+
// Detect start of a new method call
|
|
129
|
+
if (trimmed.startsWith('method call') && trimmed.includes('member=Notify')) {
|
|
130
|
+
inMethodCall = true;
|
|
131
|
+
stringIndex = 0;
|
|
132
|
+
currentNotification = {};
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Detect start of next method/signal (end of current block)
|
|
137
|
+
if (inMethodCall && (trimmed.startsWith('method call') || trimmed.startsWith('signal') || trimmed.startsWith('method return'))) {
|
|
138
|
+
// Emit the notification if we got enough data
|
|
139
|
+
this.emitNotification(currentNotification);
|
|
140
|
+
inMethodCall = trimmed.startsWith('method call') && trimmed.includes('member=Notify');
|
|
141
|
+
stringIndex = 0;
|
|
142
|
+
currentNotification = {};
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!inMethodCall) continue;
|
|
147
|
+
|
|
148
|
+
// Parse string values — the Notify method has these string args in order:
|
|
149
|
+
// 0: app_name, 1: (replaces_id is uint32, skip), 2: icon, 3: summary, 4: body
|
|
150
|
+
const stringMatch = trimmed.match(/^string\s+"(.*)"/);
|
|
151
|
+
if (stringMatch) {
|
|
152
|
+
const val = stringMatch[1];
|
|
153
|
+
switch (stringIndex) {
|
|
154
|
+
case 0: currentNotification.app = val; break;
|
|
155
|
+
// index 1 is icon (we skip)
|
|
156
|
+
case 1: break; // icon
|
|
157
|
+
case 2: currentNotification.title = val; break;
|
|
158
|
+
case 3: currentNotification.body = val; break;
|
|
159
|
+
}
|
|
160
|
+
stringIndex++;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Parse urgency from hints dict (byte value)
|
|
165
|
+
const urgencyMatch = trimmed.match(/byte\s+(\d+)/);
|
|
166
|
+
if (urgencyMatch) {
|
|
167
|
+
const urgencyLevel = parseInt(urgencyMatch[1]!);
|
|
168
|
+
currentNotification.urgency =
|
|
169
|
+
urgencyLevel === 2 ? 'critical' :
|
|
170
|
+
urgencyLevel === 1 ? 'normal' :
|
|
171
|
+
'low';
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (err) {
|
|
176
|
+
if (this.running) {
|
|
177
|
+
console.error('[notifications] Read error:', err);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private emitNotification(data: Partial<NotificationData>): void {
|
|
183
|
+
if (!data.title && !data.body) return;
|
|
184
|
+
if (!this.handler) return;
|
|
185
|
+
|
|
186
|
+
this.handler({
|
|
187
|
+
type: 'notification',
|
|
188
|
+
data: {
|
|
189
|
+
app: data.app ?? 'unknown',
|
|
190
|
+
title: data.title ?? '',
|
|
191
|
+
body: data.body ?? '',
|
|
192
|
+
urgency: data.urgency ?? 'normal',
|
|
193
|
+
},
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Observer Layer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { test, expect, describe, beforeAll, afterAll } from 'bun:test';
|
|
6
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
ObserverManager,
|
|
11
|
+
FileWatcher,
|
|
12
|
+
ClipboardMonitor,
|
|
13
|
+
ProcessMonitor,
|
|
14
|
+
NotificationListener,
|
|
15
|
+
CalendarSync,
|
|
16
|
+
EmailSync,
|
|
17
|
+
type ObserverEvent,
|
|
18
|
+
} from './index';
|
|
19
|
+
|
|
20
|
+
// Use an isolated temp dir instead of /tmp to avoid slow recursive watches on CI
|
|
21
|
+
let testDir: string;
|
|
22
|
+
beforeAll(() => {
|
|
23
|
+
testDir = mkdtempSync(join(tmpdir(), 'jarvis-test-'));
|
|
24
|
+
});
|
|
25
|
+
afterAll(() => {
|
|
26
|
+
try { rmSync(testDir, { recursive: true }); } catch {}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('ObserverManager', () => {
|
|
30
|
+
test('registers observers', () => {
|
|
31
|
+
const manager = new ObserverManager();
|
|
32
|
+
const watcher = new FileWatcher([testDir]);
|
|
33
|
+
|
|
34
|
+
manager.register(watcher);
|
|
35
|
+
|
|
36
|
+
expect(manager.listObservers()).toEqual(['file-watcher']);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('propagates event handler to observers', () => {
|
|
40
|
+
const manager = new ObserverManager();
|
|
41
|
+
const watcher = new FileWatcher([testDir]);
|
|
42
|
+
|
|
43
|
+
manager.register(watcher);
|
|
44
|
+
|
|
45
|
+
let handlerCalled = false;
|
|
46
|
+
manager.setEventHandler(() => {
|
|
47
|
+
handlerCalled = true;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Handler should be set on the observer
|
|
51
|
+
expect(handlerCalled).toBe(false); // Not called yet, just registered
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('starts and stops all observers', async () => {
|
|
55
|
+
const manager = new ObserverManager();
|
|
56
|
+
const watcher = new FileWatcher([testDir]);
|
|
57
|
+
const clipboard = new ClipboardMonitor(5000);
|
|
58
|
+
|
|
59
|
+
manager.register(watcher);
|
|
60
|
+
manager.register(clipboard);
|
|
61
|
+
|
|
62
|
+
await manager.startAll();
|
|
63
|
+
|
|
64
|
+
const status = manager.getStatus();
|
|
65
|
+
expect(status['file-watcher']).toBe(true);
|
|
66
|
+
expect(status['clipboard']).toBe(true);
|
|
67
|
+
|
|
68
|
+
await manager.stopAll();
|
|
69
|
+
|
|
70
|
+
const statusAfter = manager.getStatus();
|
|
71
|
+
expect(statusAfter['file-watcher']).toBe(false);
|
|
72
|
+
expect(statusAfter['clipboard']).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('starts and stops individual observers', async () => {
|
|
76
|
+
const manager = new ObserverManager();
|
|
77
|
+
const watcher = new FileWatcher([testDir]);
|
|
78
|
+
|
|
79
|
+
manager.register(watcher);
|
|
80
|
+
|
|
81
|
+
await manager.startObserver('file-watcher');
|
|
82
|
+
expect(manager.getStatus()['file-watcher']).toBe(true);
|
|
83
|
+
|
|
84
|
+
await manager.stopObserver('file-watcher');
|
|
85
|
+
expect(manager.getStatus()['file-watcher']).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('FileWatcher', () => {
|
|
90
|
+
test('starts and stops', async () => {
|
|
91
|
+
const watcher = new FileWatcher([testDir]);
|
|
92
|
+
|
|
93
|
+
expect(watcher.isRunning()).toBe(false);
|
|
94
|
+
|
|
95
|
+
await watcher.start();
|
|
96
|
+
expect(watcher.isRunning()).toBe(true);
|
|
97
|
+
|
|
98
|
+
await watcher.stop();
|
|
99
|
+
expect(watcher.isRunning()).toBe(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('prevents double start', async () => {
|
|
103
|
+
const watcher = new FileWatcher([testDir]);
|
|
104
|
+
|
|
105
|
+
await watcher.start();
|
|
106
|
+
await watcher.start(); // Should not throw
|
|
107
|
+
|
|
108
|
+
expect(watcher.isRunning()).toBe(true);
|
|
109
|
+
|
|
110
|
+
await watcher.stop();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('ClipboardMonitor', () => {
|
|
115
|
+
test('starts and stops', async () => {
|
|
116
|
+
const clipboard = new ClipboardMonitor(5000);
|
|
117
|
+
|
|
118
|
+
expect(clipboard.isRunning()).toBe(false);
|
|
119
|
+
|
|
120
|
+
await clipboard.start();
|
|
121
|
+
expect(clipboard.isRunning()).toBe(true);
|
|
122
|
+
|
|
123
|
+
await clipboard.stop();
|
|
124
|
+
expect(clipboard.isRunning()).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('uses custom poll interval', async () => {
|
|
128
|
+
const clipboard = new ClipboardMonitor(10000);
|
|
129
|
+
|
|
130
|
+
await clipboard.start();
|
|
131
|
+
expect(clipboard.isRunning()).toBe(true);
|
|
132
|
+
|
|
133
|
+
await clipboard.stop();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('ProcessMonitor', () => {
|
|
138
|
+
test('starts and stops', async () => {
|
|
139
|
+
const monitor = new ProcessMonitor(10000);
|
|
140
|
+
|
|
141
|
+
expect(monitor.isRunning()).toBe(false);
|
|
142
|
+
|
|
143
|
+
await monitor.start();
|
|
144
|
+
expect(monitor.isRunning()).toBe(true);
|
|
145
|
+
|
|
146
|
+
await monitor.stop();
|
|
147
|
+
expect(monitor.isRunning()).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('gets process list', async () => {
|
|
151
|
+
const monitor = new ProcessMonitor(10000);
|
|
152
|
+
|
|
153
|
+
const processes = await monitor.getProcessList();
|
|
154
|
+
|
|
155
|
+
expect(Array.isArray(processes)).toBe(true);
|
|
156
|
+
expect(processes.length).toBeGreaterThan(0);
|
|
157
|
+
|
|
158
|
+
// Check structure of first process
|
|
159
|
+
const proc = processes[0];
|
|
160
|
+
expect(proc).toHaveProperty('pid');
|
|
161
|
+
expect(proc).toHaveProperty('name');
|
|
162
|
+
expect(proc).toHaveProperty('cpu');
|
|
163
|
+
expect(proc).toHaveProperty('memory');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Stub Observers', () => {
|
|
168
|
+
test('NotificationListener starts and stops', async () => {
|
|
169
|
+
const listener = new NotificationListener();
|
|
170
|
+
|
|
171
|
+
expect(listener.isRunning()).toBe(false);
|
|
172
|
+
|
|
173
|
+
await listener.start();
|
|
174
|
+
expect(listener.isRunning()).toBe(true);
|
|
175
|
+
|
|
176
|
+
await listener.stop();
|
|
177
|
+
expect(listener.isRunning()).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('CalendarSync starts and stops', async () => {
|
|
181
|
+
const sync = new CalendarSync();
|
|
182
|
+
|
|
183
|
+
expect(sync.isRunning()).toBe(false);
|
|
184
|
+
|
|
185
|
+
await sync.start();
|
|
186
|
+
expect(sync.isRunning()).toBe(true);
|
|
187
|
+
|
|
188
|
+
await sync.stop();
|
|
189
|
+
expect(sync.isRunning()).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('EmailSync starts and stops', async () => {
|
|
193
|
+
const sync = new EmailSync();
|
|
194
|
+
|
|
195
|
+
expect(sync.isRunning()).toBe(false);
|
|
196
|
+
|
|
197
|
+
await sync.start();
|
|
198
|
+
expect(sync.isRunning()).toBe(true);
|
|
199
|
+
|
|
200
|
+
await sync.stop();
|
|
201
|
+
expect(sync.isRunning()).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
});
|