@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.
Files changed (330) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +428 -0
  3. package/bin/jarvis.ts +449 -0
  4. package/package.json +79 -0
  5. package/roles/activity-observer.yaml +60 -0
  6. package/roles/ceo-founder.yaml +144 -0
  7. package/roles/chief-of-staff.yaml +158 -0
  8. package/roles/dev-lead.yaml +182 -0
  9. package/roles/executive-assistant.yaml +77 -0
  10. package/roles/marketing-director.yaml +168 -0
  11. package/roles/personal-assistant.yaml +266 -0
  12. package/roles/research-specialist.yaml +60 -0
  13. package/roles/specialists/content-writer.yaml +53 -0
  14. package/roles/specialists/customer-support.yaml +57 -0
  15. package/roles/specialists/data-analyst.yaml +57 -0
  16. package/roles/specialists/financial-analyst.yaml +56 -0
  17. package/roles/specialists/hr-specialist.yaml +55 -0
  18. package/roles/specialists/legal-advisor.yaml +58 -0
  19. package/roles/specialists/marketing-strategist.yaml +56 -0
  20. package/roles/specialists/project-coordinator.yaml +55 -0
  21. package/roles/specialists/research-analyst.yaml +58 -0
  22. package/roles/specialists/software-engineer.yaml +57 -0
  23. package/roles/specialists/system-administrator.yaml +57 -0
  24. package/roles/system-admin.yaml +76 -0
  25. package/scripts/ensure-bun.cjs +16 -0
  26. package/src/actions/README.md +421 -0
  27. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  28. package/src/actions/app-control/desktop-controller.ts +438 -0
  29. package/src/actions/app-control/interface.ts +64 -0
  30. package/src/actions/app-control/linux.ts +273 -0
  31. package/src/actions/app-control/macos.ts +54 -0
  32. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  33. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  34. package/src/actions/app-control/windows.ts +44 -0
  35. package/src/actions/browser/cdp.ts +138 -0
  36. package/src/actions/browser/chrome-launcher.ts +261 -0
  37. package/src/actions/browser/session.ts +506 -0
  38. package/src/actions/browser/stealth.ts +49 -0
  39. package/src/actions/index.ts +20 -0
  40. package/src/actions/terminal/executor.ts +157 -0
  41. package/src/actions/terminal/wsl-bridge.ts +126 -0
  42. package/src/actions/test.ts +93 -0
  43. package/src/actions/tools/agents.ts +363 -0
  44. package/src/actions/tools/builtin.ts +950 -0
  45. package/src/actions/tools/commitments.ts +192 -0
  46. package/src/actions/tools/content.ts +217 -0
  47. package/src/actions/tools/delegate.ts +147 -0
  48. package/src/actions/tools/desktop.test.ts +55 -0
  49. package/src/actions/tools/desktop.ts +305 -0
  50. package/src/actions/tools/documents.ts +169 -0
  51. package/src/actions/tools/goals.ts +376 -0
  52. package/src/actions/tools/local-tools-guard.ts +31 -0
  53. package/src/actions/tools/registry.ts +173 -0
  54. package/src/actions/tools/research.ts +111 -0
  55. package/src/actions/tools/sidecar-list.ts +57 -0
  56. package/src/actions/tools/sidecar-route.ts +105 -0
  57. package/src/actions/tools/workflows.ts +216 -0
  58. package/src/agents/agent.ts +132 -0
  59. package/src/agents/delegation.ts +107 -0
  60. package/src/agents/hierarchy.ts +113 -0
  61. package/src/agents/index.ts +19 -0
  62. package/src/agents/messaging.ts +125 -0
  63. package/src/agents/orchestrator.ts +592 -0
  64. package/src/agents/role-discovery.ts +61 -0
  65. package/src/agents/sub-agent-runner.ts +309 -0
  66. package/src/agents/task-manager.ts +151 -0
  67. package/src/authority/approval-delivery.ts +59 -0
  68. package/src/authority/approval.ts +196 -0
  69. package/src/authority/audit.ts +158 -0
  70. package/src/authority/authority.test.ts +519 -0
  71. package/src/authority/deferred-executor.ts +103 -0
  72. package/src/authority/emergency.ts +66 -0
  73. package/src/authority/engine.ts +301 -0
  74. package/src/authority/index.ts +12 -0
  75. package/src/authority/learning.ts +111 -0
  76. package/src/authority/tool-action-map.ts +74 -0
  77. package/src/awareness/analytics.ts +466 -0
  78. package/src/awareness/awareness.test.ts +332 -0
  79. package/src/awareness/capture-engine.ts +305 -0
  80. package/src/awareness/context-graph.ts +130 -0
  81. package/src/awareness/context-tracker.ts +349 -0
  82. package/src/awareness/index.ts +25 -0
  83. package/src/awareness/intelligence.ts +321 -0
  84. package/src/awareness/ocr-engine.ts +88 -0
  85. package/src/awareness/service.ts +528 -0
  86. package/src/awareness/struggle-detector.ts +342 -0
  87. package/src/awareness/suggestion-engine.ts +476 -0
  88. package/src/awareness/types.ts +201 -0
  89. package/src/cli/autostart.ts +417 -0
  90. package/src/cli/deps.ts +449 -0
  91. package/src/cli/doctor.ts +238 -0
  92. package/src/cli/helpers.ts +401 -0
  93. package/src/cli/onboard.ts +827 -0
  94. package/src/cli/uninstall.test.ts +37 -0
  95. package/src/cli/uninstall.ts +202 -0
  96. package/src/comms/README.md +329 -0
  97. package/src/comms/auth-error.html +48 -0
  98. package/src/comms/channels/discord.ts +228 -0
  99. package/src/comms/channels/signal.ts +56 -0
  100. package/src/comms/channels/telegram.ts +316 -0
  101. package/src/comms/channels/whatsapp.ts +60 -0
  102. package/src/comms/channels.test.ts +173 -0
  103. package/src/comms/dashboard-auth.ts +75 -0
  104. package/src/comms/desktop-notify.ts +114 -0
  105. package/src/comms/example.ts +129 -0
  106. package/src/comms/index.ts +129 -0
  107. package/src/comms/streaming.ts +149 -0
  108. package/src/comms/voice.test.ts +504 -0
  109. package/src/comms/voice.ts +341 -0
  110. package/src/comms/websocket.test.ts +409 -0
  111. package/src/comms/websocket.ts +669 -0
  112. package/src/config/README.md +389 -0
  113. package/src/config/index.ts +6 -0
  114. package/src/config/loader.test.ts +183 -0
  115. package/src/config/loader.ts +148 -0
  116. package/src/config/types.ts +293 -0
  117. package/src/daemon/README.md +232 -0
  118. package/src/daemon/agent-service-interface.ts +9 -0
  119. package/src/daemon/agent-service.ts +667 -0
  120. package/src/daemon/api-routes.ts +3067 -0
  121. package/src/daemon/background-agent-service.ts +396 -0
  122. package/src/daemon/background-agent.test.ts +78 -0
  123. package/src/daemon/channel-service.ts +201 -0
  124. package/src/daemon/commitment-executor.ts +297 -0
  125. package/src/daemon/dashboard-auth.test.ts +170 -0
  126. package/src/daemon/event-classifier.ts +239 -0
  127. package/src/daemon/event-coalescer.ts +123 -0
  128. package/src/daemon/event-reactor.ts +214 -0
  129. package/src/daemon/flock.c +7 -0
  130. package/src/daemon/health.ts +220 -0
  131. package/src/daemon/index.ts +1070 -0
  132. package/src/daemon/llm-settings.test.ts +78 -0
  133. package/src/daemon/llm-settings.ts +450 -0
  134. package/src/daemon/observer-service.ts +150 -0
  135. package/src/daemon/pid.test.ts +283 -0
  136. package/src/daemon/pid.ts +224 -0
  137. package/src/daemon/research-queue.ts +155 -0
  138. package/src/daemon/services.ts +175 -0
  139. package/src/daemon/ws-service.ts +926 -0
  140. package/src/global.d.ts +4 -0
  141. package/src/goals/accountability.ts +240 -0
  142. package/src/goals/awareness-bridge.ts +185 -0
  143. package/src/goals/estimator.ts +185 -0
  144. package/src/goals/events.ts +28 -0
  145. package/src/goals/goals.test.ts +400 -0
  146. package/src/goals/integration.test.ts +329 -0
  147. package/src/goals/nl-builder.test.ts +220 -0
  148. package/src/goals/nl-builder.ts +256 -0
  149. package/src/goals/rhythm.test.ts +177 -0
  150. package/src/goals/rhythm.ts +275 -0
  151. package/src/goals/service.test.ts +135 -0
  152. package/src/goals/service.ts +407 -0
  153. package/src/goals/types.ts +106 -0
  154. package/src/goals/workflow-bridge.ts +96 -0
  155. package/src/integrations/google-api.ts +134 -0
  156. package/src/integrations/google-auth.ts +175 -0
  157. package/src/llm/README.md +291 -0
  158. package/src/llm/anthropic.ts +400 -0
  159. package/src/llm/gemini.ts +380 -0
  160. package/src/llm/groq.ts +406 -0
  161. package/src/llm/history.ts +147 -0
  162. package/src/llm/index.ts +21 -0
  163. package/src/llm/manager.ts +226 -0
  164. package/src/llm/ollama.ts +316 -0
  165. package/src/llm/openai.ts +411 -0
  166. package/src/llm/openrouter.ts +390 -0
  167. package/src/llm/provider.test.ts +487 -0
  168. package/src/llm/provider.ts +61 -0
  169. package/src/llm/test.ts +88 -0
  170. package/src/observers/README.md +278 -0
  171. package/src/observers/calendar.ts +113 -0
  172. package/src/observers/clipboard.ts +136 -0
  173. package/src/observers/email.ts +109 -0
  174. package/src/observers/example.ts +58 -0
  175. package/src/observers/file-watcher.ts +124 -0
  176. package/src/observers/index.ts +159 -0
  177. package/src/observers/notifications.ts +197 -0
  178. package/src/observers/observers.test.ts +203 -0
  179. package/src/observers/processes.ts +225 -0
  180. package/src/personality/README.md +61 -0
  181. package/src/personality/adapter.ts +196 -0
  182. package/src/personality/index.ts +20 -0
  183. package/src/personality/learner.ts +209 -0
  184. package/src/personality/model.ts +132 -0
  185. package/src/personality/personality.test.ts +236 -0
  186. package/src/roles/README.md +252 -0
  187. package/src/roles/authority.ts +120 -0
  188. package/src/roles/example-usage.ts +198 -0
  189. package/src/roles/index.ts +42 -0
  190. package/src/roles/loader.ts +143 -0
  191. package/src/roles/prompt-builder.ts +218 -0
  192. package/src/roles/test-multi.ts +102 -0
  193. package/src/roles/test-role.yaml +77 -0
  194. package/src/roles/test-utils.ts +93 -0
  195. package/src/roles/test.ts +106 -0
  196. package/src/roles/tool-guide.ts +195 -0
  197. package/src/roles/types.ts +36 -0
  198. package/src/roles/utils.ts +200 -0
  199. package/src/scripts/google-setup.ts +168 -0
  200. package/src/sidecar/connection.ts +179 -0
  201. package/src/sidecar/index.ts +6 -0
  202. package/src/sidecar/manager.ts +542 -0
  203. package/src/sidecar/protocol.ts +85 -0
  204. package/src/sidecar/rpc.ts +161 -0
  205. package/src/sidecar/scheduler.ts +136 -0
  206. package/src/sidecar/types.ts +112 -0
  207. package/src/sidecar/validator.ts +144 -0
  208. package/src/sites/builder-tools.ts +215 -0
  209. package/src/sites/dev-server-manager.ts +286 -0
  210. package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
  211. package/src/sites/fixtures/security-test-site/Makefile +15 -0
  212. package/src/sites/fixtures/security-test-site/README.md +18 -0
  213. package/src/sites/fixtures/security-test-site/index.html +12 -0
  214. package/src/sites/fixtures/security-test-site/index.ts +16 -0
  215. package/src/sites/fixtures/security-test-site/package.json +13 -0
  216. package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
  217. package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
  218. package/src/sites/git-manager.ts +240 -0
  219. package/src/sites/github-manager.ts +355 -0
  220. package/src/sites/index.ts +25 -0
  221. package/src/sites/project-manager.ts +389 -0
  222. package/src/sites/proxy.ts +133 -0
  223. package/src/sites/service.ts +136 -0
  224. package/src/sites/templates.ts +169 -0
  225. package/src/sites/types.ts +89 -0
  226. package/src/user/profile-followup.test.ts +84 -0
  227. package/src/user/profile-followup.ts +185 -0
  228. package/src/user/profile.ts +224 -0
  229. package/src/vault/README.md +110 -0
  230. package/src/vault/awareness.ts +341 -0
  231. package/src/vault/commitments.ts +299 -0
  232. package/src/vault/content-pipeline.ts +270 -0
  233. package/src/vault/conversations.ts +173 -0
  234. package/src/vault/dashboard-sessions.ts +44 -0
  235. package/src/vault/documents.ts +130 -0
  236. package/src/vault/entities.ts +185 -0
  237. package/src/vault/extractor.test.ts +356 -0
  238. package/src/vault/extractor.ts +345 -0
  239. package/src/vault/facts.ts +190 -0
  240. package/src/vault/goals.ts +477 -0
  241. package/src/vault/index.ts +87 -0
  242. package/src/vault/keychain.ts +99 -0
  243. package/src/vault/observations.ts +115 -0
  244. package/src/vault/relationships.ts +178 -0
  245. package/src/vault/retrieval.test.ts +139 -0
  246. package/src/vault/retrieval.ts +258 -0
  247. package/src/vault/schema.ts +709 -0
  248. package/src/vault/settings.ts +38 -0
  249. package/src/vault/user-profile.test.ts +113 -0
  250. package/src/vault/user-profile.ts +176 -0
  251. package/src/vault/vectors.ts +92 -0
  252. package/src/vault/webapp-template-seeds.ts +116 -0
  253. package/src/vault/webapp-templates.ts +244 -0
  254. package/src/vault/workflows.ts +403 -0
  255. package/src/workflows/auto-suggest.ts +290 -0
  256. package/src/workflows/engine.ts +366 -0
  257. package/src/workflows/events.ts +24 -0
  258. package/src/workflows/executor.ts +207 -0
  259. package/src/workflows/nl-builder.ts +198 -0
  260. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  261. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  262. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  263. package/src/workflows/nodes/actions/discord.ts +77 -0
  264. package/src/workflows/nodes/actions/file-write.ts +73 -0
  265. package/src/workflows/nodes/actions/gmail.ts +69 -0
  266. package/src/workflows/nodes/actions/http-request.ts +117 -0
  267. package/src/workflows/nodes/actions/notification.ts +85 -0
  268. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  269. package/src/workflows/nodes/actions/send-message.ts +82 -0
  270. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  271. package/src/workflows/nodes/actions/telegram.ts +60 -0
  272. package/src/workflows/nodes/builtin.ts +119 -0
  273. package/src/workflows/nodes/error/error-handler.ts +37 -0
  274. package/src/workflows/nodes/error/fallback.ts +47 -0
  275. package/src/workflows/nodes/error/retry.ts +82 -0
  276. package/src/workflows/nodes/logic/delay.ts +42 -0
  277. package/src/workflows/nodes/logic/if-else.ts +41 -0
  278. package/src/workflows/nodes/logic/loop.ts +90 -0
  279. package/src/workflows/nodes/logic/merge.ts +38 -0
  280. package/src/workflows/nodes/logic/race.ts +40 -0
  281. package/src/workflows/nodes/logic/switch.ts +59 -0
  282. package/src/workflows/nodes/logic/template-render.ts +53 -0
  283. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  284. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  285. package/src/workflows/nodes/registry.ts +99 -0
  286. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  287. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  288. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  289. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  290. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  291. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  292. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  293. package/src/workflows/nodes/triggers/cron.ts +40 -0
  294. package/src/workflows/nodes/triggers/email.ts +40 -0
  295. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  296. package/src/workflows/nodes/triggers/git.ts +46 -0
  297. package/src/workflows/nodes/triggers/manual.ts +23 -0
  298. package/src/workflows/nodes/triggers/poll.ts +81 -0
  299. package/src/workflows/nodes/triggers/process.ts +44 -0
  300. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  301. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  302. package/src/workflows/safe-eval.ts +139 -0
  303. package/src/workflows/template.ts +118 -0
  304. package/src/workflows/triggers/cron.ts +311 -0
  305. package/src/workflows/triggers/manager.ts +285 -0
  306. package/src/workflows/triggers/observer-bridge.ts +172 -0
  307. package/src/workflows/triggers/poller.ts +201 -0
  308. package/src/workflows/triggers/screen-condition.ts +218 -0
  309. package/src/workflows/triggers/triggers.test.ts +740 -0
  310. package/src/workflows/triggers/webhook.ts +191 -0
  311. package/src/workflows/types.ts +133 -0
  312. package/src/workflows/variables.ts +72 -0
  313. package/src/workflows/workflows.test.ts +383 -0
  314. package/src/workflows/yaml.ts +104 -0
  315. package/ui/dist/index-3gr23jt9.js +112614 -0
  316. package/ui/dist/index-9vmj8127.css +14239 -0
  317. package/ui/dist/index-hy9pc1gm.js +112873 -0
  318. package/ui/dist/index-j2ep5d1w.js +112374 -0
  319. package/ui/dist/index-jt00vjqs.js +112858 -0
  320. package/ui/dist/index-k9ymx5qb.js +112374 -0
  321. package/ui/dist/index.html +16 -0
  322. package/ui/public/audio/pcm-capture-processor.js +11 -0
  323. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  324. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  325. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  326. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  327. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  328. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  329. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  330. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,667 @@
