@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,592 @@
1
+ import type { RoleDefinition } from '../roles/types.ts';
2
+ import type { LLMMessage, LLMResponse, LLMStreamEvent, LLMToolCall, LLMTool, ContentBlock } from '../llm/provider.ts';
3
+ import { guardImageSize } from '../llm/provider.ts';
4
+ import { LLMManager } from '../llm/manager.ts';
5
+ import { AgentInstance } from './agent.ts';
6
+ import { AgentHierarchy } from './hierarchy.ts';
7
+ import { ToolRegistry, type ToolDefinition, isToolResult } from '../actions/tools/registry.ts';
8
+ import { toolDefToLLMTool } from '../actions/tools/builtin.ts';
9
+ import type { ActionCategory } from '../roles/authority.ts';
10
+ import type { AuthorityEngine } from '../authority/engine.ts';
11
+ import type { ApprovalManager, ApprovalRequest } from '../authority/approval.ts';
12
+ import type { AuditTrail } from '../authority/audit.ts';
13
+ import type { EmergencyController } from '../authority/emergency.ts';
14
+ import { getActionForTool } from '../authority/tool-action-map.ts';
15
+
16
+ const MAX_TOOL_ITERATIONS = 200;
17
+ const MAX_TOOL_RESULT_CHARS = 6000; // Cap individual tool results to control context size
18
+
19
+ export class AgentOrchestrator {
20
+ private hierarchy: AgentHierarchy;
21
+ private llmManager: LLMManager | null;
22
+ private toolRegistry: ToolRegistry | null;
23
+
24
+ // Authority engine components
25
+ private authorityEngine: AuthorityEngine | null = null;
26
+ private approvalManager: ApprovalManager | null = null;
27
+ private auditTrail: AuditTrail | null = null;
28
+ private emergencyController: EmergencyController | null = null;
29
+ private temporaryGrants: Map<string, ActionCategory[]> = new Map();
30
+ private onApprovalNeeded: ((request: ApprovalRequest) => void) | null = null;
31
+
32
+ constructor() {
33
+ this.hierarchy = new AgentHierarchy();
34
+ this.llmManager = null;
35
+ this.toolRegistry = null;
36
+ }
37
+
38
+ setLLMManager(llm: LLMManager): void {
39
+ this.llmManager = llm;
40
+ }
41
+
42
+ getLLMManager(): LLMManager | null {
43
+ return this.llmManager;
44
+ }
45
+
46
+ setToolRegistry(registry: ToolRegistry): void {
47
+ this.toolRegistry = registry;
48
+ }
49
+
50
+ getToolRegistry(): ToolRegistry | null {
51
+ return this.toolRegistry;
52
+ }
53
+
54
+ // --- Authority setters ---
55
+
56
+ setAuthorityEngine(engine: AuthorityEngine): void {
57
+ this.authorityEngine = engine;
58
+ }
59
+
60
+ setApprovalManager(manager: ApprovalManager): void {
61
+ this.approvalManager = manager;
62
+ }
63
+
64
+ setAuditTrail(trail: AuditTrail): void {
65
+ this.auditTrail = trail;
66
+ }
67
+
68
+ setEmergencyController(controller: EmergencyController): void {
69
+ this.emergencyController = controller;
70
+ }
71
+
72
+ setApprovalCallback(cb: (request: ApprovalRequest) => void): void {
73
+ this.onApprovalNeeded = cb;
74
+ }
75
+
76
+ /**
77
+ * Grant a temporary permission to a specific agent (for parent escalation).
78
+ */
79
+ grantTemporary(agentId: string, action: ActionCategory): void {
80
+ const existing = this.temporaryGrants.get(agentId) ?? [];
81
+ if (!existing.includes(action)) {
82
+ existing.push(action);
83
+ this.temporaryGrants.set(agentId, existing);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Revoke a temporary permission from an agent.
89
+ */
90
+ revokeTemporary(agentId: string, action: ActionCategory): void {
91
+ const existing = this.temporaryGrants.get(agentId);
92
+ if (existing) {
93
+ this.temporaryGrants.set(agentId, existing.filter(a => a !== action));
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Clear all temporary grants for an agent (called when task completes).
99
+ */
100
+ clearTemporaryGrants(agentId: string): void {
101
+ this.temporaryGrants.delete(agentId);
102
+ }
103
+
104
+ /**
105
+ * Create the primary agent from a role.
106
+ * No inline system prompt — the AgentService builds a rich dynamic prompt each turn.
107
+ */
108
+ createPrimary(role: RoleDefinition): AgentInstance {
109
+ const existing = this.hierarchy.getPrimary();
110
+ if (existing) {
111
+ throw new Error('Primary agent already exists. Terminate it first.');
112
+ }
113
+
114
+ const agent = new AgentInstance(role);
115
+ this.hierarchy.addAgent(agent);
116
+ return agent;
117
+ }
118
+
119
+ /**
120
+ * Spawn a sub-agent under a parent
121
+ */
122
+ spawnSubAgent(
123
+ parentId: string,
124
+ role: RoleDefinition,
125
+ opts?: { memory_scope?: string[] }
126
+ ): AgentInstance {
127
+ const parent = this.hierarchy.getAgent(parentId);
128
+ if (!parent) {
129
+ throw new Error(`Parent agent not found: ${parentId}`);
130
+ }
131
+
132
+ if (!parent.agent.authority.can_spawn_children) {
133
+ throw new Error('Parent agent does not have authority to spawn children');
134
+ }
135
+
136
+ // Create child agent with reduced authority
137
+ const childAuthority = {
138
+ max_authority_level: Math.min(
139
+ role.authority_level,
140
+ parent.agent.authority.max_authority_level - 1
141
+ ),
142
+ allowed_tools: role.tools.filter((tool) =>
143
+ parent.agent.authority.allowed_tools.includes(tool)
144
+ ),
145
+ denied_tools: parent.agent.authority.denied_tools,
146
+ max_token_budget: Math.floor(parent.agent.authority.max_token_budget / 2),
147
+ can_spawn_children: role.sub_roles.length > 0,
148
+ };
149
+
150
+ const agent = new AgentInstance(role, {
151
+ parent_id: parentId,
152
+ authority: childAuthority,
153
+ memory_scope: opts?.memory_scope ?? [],
154
+ });
155
+
156
+ this.hierarchy.addAgent(agent);
157
+
158
+ // Add system message with role context for sub-agents
159
+ agent.addMessage(
160
+ 'system',
161
+ `You are ${role.name}, spawned by ${parent.agent.role.name}. ${role.description}\n\nResponsibilities:\n${role.responsibilities.map((r) => `- ${r}`).join('\n')}\n\nYou report to: ${parent.agent.role.name}\n\nCommunication style: ${role.communication_style.tone} tone, ${role.communication_style.verbosity} verbosity, ${role.communication_style.formality} formality.`
162
+ );
163
+
164
+ return agent;
165
+ }
166
+
167
+ /**
168
+ * Terminate an agent and its children
169
+ */
170
+ terminateAgent(agentId: string): void {
171
+ const agent = this.hierarchy.getAgent(agentId);
172
+ if (!agent) {
173
+ throw new Error(`Agent not found: ${agentId}`);
174
+ }
175
+
176
+ // Recursively terminate children first
177
+ const children = this.hierarchy.getChildren(agentId);
178
+ for (const child of children) {
179
+ this.terminateAgent(child.id);
180
+ }
181
+
182
+ // Terminate this agent
183
+ agent.terminate();
184
+ this.hierarchy.removeAgent(agentId);
185
+ }
186
+
187
+ getPrimary(): AgentInstance | undefined {
188
+ return this.hierarchy.getPrimary();
189
+ }
190
+
191
+ getAgent(agentId: string): AgentInstance | undefined {
192
+ return this.hierarchy.getAgent(agentId);
193
+ }
194
+
195
+ getAllAgents(): AgentInstance[] {
196
+ return this.hierarchy.getAllAgents();
197
+ }
198
+
199
+ getHierarchy(): AgentHierarchy {
200
+ return this.hierarchy;
201
+ }
202
+
203
+ /**
204
+ * Process a user message through the primary agent (non-streaming).
205
+ * Includes the tool execution loop: LLM → tool_calls → execute → re-call → repeat.
206
+ */
207
+ async processMessage(systemPrompt: string, message: string): Promise<string> {
208
+ const primary = this.getPrimary();
209
+ if (!primary) {
210
+ throw new Error('No primary agent exists. Create one first.');
211
+ }
212
+
213
+ // Add user message to persistent history
214
+ primary.addMessage('user', message);
215
+
216
+ // If no LLM manager, return placeholder
217
+ if (!this.llmManager) {
218
+ const response = `[No LLM configured] Received: ${message}`;
219
+ primary.addMessage('assistant', response);
220
+ return response;
221
+ }
222
+
223
+ // Build local messages array for this turn (system + history)
224
+ const messages: LLMMessage[] = [
225
+ { role: 'system', content: systemPrompt },
226
+ ...primary.getMessages(),
227
+ ];
228
+
229
+ const tools = this.getLLMTools();
230
+ let finalText = '';
231
+
232
+ // Tool execution loop
233
+ for (let iteration = 0; iteration < MAX_TOOL_ITERATIONS; iteration++) {
234
+ const llmResponse: LLMResponse = await this.llmManager.chat(messages, { tools });
235
+
236
+ if (llmResponse.finish_reason === 'tool_use' && llmResponse.tool_calls.length > 0) {
237
+ // Add assistant message with tool calls to local messages
238
+ messages.push({
239
+ role: 'assistant',
240
+ content: llmResponse.content,
241
+ tool_calls: llmResponse.tool_calls,
242
+ });
243
+
244
+ // Execute each tool and add results
245
+ for (const tc of llmResponse.tool_calls) {
246
+ const result = await this.executeTool(tc);
247
+ messages.push({
248
+ role: 'tool',
249
+ content: result,
250
+ tool_call_id: tc.id,
251
+ });
252
+ const logStr = typeof result === 'string' ? result.slice(0, 100) : `[${result.length} content blocks]`;
253
+ console.log(`[Orchestrator] Tool ${tc.name} → ${logStr}...`);
254
+
255
+ // Capture document markers so they appear in the final response
256
+ if (typeof result === 'string') {
257
+ const docMarker = result.match(/<!-- jarvis:document id="[^"]+" title="[^"]+" format="[^"]+" size="[^"]+" -->/);
258
+ if (docMarker) {
259
+ finalText += '\n' + docMarker[0] + '\n';
260
+ }
261
+ }
262
+ }
263
+
264
+ // Continue loop to re-call LLM with tool results
265
+ continue;
266
+ }
267
+
268
+ // No tool calls — this is the final response
269
+ finalText = llmResponse.content;
270
+
271
+ // Warn on truncation
272
+ if (llmResponse.finish_reason === 'length') {
273
+ finalText += '\n\n[Response was truncated due to output token limits. If you asked for long content, ask to continue or use shorter chunks.]';
274
+ }
275
+
276
+ break;
277
+ }
278
+
279
+ // Add final response to persistent history
280
+ primary.addMessage('assistant', finalText);
281
+ return finalText;
282
+ }
283
+
284
+ /**
285
+ * Stream a message through the primary agent with tool execution loop.
286
+ * Yields text/tool_call events through all iterations.
287
+ * Only emits 'done' when the final response is complete.
288
+ */
289
+ async *streamMessage(systemPrompt: string, message: string): AsyncIterable<LLMStreamEvent> {
290
+ const primary = this.getPrimary();
291
+ if (!primary) {
292
+ throw new Error('No primary agent exists. Create one first.');
293
+ }
294
+
295
+ // Add user message to persistent history
296
+ primary.addMessage('user', message);
297
+
298
+ // If no LLM manager, yield placeholder
299
+ if (!this.llmManager) {
300
+ const response = `[No LLM configured] Received: ${message}`;
301
+ primary.addMessage('assistant', response);
302
+ yield { type: 'text', text: response };
303
+ yield {
304
+ type: 'done',
305
+ response: {
306
+ content: response,
307
+ tool_calls: [],
308
+ usage: { input_tokens: 0, output_tokens: 0 },
309
+ model: 'none',
310
+ finish_reason: 'stop',
311
+ },
312
+ };
313
+ return;
314
+ }
315
+
316
+ // Build local messages array for this turn
317
+ const messages: LLMMessage[] = [
318
+ { role: 'system', content: systemPrompt },
319
+ ...primary.getMessages(),
320
+ ];
321
+
322
+ const tools = this.getLLMTools();
323
+ const totalUsage = { input_tokens: 0, output_tokens: 0 };
324
+ let finalText = '';
325
+ let responseModel = 'unknown';
326
+
327
+ // Tool execution loop
328
+ for (let iteration = 0; iteration < MAX_TOOL_ITERATIONS; iteration++) {
329
+ let accumulatedText = '';
330
+ const toolCalls: LLMToolCall[] = [];
331
+ let doneResponse: LLMResponse | null = null;
332
+
333
+ // Stream from LLM
334
+ for await (const event of this.llmManager.stream(messages, { tools })) {
335
+ if (event.type === 'text') {
336
+ accumulatedText += event.text;
337
+ yield event; // Forward text chunks to client
338
+ } else if (event.type === 'tool_call') {
339
+ toolCalls.push(event.tool_call);
340
+ yield event; // Forward tool_call events to client
341
+ } else if (event.type === 'done') {
342
+ doneResponse = event.response;
343
+ totalUsage.input_tokens += event.response.usage.input_tokens;
344
+ totalUsage.output_tokens += event.response.usage.output_tokens;
345
+ responseModel = event.response.model;
346
+ // Don't yield done yet — may need more iterations
347
+ } else if (event.type === 'error') {
348
+ yield event;
349
+ return;
350
+ }
351
+ }
352
+
353
+ // Ensure doneResponse is never null (stream may end without 'done' event)
354
+ if (!doneResponse) {
355
+ doneResponse = {
356
+ content: accumulatedText,
357
+ tool_calls: toolCalls,
358
+ usage: { input_tokens: 0, output_tokens: 0 },
359
+ model: responseModel,
360
+ finish_reason: 'stop',
361
+ };
362
+ }
363
+
364
+ // No tool calls — this is the final response
365
+ if (toolCalls.length === 0) {
366
+ finalText += accumulatedText;
367
+
368
+ // Check if we stopped due to token limit (truncation)
369
+ const wasLength = doneResponse?.finish_reason === 'length';
370
+ if (wasLength && !finalText.includes('[SYSTEM WARNING')) {
371
+ const truncWarning = '\n\n[Response was truncated due to output token limits. If you asked for long content, ask to continue or use shorter chunks.]';
372
+ finalText += truncWarning;
373
+ yield { type: 'text', text: truncWarning };
374
+ }
375
+
376
+ yield {
377
+ type: 'done',
378
+ response: {
379
+ content: finalText,
380
+ tool_calls: [],
381
+ usage: totalUsage,
382
+ model: responseModel,
383
+ finish_reason: wasLength ? 'length' : 'stop',
384
+ },
385
+ };
386
+ // Add final response to persistent history (only user-facing text)
387
+ primary.addMessage('assistant', finalText);
388
+ return;
389
+ }
390
+
391
+ // Tool calls present — execute them
392
+ finalText += accumulatedText;
393
+
394
+ // Add assistant message with tool calls to local messages
395
+ messages.push({
396
+ role: 'assistant',
397
+ content: accumulatedText,
398
+ tool_calls: toolCalls,
399
+ });
400
+
401
+ // Execute each tool and add results
402
+ for (const tc of toolCalls) {
403
+ const result = await this.executeTool(tc);
404
+ messages.push({
405
+ role: 'tool',
406
+ content: result,
407
+ tool_call_id: tc.id,
408
+ });
409
+ const logStr = typeof result === 'string' ? result.slice(0, 100) : `[${result.length} content blocks]`;
410
+ console.log(`[Orchestrator] Tool ${tc.name} → ${logStr}...`);
411
+
412
+ // Inject document markers into the stream so the UI can render download cards
413
+ if (typeof result === 'string') {
414
+ const docMarker = result.match(/<!-- jarvis:document id="[^"]+" title="[^"]+" format="[^"]+" size="[^"]+" -->/);
415
+ if (docMarker) {
416
+ yield { type: 'text' as const, text: '\n' + docMarker[0] + '\n' };
417
+ }
418
+ }
419
+ }
420
+
421
+ // Continue loop — will stream next LLM response
422
+ }
423
+
424
+ // Max iterations reached
425
+ yield { type: 'text', text: '\n[Max tool iterations reached]' };
426
+ yield {
427
+ type: 'done',
428
+ response: {
429
+ content: finalText + '\n[Max tool iterations reached]',
430
+ tool_calls: [],
431
+ usage: totalUsage,
432
+ model: responseModel,
433
+ finish_reason: 'stop',
434
+ },
435
+ };
436
+ primary.addMessage('assistant', finalText);
437
+ }
438
+
439
+ /**
440
+ * Heartbeat: let the primary agent check for proactive actions.
441
+ */
442
+ async heartbeat(systemPrompt: string): Promise<string | null> {
443
+ const primary = this.getPrimary();
444
+ if (!primary || !this.llmManager) {
445
+ return null;
446
+ }
447
+
448
+ const messages: LLMMessage[] = [
449
+ { role: 'system', content: systemPrompt },
450
+ ...primary.getMessages(),
451
+ ];
452
+
453
+ const llmResponse: LLMResponse = await this.llmManager.chat(messages);
454
+
455
+ if (llmResponse.content && llmResponse.content.trim().length > 0) {
456
+ primary.addMessage('assistant', llmResponse.content);
457
+ return llmResponse.content;
458
+ }
459
+
460
+ return null;
461
+ }
462
+
463
+ // --- Private helpers ---
464
+
465
+ /**
466
+ * Get LLM-formatted tools from the ToolRegistry.
467
+ */
468
+ private getLLMTools(): LLMTool[] | undefined {
469
+ if (!this.toolRegistry || this.toolRegistry.count() === 0) {
470
+ return undefined;
471
+ }
472
+
473
+ return this.toolRegistry.list().map(toolDefToLLMTool);
474
+ }
475
+
476
+ /**
477
+ * Execute a single tool call via the ToolRegistry.
478
+ * Includes authority gate: checks emergency state, authority level, and governed categories.
479
+ * Returns a string for text-only results, or ContentBlock[] for multi-modal results (images).
480
+ */
481
+ private async executeTool(toolCall: LLMToolCall): Promise<string | ContentBlock[]> {
482
+ if (!this.toolRegistry) {
483
+ return `Error: No tool registry configured`;
484
+ }
485
+
486
+ // --- Authority Gate ---
487
+
488
+ // 1. Emergency check
489
+ if (this.emergencyController && !this.emergencyController.canExecute()) {
490
+ const state = this.emergencyController.getState();
491
+ return `[SYSTEM ${state.toUpperCase()}] All tool execution is currently suspended. The user has ${state} the system.`;
492
+ }
493
+
494
+ // 2. Authority check
495
+ const primary = this.getPrimary();
496
+ if (this.authorityEngine && primary) {
497
+ const tool = this.toolRegistry.get(toolCall.name);
498
+ const actionCategory = getActionForTool(toolCall.name, tool?.category ?? 'unknown');
499
+
500
+ const decision = this.authorityEngine.checkAuthority({
501
+ agentId: primary.id,
502
+ agentAuthorityLevel: primary.agent.authority.max_authority_level,
503
+ agentRoleId: primary.agent.role.id,
504
+ toolName: toolCall.name,
505
+ toolCategory: tool?.category ?? 'unknown',
506
+ actionCategory,
507
+ temporaryGrants: this.temporaryGrants,
508
+ });
509
+
510
+ // Determine decision type for audit
511
+ const decisionType = decision.allowed
512
+ ? (decision.requiresApproval ? 'approval_required' as const : 'allowed' as const)
513
+ : 'denied' as const;
514
+
515
+ // 3. Log to audit trail
516
+ this.auditTrail?.log({
517
+ agent_id: primary.id,
518
+ agent_name: primary.agent.role.name,
519
+ tool_name: toolCall.name,
520
+ action_category: actionCategory,
521
+ authority_decision: decisionType,
522
+ approval_id: null,
523
+ executed: decision.allowed && !decision.requiresApproval,
524
+ execution_time_ms: null,
525
+ });
526
+
527
+ // 4. Denied
528
+ if (!decision.allowed) {
529
+ return `[AUTHORITY DENIED] Cannot execute ${toolCall.name}: ${decision.reason}. Your authority level is insufficient for ${actionCategory} actions.`;
530
+ }
531
+
532
+ // 5. Requires approval
533
+ if (decision.requiresApproval && this.approvalManager) {
534
+ const urgency = this.determineUrgency(actionCategory);
535
+ const request = this.approvalManager.createRequest({
536
+ agentId: primary.id,
537
+ agentName: primary.agent.role.name,
538
+ toolName: toolCall.name,
539
+ toolArguments: toolCall.arguments,
540
+ actionCategory,
541
+ urgency,
542
+ reason: decision.reason,
543
+ context: `Agent attempted: ${toolCall.name}(${JSON.stringify(toolCall.arguments).slice(0, 200)})`,
544
+ });
545
+
546
+ // Emit approval request event
547
+ this.onApprovalNeeded?.(request);
548
+
549
+ return `[AWAITING_APPROVAL] Request #${request.id.slice(0, 8)} submitted. ` +
550
+ `Action: ${toolCall.name} (${actionCategory}). ` +
551
+ `Reason: ${decision.reason}. ` +
552
+ `The user will be notified and can approve or deny this action.`;
553
+ }
554
+ }
555
+
556
+ // --- Normal execution ---
557
+ try {
558
+ const startTime = Date.now();
559
+ const raw = await this.toolRegistry.execute(toolCall.name, toolCall.arguments);
560
+ const executionTimeMs = Date.now() - startTime;
561
+
562
+ // Update audit entry with execution time (for allowed actions)
563
+ // We already logged above; for simplicity we log execution separately if needed
564
+
565
+ // Multi-modal result (e.g. screenshot with image data)
566
+ if (isToolResult(raw)) {
567
+ return raw.content.map(guardImageSize);
568
+ }
569
+
570
+ // Plain text result
571
+ let result = typeof raw === 'string' ? raw : JSON.stringify(raw);
572
+
573
+ // Cap tool result size to control context growth
574
+ if (result.length > MAX_TOOL_RESULT_CHARS) {
575
+ result = result.slice(0, MAX_TOOL_RESULT_CHARS) + `\n... (truncated, was ${result.length} chars)`;
576
+ }
577
+
578
+ return result;
579
+ } catch (err) {
580
+ return `Error executing ${toolCall.name}: ${err instanceof Error ? err.message : String(err)}`;
581
+ }
582
+ }
583
+
584
+ /**
585
+ * Determine urgency for an approval request based on action category.
586
+ */
587
+ private determineUrgency(actionCategory: ActionCategory): 'urgent' | 'normal' {
588
+ // Financial actions are always urgent
589
+ if (actionCategory === 'make_payment') return 'urgent';
590
+ return 'normal';
591
+ }
592
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Role Discovery — Specialist Role Loader
3
+ *
4
+ * Discovers specialist roles from a directory of YAML files.
5
+ * Returns a map of role definitions and a formatted list for system prompts.
6
+ */
7
+
8
+ import { join } from 'node:path';
9
+ import { loadRolesFromDir } from '../roles/loader.ts';
10
+ import type { RoleDefinition } from '../roles/types.ts';
11
+
12
+ /** Package root — resolves correctly whether running from repo or global install */
13
+ const PACKAGE_ROOT = join(import.meta.dir, '../..');
14
+
15
+ /**
16
+ * Discover specialist roles from a directory.
17
+ * Resolves relative paths against the package root (not CWD).
18
+ */
19
+ export function discoverSpecialists(dir: string): Map<string, RoleDefinition> {
20
+ const resolved = dir.startsWith('/') ? dir : join(PACKAGE_ROOT, dir);
21
+ return loadRolesFromDir(resolved);
22
+ }
23
+
24
+ /**
25
+ * Format the specialist map into a human-readable list for system prompts.
26
+ * The PA sees this and knows what specialists it can delegate to.
27
+ */
28
+ export function formatSpecialistList(specialists: Map<string, RoleDefinition>): string {
29
+ if (specialists.size === 0) {
30
+ return '';
31
+ }
32
+
33
+ const lines: string[] = ['## Available Specialists', ''];
34
+
35
+ for (const [id, role] of specialists) {
36
+ const desc = role.description.split('\n')
37
+ .map(l => l.trim())
38
+ .filter(l => l.length > 0)[0] ?? '';
39
+ const tools = role.tools.join(', ');
40
+ lines.push(`- **${role.name}** (\`${id}\`): ${desc} [tools: ${tools}]`);
41
+ }
42
+
43
+ lines.push('');
44
+ lines.push('## Delegation Strategy');
45
+ lines.push('');
46
+ lines.push('**Quick tasks** (research a question, write a draft, analyze data):');
47
+ lines.push('Use `delegate_task` — spawns a specialist, runs to completion, returns result. Blocks until done.');
48
+ lines.push('');
49
+ lines.push('**Complex / parallel work** (research + write, compare multiple topics, multi-step analysis):');
50
+ lines.push('Use `manage_agents`:');
51
+ lines.push('1. `spawn` the specialists you need');
52
+ lines.push('2. `assign` tasks to them (they run in the background in parallel)');
53
+ lines.push('3. `status` to check progress');
54
+ lines.push('4. `collect` results when done');
55
+ lines.push('5. `terminate` agents when no longer needed');
56
+ lines.push('');
57
+ lines.push('**When to delegate:** When a task falls into a specialist\'s domain and benefits from focused expertise.');
58
+ lines.push('For simple questions, handle them yourself. The user can explicitly ask to delegate.');
59
+
60
+ return lines.join('\n');
61
+ }