@usejarvis/brain 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.
Files changed (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * ResearchQueue — Background Research Engine
3
+ *
4
+ * Manages a queue of topics for JARVIS to research during idle time.
5
+ * Topics come from conversations, explicit requests, or the agent itself.
6
+ * The heartbeat picks up the next topic when nothing urgent is happening.
7
+ */
8
+
9
+ export type ResearchPriority = 'high' | 'normal' | 'low';
10
+ export type ResearchStatus = 'queued' | 'in_progress' | 'completed' | 'failed';
11
+
12
+ export type ResearchTopic = {
13
+ id: string;
14
+ topic: string;
15
+ reason: string;
16
+ source: string; // 'user' | 'agent' | 'conversation'
17
+ priority: ResearchPriority;
18
+ status: ResearchStatus;
19
+ result?: string;
20
+ failReason?: string;
21
+ created_at: number;
22
+ completed_at?: number;
23
+ };
24
+
25
+ const MAX_QUEUE_SIZE = 50;
26
+
27
+ export class ResearchQueue {
28
+ private topics: Map<string, ResearchTopic> = new Map();
29
+
30
+ /**
31
+ * Add a topic to the research queue.
32
+ */
33
+ addTopic(
34
+ topic: string,
35
+ reason: string,
36
+ source: string = 'user',
37
+ priority: ResearchPriority = 'normal'
38
+ ): ResearchTopic {
39
+ // Enforce max queue size (drop oldest low-priority)
40
+ if (this.topics.size >= MAX_QUEUE_SIZE) {
41
+ this.evictOldest();
42
+ }
43
+
44
+ const entry: ResearchTopic = {
45
+ id: crypto.randomUUID(),
46
+ topic,
47
+ reason,
48
+ source,
49
+ priority,
50
+ status: 'queued',
51
+ created_at: Date.now(),
52
+ };
53
+
54
+ this.topics.set(entry.id, entry);
55
+ console.log(`[ResearchQueue] Added: "${topic}" (${priority}, from ${source})`);
56
+ return entry;
57
+ }
58
+
59
+ /**
60
+ * Get the next highest-priority queued topic.
61
+ */
62
+ getNext(): ResearchTopic | null {
63
+ const queued = Array.from(this.topics.values())
64
+ .filter((t) => t.status === 'queued')
65
+ .sort((a, b) => {
66
+ // Priority order: high > normal > low
67
+ const pOrder: Record<ResearchPriority, number> = { high: 0, normal: 1, low: 2 };
68
+ const pDiff = pOrder[a.priority] - pOrder[b.priority];
69
+ if (pDiff !== 0) return pDiff;
70
+ // Older topics first within same priority
71
+ return a.created_at - b.created_at;
72
+ });
73
+
74
+ return queued[0] ?? null;
75
+ }
76
+
77
+ /**
78
+ * Mark a topic as in-progress.
79
+ */
80
+ startResearch(id: string): boolean {
81
+ const topic = this.topics.get(id);
82
+ if (!topic || topic.status !== 'queued') return false;
83
+ topic.status = 'in_progress';
84
+ return true;
85
+ }
86
+
87
+ /**
88
+ * Mark a topic as completed with a result.
89
+ */
90
+ complete(id: string, result: string): boolean {
91
+ const topic = this.topics.get(id);
92
+ if (!topic) return false;
93
+ topic.status = 'completed';
94
+ topic.result = result;
95
+ topic.completed_at = Date.now();
96
+ console.log(`[ResearchQueue] Completed: "${topic.topic}"`);
97
+ return true;
98
+ }
99
+
100
+ /**
101
+ * Mark a topic as failed.
102
+ */
103
+ fail(id: string, reason: string): boolean {
104
+ const topic = this.topics.get(id);
105
+ if (!topic) return false;
106
+ topic.status = 'failed';
107
+ topic.failReason = reason;
108
+ topic.completed_at = Date.now();
109
+ console.log(`[ResearchQueue] Failed: "${topic.topic}" — ${reason}`);
110
+ return true;
111
+ }
112
+
113
+ /**
114
+ * Remove a topic from the queue.
115
+ */
116
+ remove(id: string): boolean {
117
+ return this.topics.delete(id);
118
+ }
119
+
120
+ /**
121
+ * List all topics, optionally filtered by status.
122
+ */
123
+ list(status?: ResearchStatus): ResearchTopic[] {
124
+ const all = Array.from(this.topics.values());
125
+ if (status) return all.filter((t) => t.status === status);
126
+ return all;
127
+ }
128
+
129
+ /**
130
+ * Get count of queued topics.
131
+ */
132
+ queuedCount(): number {
133
+ return Array.from(this.topics.values()).filter((t) => t.status === 'queued').length;
134
+ }
135
+
136
+ private evictOldest(): void {
137
+ // Evict oldest completed, then oldest low-priority queued
138
+ const completed = Array.from(this.topics.values())
139
+ .filter((t) => t.status === 'completed' || t.status === 'failed')
140
+ .sort((a, b) => a.created_at - b.created_at);
141
+
142
+ if (completed.length > 0) {
143
+ this.topics.delete(completed[0]!.id);
144
+ return;
145
+ }
146
+
147
+ const low = Array.from(this.topics.values())
148
+ .filter((t) => t.priority === 'low' && t.status === 'queued')
149
+ .sort((a, b) => a.created_at - b.created_at);
150
+
151
+ if (low.length > 0) {
152
+ this.topics.delete(low[0]!.id);
153
+ }
154
+ }
155
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Service Registry
3
+ *
4
+ * Manages lifecycle of all daemon services (observers, agents, WebSocket server, etc.)
5
+ * Services are started in registration order and stopped in reverse order.
6
+ */
7
+
8
+ export type ServiceStatus = 'stopped' | 'starting' | 'running' | 'stopping' | 'error';
9
+
10
+ export interface Service {
11
+ name: string;
12
+ start(): Promise<void>;
13
+ stop(): Promise<void>;
14
+ status(): ServiceStatus;
15
+ }
16
+
17
+ interface RegisteredService {
18
+ service: Service;
19
+ status: ServiceStatus;
20
+ error?: string;
21
+ }
22
+
23
+ export class ServiceRegistry {
24
+ private services = new Map<string, RegisteredService>();
25
+
26
+ /**
27
+ * Register a service
28
+ */
29
+ register(service: Service): void {
30
+ if (this.services.has(service.name)) {
31
+ throw new Error(`Service '${service.name}' is already registered`);
32
+ }
33
+ this.services.set(service.name, {
34
+ service,
35
+ status: 'stopped',
36
+ });
37
+ console.log(`[ServiceRegistry] Registered service: ${service.name}`);
38
+ }
39
+
40
+ /**
41
+ * Start all registered services in order
42
+ */
43
+ async startAll(): Promise<void> {
44
+ console.log('[ServiceRegistry] Starting all services...');
45
+ for (const [name, registered] of this.services) {
46
+ await this.startService(name);
47
+ }
48
+ console.log('[ServiceRegistry] All services started');
49
+ }
50
+
51
+ /**
52
+ * Stop all services in reverse order
53
+ */
54
+ async stopAll(): Promise<void> {
55
+ console.log('[ServiceRegistry] Stopping all services...');
56
+ const serviceNames = Array.from(this.services.keys()).reverse();
57
+ for (const name of serviceNames) {
58
+ await this.stopService(name);
59
+ }
60
+ console.log('[ServiceRegistry] All services stopped');
61
+ }
62
+
63
+ /**
64
+ * Start a specific service
65
+ */
66
+ async startService(name: string): Promise<void> {
67
+ const registered = this.services.get(name);
68
+ if (!registered) {
69
+ throw new Error(`Service '${name}' not found`);
70
+ }
71
+
72
+ if (registered.status === 'running') {
73
+ console.log(`[ServiceRegistry] Service '${name}' is already running`);
74
+ return;
75
+ }
76
+
77
+ try {
78
+ registered.status = 'starting';
79
+ this.services.set(name, registered);
80
+
81
+ console.log(`[ServiceRegistry] Starting ${name}...`);
82
+ await registered.service.start();
83
+
84
+ registered.status = 'running';
85
+ registered.error = undefined;
86
+ this.services.set(name, registered);
87
+
88
+ console.log(`[ServiceRegistry] ✓ ${name} started`);
89
+ } catch (error) {
90
+ const message = error instanceof Error ? error.message : String(error);
91
+ registered.status = 'error';
92
+ registered.error = message;
93
+ this.services.set(name, registered);
94
+
95
+ console.error(`[ServiceRegistry] ✗ Failed to start ${name}: ${message}`);
96
+ throw error;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Stop a specific service
102
+ */
103
+ async stopService(name: string): Promise<void> {
104
+ const registered = this.services.get(name);
105
+ if (!registered) {
106
+ throw new Error(`Service '${name}' not found`);
107
+ }
108
+
109
+ if (registered.status === 'stopped') {
110
+ console.log(`[ServiceRegistry] Service '${name}' is already stopped`);
111
+ return;
112
+ }
113
+
114
+ try {
115
+ registered.status = 'stopping';
116
+ this.services.set(name, registered);
117
+
118
+ console.log(`[ServiceRegistry] Stopping ${name}...`);
119
+ await registered.service.stop();
120
+
121
+ registered.status = 'stopped';
122
+ registered.error = undefined;
123
+ this.services.set(name, registered);
124
+
125
+ console.log(`[ServiceRegistry] ✓ ${name} stopped`);
126
+ } catch (error) {
127
+ const message = error instanceof Error ? error.message : String(error);
128
+ registered.status = 'error';
129
+ registered.error = message;
130
+ this.services.set(name, registered);
131
+
132
+ console.error(`[ServiceRegistry] ✗ Failed to stop ${name}: ${message}`);
133
+ throw error;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Get status of all services
139
+ */
140
+ getStatus(): Record<string, ServiceStatus> {
141
+ const status: Record<string, ServiceStatus> = {};
142
+ for (const [name, registered] of this.services) {
143
+ status[name] = registered.status;
144
+ }
145
+ return status;
146
+ }
147
+
148
+ /**
149
+ * Get a specific service
150
+ */
151
+ get(name: string): Service | undefined {
152
+ return this.services.get(name)?.service;
153
+ }
154
+
155
+ /**
156
+ * Get all registered service names
157
+ */
158
+ list(): string[] {
159
+ return Array.from(this.services.keys());
160
+ }
161
+
162
+ /**
163
+ * Check if a service is registered
164
+ */
165
+ has(name: string): boolean {
166
+ return this.services.has(name);
167
+ }
168
+
169
+ /**
170
+ * Get detailed service info including errors
171
+ */
172
+ getServiceInfo(name: string): RegisteredService | undefined {
173
+ return this.services.get(name);
174
+ }
175
+ }