1
+ /**
2
+ * Agent Service — The Brain
3
+ *
4
+ * Owns the LLM manager, agent orchestrator, and personality state.
5
+ * Builds dynamic system prompts each turn with role context, personality,
6
+ * commitments, and observations.
7
+ */
8
+
9
+ import { join } from 'node:path';
10
+ import type { Service, ServiceStatus } from './services.ts';
11
+ import type { JarvisConfig } from '../config/types.ts';
12
+ import type { LLMStreamEvent } from '../llm/provider.ts';
13
+ import type { RoleDefinition } from '../roles/types.ts';
14
+ import type { PersonalityModel } from '../personality/model.ts';
15
+
16
+ import { LLMManager } from '../llm/manager.ts';
17
+ import { AnthropicProvider } from '../llm/anthropic.ts';
18
+ import { OpenAIProvider } from '../llm/openai.ts';
19
+ import { GroqProvider } from '../llm/groq.ts';
20
+ import { GeminiProvider } from '../llm/gemini.ts';
21
+ import { OllamaProvider } from '../llm/ollama.ts';
22
+ import { OpenRouterProvider } from '../llm/openrouter.ts';
23
+ import { AgentOrchestrator } from '../agents/orchestrator.ts';
24
+ import { loadRole } from '../roles/loader.ts';
25
+ import { ToolRegistry } from '../actions/tools/registry.ts';
26
+ import { BUILTIN_TOOLS, browser } from '../actions/tools/builtin.ts';
27
+ import { createDelegateTool, type DelegateToolDeps } from '../actions/tools/delegate.ts';
28
+ import { createManageAgentsTool, type AgentToolDeps } from '../actions/tools/agents.ts';
29
+ import { contentPipelineTool } from '../actions/tools/content.ts';
30
+ import { commitmentsTool } from '../actions/tools/commitments.ts';
31
+ import { researchQueueTool } from '../actions/tools/research.ts';
32
+ import { documentTool } from '../actions/tools/documents.ts';
33
+ import { AgentTaskManager } from '../agents/task-manager.ts';
34
+ import { discoverSpecialists, formatSpecialistList } from '../agents/role-discovery.ts';
35
+ import { buildSystemPrompt, type PromptContext } from '../roles/prompt-builder.ts';
36
+ import type { ProgressCallback } from '../agents/sub-agent-runner.ts';
37
+ import {
38
+ getPersonality,
39
+ savePersonality,
40
+ } from '../personality/model.ts';
41
+ import {
42
+ getChannelPersonality,
43
+ personalityToPrompt,
44
+ } from '../personality/adapter.ts';
45
+ import {
46
+ extractSignals,
47
+ applySignals,
48
+ recordInteraction,
49
+ } from '../personality/learner.ts';
50
+ import { getDueCommitments, getUpcoming } from '../vault/commitments.ts';
51
+ import { findContent } from '../vault/content-pipeline.ts';
52
+ import { getRecentObservations } from '../vault/observations.ts';
53
+ import { extractAndStore } from '../vault/extractor.ts';
54
+ import { getKnowledgeForMessage } from '../vault/retrieval.ts';
55
+ import { formatUserProfileForPrompt } from '../user/profile.ts';
56
+ import { getUserProfile } from '../vault/user-profile.ts';
57
+ import { getWebappInstructionsForMessage } from '../vault/webapp-templates.ts';
58
+ import type { ResearchQueue } from './research-queue.ts';
59
+ import type { IAgentService } from './agent-service-interface.ts';
60
+ import type { AuthorityEngine } from '../authority/engine.ts';
61
+ import { getSidecarManager } from '../actions/tools/sidecar-route.ts';
62
+
63
+ export class AgentService implements Service, IAgentService {
64
+ name = 'agent';
65
+ private _status: ServiceStatus = 'stopped';
66
+ private config: JarvisConfig;
67
+ private llmManager: LLMManager;
68
+ private orchestrator: AgentOrchestrator;
69
+ private role: RoleDefinition | null = null;
70
+ private personality: PersonalityModel | null = null;
71
+ private specialists: Map<string, RoleDefinition> = new Map();
72
+ private specialistListText: string = '';
73
+ private delegationProgressCallback: ProgressCallback | null = null;
74
+ private delegationCallback: ((specialistName: string, task: string) => void) | null = null;
75
+ private researchQueue: ResearchQueue | null = null;
76
+ private taskManager: AgentTaskManager | null = null;
77
+ private authorityEngine: AuthorityEngine | null = null;
78
+
79
+ constructor(config: JarvisConfig) {
80
+ this.config = config;
81
+ this.llmManager = new LLMManager();
82
+ this.orchestrator = new AgentOrchestrator();
83
+ }
84
+
85
+ /**
86
+ * Set callback for sub-agent progress events (delegation visibility).
87
+ * Typically wired to WebSocket broadcast by the daemon.
88
+ */
89
+ setDelegationProgressCallback(cb: ProgressCallback): void {
90
+ this.delegationProgressCallback = cb;
91
+ }
92
+
93
+ /**
94
+ * Set callback fired when the PA delegates a task to a specialist.
95
+ * Used by ws-service to update task board ownership in real time.
96
+ */
97
+ setDelegationCallback(cb: (specialistName: string, task: string) => void): void {
98
+ this.delegationCallback = cb;
99
+ }
100
+
101
+ /**
102
+ * Set the research queue for idle-time background research.
103
+ */
104
+ setResearchQueue(queue: ResearchQueue): void {
105
+ this.researchQueue = queue;
106
+ }
107
+
108
+ setAuthorityEngine(engine: AuthorityEngine): void {
109
+ this.authorityEngine = engine;
110
+ }
111
+
112
+
113
+ getOrchestrator(): AgentOrchestrator {
114
+ return this.orchestrator;
115
+ }
116
+
117
+ getLLMManager(): LLMManager {
118
+ return this.llmManager;
119
+ }
120
+
121
+ getTaskManager(): AgentTaskManager | null {
122
+ return this.taskManager;
123
+ }
124
+
125
+ getSpecialists(): Map<string, RoleDefinition> {
126
+ return new Map(this.specialists);
127
+ }
128
+
129
+ async start(): Promise<void> {
130
+ this._status = 'starting';
131
+
132
+ try {
133
+ // 1. Create LLM providers from config
134
+ this.registerProviders();
135
+
136
+ // 2. Load role YAML
137
+ this.role = this.loadActiveRole();
138
+
139
+ // 3. Wire LLM manager to orchestrator
140
+ this.orchestrator.setLLMManager(this.llmManager);
141
+
142
+ // 4. Discover specialist roles
143
+ this.specialists = discoverSpecialists('roles/specialists');
144
+ if (this.specialists.size > 0) {
145
+ this.specialistListText = formatSpecialistList(this.specialists);
146
+ console.log(`[AgentService] Discovered ${this.specialists.size} specialists: ${Array.from(this.specialists.keys()).join(', ')}`);
147
+ }
148
+
149
+ // 5. Register tools (builtin + delegation)
150
+ const toolRegistry = new ToolRegistry();
151
+ for (const tool of BUILTIN_TOOLS) {
152
+ toolRegistry.register(tool);
153
+ }
154
+
155
+ // Register content pipeline tool
156
+ toolRegistry.register(contentPipelineTool);
157
+
158
+ // Register commitments tool
159
+ toolRegistry.register(commitmentsTool);
160
+
161
+ // Register research queue tool
162
+ toolRegistry.register(researchQueueTool);
163
+
164
+ // Register document tool (vault-stored documents)
165
+ toolRegistry.register(documentTool);
166
+
167
+ // Register delegate_task tool if specialists are available
168
+ if (this.specialists.size > 0) {
169
+ const delegateDeps: DelegateToolDeps = {
170
+ orchestrator: this.orchestrator,
171
+ llmManager: this.llmManager,
172
+ specialists: this.specialists,
173
+ onProgress: (event) => {
174
+ if (this.delegationProgressCallback) {
175
+ this.delegationProgressCallback(event);
176
+ }
177
+ },
178
+ onDelegation: (specialistName, task) => {
179
+ if (this.delegationCallback) {
180
+ this.delegationCallback(specialistName, task);
181
+ }
182
+ },
183
+ };
184
+ const delegateTool = createDelegateTool(delegateDeps);
185
+ toolRegistry.register(delegateTool);
186
+ console.log('[AgentService] Registered delegate_task tool');
187
+
188
+ // Register manage_agents tool for persistent/async agents
189
+ this.taskManager = new AgentTaskManager();
190
+ const agentToolDeps: AgentToolDeps = {
191
+ orchestrator: this.orchestrator,
192
+ llmManager: this.llmManager,
193
+ specialists: this.specialists,
194
+ taskManager: this.taskManager,
195
+ onProgress: (event) => {
196
+ if (this.delegationProgressCallback) {
197
+ this.delegationProgressCallback(event);
198
+ }
199
+ },
200
+ };
201
+ const agentTool = createManageAgentsTool(agentToolDeps);
202
+ toolRegistry.register(agentTool);
203
+ console.log('[AgentService] Registered manage_agents tool');
204
+ }
205
+
206
+ this.orchestrator.setToolRegistry(toolRegistry);
207
+ console.log(`[AgentService] Registered ${toolRegistry.count()} tools total`);
208
+
209
+ // 6. Create primary agent
210
+ this.orchestrator.createPrimary(this.role);
211
+
212
+ // 7. Load personality
213
+ this.personality = getPersonality();
214
+
215
+ this._status = 'running';
216
+ console.log(`[AgentService] Started with role: ${this.role.name}`);
217
+ } catch (error) {
218
+ this._status = 'error';
219
+ throw error;
220
+ }
221
+ }
222
+
223
+ async stop(): Promise<void> {
224
+ this._status = 'stopping';
225
+ const primary = this.orchestrator.getPrimary();
226
+ if (primary) {
227
+ this.orchestrator.terminateAgent(primary.id);
228
+ }
229
+
230
+ // Disconnect browser (stops auto-launched Chrome if any)
231
+ if (browser.connected) {
232
+ await browser.disconnect();
233
+ }
234
+
235
+ this._status = 'stopped';
236
+ console.log('[AgentService] Stopped');
237
+ }
238
+
239
+ status(): ServiceStatus {
240
+ return this._status;
241
+ }
242
+
243
+ /**
244
+ * Stream a message through the agent. Returns a stream and an onComplete callback.
245
+ */
246
+ streamMessage(text: string, channel: string = 'websocket', siteContext?: string): {
247
+ stream: AsyncIterable<LLMStreamEvent>;
248
+ onComplete: (fullText: string) => Promise<void>;
249
+ } {
250
+ let systemPrompt = this.buildFullSystemPrompt(channel, text);
251
+ if (siteContext) {
252
+ systemPrompt += '\n\n' + siteContext;
253
+ }
254
+
255
+ const stream = this.orchestrator.streamMessage(systemPrompt, text);
256
+
257
+ const onComplete = async (fullText: string): Promise<void> => {
258
+ // Note: orchestrator already adds assistant response to history
259
+ // Run extraction and learning in parallel, wait for both to settle
260
+ await Promise.allSettled([
261
+ this.extractKnowledge(text, fullText).catch((err) =>
262
+ console.error('[AgentService] Extraction error:', err instanceof Error ? err.message : err)
263
+ ),
264
+ this.learnFromInteraction(text, fullText, channel).catch((err) =>
265
+ console.error('[AgentService] Learning error:', err instanceof Error ? err.message : err)
266
+ ),
267
+ ]);
268
+ };
269
+
270
+ return { stream, onComplete };
271
+ }
272
+
273
+ /**
274
+ * Non-streaming message handler. Returns full response string.
275
+ */
276
+ async handleMessage(text: string, channel: string = 'websocket'): Promise<string> {
277
+ const systemPrompt = this.buildFullSystemPrompt(channel, text);
278
+
279
+ const response = await this.orchestrator.processMessage(systemPrompt, text);
280
+
281
+ // Run extraction and learning in parallel (non-blocking but tracked)
282
+ Promise.allSettled([
283
+ this.extractKnowledge(text, response).catch((err) =>
284
+ console.error('[AgentService] Extraction error:', err instanceof Error ? err.message : err)
285
+ ),
286
+ this.learnFromInteraction(text, response, channel).catch((err) =>
287
+ console.error('[AgentService] Learning error:', err instanceof Error ? err.message : err)
288
+ ),
289
+ ]);
290
+
291
+ return response;
292
+ }
293
+
294
+ /**
295
+ * Handle periodic heartbeat with full tool access.
296
+ * Accepts optional coalesced event summary to include in the prompt.
297
+ * Uses processMessage() so the agent can take action (browse, run commands, etc.).
298
+ */
299
+ async handleHeartbeat(coalescedEvents?: string): Promise<string | null> {
300
+ if (!this.role) return null;
301
+
302
+ const systemPrompt = this.buildHeartbeatPrompt(coalescedEvents);
303
+
304
+ // Build the heartbeat "user message" that triggers the agent
305
+ const parts: string[] = ['[HEARTBEAT] Periodic check-in. Review your responsibilities and take action.'];
306
+
307
+ if (coalescedEvents) {
308
+ parts.push('');
309
+ parts.push(coalescedEvents);
310
+ }
311
+
312
+ const heartbeatMessage = parts.join('\n');
313
+
314
+ try {
315
+ const response = await this.orchestrator.processMessage(systemPrompt, heartbeatMessage);
316
+ if (response && response.trim().length > 0) {
317
+ return response;
318
+ }
319
+ return null;
320
+ } catch (err) {
321
+ console.error('[AgentService] Heartbeat processing error:', err);
322
+ return null;
323
+ }
324
+ }
325
+
326
+ // --- Private methods ---
327
+
328
+ private registerProviders(): void {
329
+ const { llm } = this.config;
330
+ let hasProvider = false;
331
+
332
+ // Register Anthropic
333
+ if (llm.anthropic?.api_key) {
334
+ const provider = new AnthropicProvider(
335
+ llm.anthropic.api_key,
336
+ llm.anthropic.model
337
+ );
338
+ this.llmManager.registerProvider(provider);
339
+ hasProvider = true;
340
+ console.log('[AgentService] Registered Anthropic provider');
341
+ }
342
+
343
+ // Register OpenAI
344
+ if (llm.openai?.api_key) {
345
+ const provider = new OpenAIProvider(
346
+ llm.openai.api_key,
347
+ llm.openai.model,
348
+ llm.openai.base_url,
349
+ );
350
+ this.llmManager.registerProvider(provider);
351
+ hasProvider = true;
352
+ console.log('[AgentService] Registered OpenAI provider');
353
+ }
354
+
355
+ // Register Groq
356
+ if (llm.groq?.api_key) {
357
+ const provider = new GroqProvider(
358
+ llm.groq.api_key,
359
+ llm.groq.model
360
+ );
361
+ this.llmManager.registerProvider(provider);
362
+ hasProvider = true;
363
+ console.log('[AgentService] Registered Groq provider');
364
+ }
365
+
366
+ // Register Gemini
367
+ if (llm.gemini?.api_key) {
368
+ const provider = new GeminiProvider(
369
+ llm.gemini.api_key,
370
+ llm.gemini.model
371
+ );
372
+ this.llmManager.registerProvider(provider);
373
+ hasProvider = true;
374
+ console.log('[AgentService] Registered Gemini provider');
375
+ }
376
+
377
+ // Register OpenRouter
378
+ if (llm.openrouter?.api_key) {
379
+ const provider = new OpenRouterProvider(
380
+ llm.openrouter.api_key,
381
+ llm.openrouter.model
382
+ );
383
+ this.llmManager.registerProvider(provider);
384
+ hasProvider = true;
385
+ console.log('[AgentService] Registered OpenRouter provider');
386
+ }
387
+
388
+ // Register Ollama (always available, no API key needed)
389
+ if (llm.ollama) {
390
+ const provider = new OllamaProvider(
391
+ llm.ollama.base_url,
392
+ llm.ollama.model
393
+ );
394
+ this.llmManager.registerProvider(provider);
395
+ hasProvider = true;
396
+ console.log('[AgentService] Registered Ollama provider');
397
+ }
398
+
399
+ if (!hasProvider) {
400
+ console.warn('[AgentService] No LLM providers configured. Responses will be placeholders.');
401
+ }
402
+
403
+ // Set primary and fallback chain
404
+ if (hasProvider) {
405
+ try {
406
+ this.llmManager.setPrimary(llm.primary);
407
+ } catch {
408
+ // Primary provider not available, first registered is already primary
409
+ }
410
+
411
+ // Set fallback chain (only for providers that were registered)
412
+ const registeredFallbacks = llm.fallback.filter(
413
+ (name) => this.llmManager.getProvider(name) !== undefined
414
+ );
415
+ if (registeredFallbacks.length > 0) {
416
+ this.llmManager.setFallbackChain(registeredFallbacks);
417
+ }
418
+ }
419
+ }
420
+
421
+ private loadActiveRole(): RoleDefinition {
422
+ const roleName = this.config.active_role;
423
+
424
+ // Try multiple locations for role YAML (package-root-relative for global install)
425
+ const pkgRoot = join(import.meta.dir, '../..');
426
+ const paths = [
427
+ join(pkgRoot, `roles/${roleName}.yaml`),
428
+ join(pkgRoot, `roles/${roleName}.yml`),
429
+ join(pkgRoot, `config/roles/${roleName}.yaml`),
430
+ join(pkgRoot, `config/roles/${roleName}.yml`),
431
+ // Also try CWD-relative for local dev
432
+ `roles/${roleName}.yaml`,
433
+ `roles/${roleName}.yml`,
434
+ ];
435
+
436
+ for (const rolePath of paths) {
437
+ try {
438
+ const role = loadRole(rolePath);
439
+ console.log(`[AgentService] Loaded role '${role.name}' from ${rolePath}`);
440
+ return role;
441
+ } catch {
442
+ // Try next path
443
+ }
444
+ }
445
+
446
+ // Fatal — cannot start without a role
447
+ throw new Error(
448
+ `[AgentService] Could not load role '${roleName}'. Searched: ${paths.join(', ')}`
449
+ );
450
+ }
451
+
452
+ private buildFullSystemPrompt(channel: string, userMessage?: string): string {
453
+ if (!this.role) return '';
454
+
455
+ // Build prompt context with live data + vault knowledge
456
+ const context = this.buildPromptContext(userMessage);
457
+
458
+ // Build base system prompt from role + context
459
+ const rolePrompt = buildSystemPrompt(this.role, context);
460
+
461
+ // Build personality prompt for this channel
462
+ const personality = this.personality ?? getPersonality();
463
+ const channelPersonality = getChannelPersonality(personality, channel);
464
+ const personalityPrompt = personalityToPrompt(channelPersonality);
465
+
466
+ return `${rolePrompt}\n\n${personalityPrompt}`;
467
+ }
468
+
469
+ private buildHeartbeatPrompt(coalescedEvents?: string): string {
470
+ if (!this.role) return '';
471
+
472
+ const context = this.buildPromptContext();
473
+ const rolePrompt = buildSystemPrompt(this.role, context);
474
+
475
+ const parts = [rolePrompt, '', '# Heartbeat Check', this.role.heartbeat_instructions];
476
+
477
+ if (coalescedEvents) {
478
+ parts.push('', '# Recent System Events', coalescedEvents);
479
+ }
480
+
481
+ // Inject commitment execution instructions
482
+ parts.push('', '# COMMITMENT EXECUTION');
483
+ parts.push('If any commitments are overdue or due soon, EXECUTE them now using your tools.');
484
+ parts.push('Do not just mention them — actually perform the work. Use browse, terminal, file operations as needed.');
485
+
486
+ // Inject background research instructions when idle
487
+ if (this.researchQueue && this.researchQueue.queuedCount() > 0) {
488
+ const next = this.researchQueue.getNext();
489
+ if (next) {
490
+ parts.push('', '# BACKGROUND RESEARCH');
491
+ parts.push(`You have a research topic queued: "${next.topic}"`);
492
+ parts.push(`Reason: ${next.reason}`);
493
+ parts.push(`Research ID: ${next.id}`);
494
+ parts.push('If nothing urgent needs your attention, research this topic now.');
495
+ parts.push('Use your browser and tools to gather information, then use the research_queue tool with action "complete" to save your findings.');
496
+ }
497
+ } else {
498
+ parts.push('', '# IDLE MODE');
499
+ parts.push('No research topics queued. If nothing urgent, you may:');
500
+ parts.push('- Check news or trends relevant to the user');
501
+ parts.push('- Review and organize pending tasks');
502
+ parts.push('- Or simply report "All clear" if nothing needs attention');
503
+ }
504
+
505
+ parts.push('', '# Important', 'You have full tool access during this heartbeat. If you need to take action (browse the web, run commands, check files), DO IT. Be proactive and aggressive about helping.');
506
+
507
+ return parts.join('\n');
508
+ }
509
+
510
+ private buildPromptContext(userMessage?: string): PromptContext {
511
+ // Check if any sidecars are enrolled (cheap DB query, controls tool guide content)
512
+ let hasSidecars = false;
513
+ try {
514
+ const mgr = getSidecarManager();
515
+ if (mgr) hasSidecars = mgr.listSidecars().length > 0;
516
+ } catch { /* ignore */ }
517
+
518
+ const context: PromptContext = {
519
+ userName: this.config.user?.name || undefined,
520
+ currentTime: new Date().toISOString(),
521
+ availableSpecialists: this.specialistListText || undefined,
522
+ hasSidecars,
523
+ };
524
+
525
+ try {
526
+ const profile = getUserProfile();
527
+ const preferredName = profile?.answers.preferred_name?.trim();
528
+ if (preferredName) {
529
+ context.userName = preferredName;
530
+ }
531
+
532
+ const profileContext = formatUserProfileForPrompt(profile);
533
+ if (profileContext) {
534
+ context.userProfile = profileContext;
535
+ }
536
+ } catch (err) {
537
+ console.error('[AgentService] Error loading user profile:', err);
538
+ }
539
+
540
+ // Retrieve relevant knowledge from vault based on user message
541
+ if (userMessage) {
542
+ try {
543
+ const knowledge = getKnowledgeForMessage(userMessage);
544
+ if (knowledge) {
545
+ context.knowledgeContext = knowledge;
546
+ }
547
+ } catch (err) {
548
+ console.error('[AgentService] Error retrieving knowledge:', err);
549
+ }
550
+
551
+ // Retrieve webapp-specific browser instructions if message mentions a known app
552
+ try {
553
+ const webappInstructions = getWebappInstructionsForMessage(userMessage);
554
+ if (webappInstructions) {
555
+ context.webappInstructions = webappInstructions;
556
+ }
557
+ } catch (err) {
558
+ console.error('[AgentService] Error retrieving webapp instructions:', err);
559
+ }
560
+ }
561
+
562
+ // Get due commitments
563
+ try {
564
+ const due = getDueCommitments();
565
+ const upcoming = getUpcoming(5);
566
+ const allCommitments = [...due, ...upcoming];
567
+
568
+ if (allCommitments.length > 0) {
569
+ context.activeCommitments = allCommitments.map((c) => {
570
+ const dueStr = c.when_due
571
+ ? ` (due: ${new Date(c.when_due).toLocaleString()})`
572
+ : '';
573
+ return `[${c.priority}] ${c.what}${dueStr} — ${c.status}`;
574
+ });
575
+ }
576
+ } catch (err) {
577
+ console.error('[AgentService] Error loading commitments:', err);
578
+ }
579
+
580
+ // Get active content pipeline items (not published)
581
+ try {
582
+ const activeContent = findContent({}).filter(
583
+ (c) => c.stage !== 'published'
584
+ ).slice(0, 10);
585
+ if (activeContent.length > 0) {
586
+ context.contentPipeline = activeContent.map((c) => {
587
+ const tags = c.tags.length > 0 ? ` [${c.tags.join(', ')}]` : '';
588
+ return `"${c.title}" (${c.content_type}) — ${c.stage}${tags}`;
589
+ });
590
+ }
591
+ } catch (err) {
592
+ console.error('[AgentService] Error loading content pipeline:', err);
593
+ }
594
+
595
+ // Get recent observations
596
+ try {
597
+ const observations = getRecentObservations(undefined, 10);
598
+ if (observations.length > 0) {
599
+ context.recentObservations = observations.map((o) => {
600
+ const time = new Date(o.created_at).toLocaleTimeString();
601
+ return `[${time}] ${o.type}: ${JSON.stringify(o.data).slice(0, 200)}`;
602
+ });
603
+ }
604
+ } catch (err) {
605
+ console.error('[AgentService] Error loading observations:', err);
606
+ }
607
+
608
+ // Active goals context for the system prompt
609
+ try {
610
+ const { getActiveGoalsSummary } = require('../vault/retrieval.ts');
611
+ const goalsSummary = getActiveGoalsSummary();
612
+ if (goalsSummary) {
613
+ context.activeGoals = goalsSummary;
614
+ }
615
+ } catch {
616
+ // Goals module may not be available — ignore
617
+ }
618
+
619
+ // Authority rules for the system prompt
620
+ if (this.authorityEngine && this.role) {
621
+ try {
622
+ context.authorityRules = this.authorityEngine.describeRulesForAgent(
623
+ this.role.authority_level,
624
+ this.role.id
625
+ );
626
+ const configLevel = this.authorityEngine.getConfig().default_level;
627
+ context.effectiveAuthorityLevel = Math.max(this.role.authority_level, configLevel);
628
+ } catch (err) {
629
+ console.error('[AgentService] Error building authority rules:', err);
630
+ }
631
+ }
632
+
633
+ return context;
634
+ }
635
+
636
+ private async extractKnowledge(userMessage: string, assistantResponse: string): Promise<void> {
637
+ // Get the primary provider for extraction
638
+ const provider = this.llmManager.getProvider(this.config.llm.primary)
639
+ ?? this.llmManager.getProvider('anthropic')
640
+ ?? this.llmManager.getProvider('openai');
641
+
642
+ await extractAndStore(userMessage, assistantResponse, provider);
643
+ }
644
+
645
+ private async learnFromInteraction(
646
+ userMessage: string,
647
+ assistantResponse: string,
648
+ _channel: string
649
+ ): Promise<void> {
650
+ let personality = this.personality ?? getPersonality();
651
+
652
+ // Extract signals from the interaction
653
+ const signals = extractSignals(userMessage, assistantResponse);
654
+
655
+ // Apply signals if any
656
+ if (signals.length > 0) {
657
+ personality = applySignals(personality, signals);
658
+ }
659
+
660
+ // Record the interaction (increments message count, adjusts trust)
661
+ personality = recordInteraction(personality);
662
+
663
+ // Save updated personality
664
+ savePersonality(personality);
665
+ this.personality = personality;
666
+ }
667
+ }