@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,305 @@
1
+ /**
2
+ * Desktop Tools — Desktop Automation via Sidecar RPC or Local Execution
3
+ *
4
+ * 9 tools for controlling desktop applications. Each tool accepts a `target`
5
+ * parameter to route to a specific sidecar. Without `target`, attempts local
6
+ * execution (currently stub — TODO: implement per-platform AppController).
7
+ * Respects --no-local-tools flag.
8
+ *
9
+ * The same tools work on all platforms (Windows, macOS, Linux). The sidecar
10
+ * handles platform-specific implementation details internally.
11
+ */
12
+
13
+ import type { ToolDefinition, ToolResult } from './registry.ts';
14
+ import { routeToSidecar } from './sidecar-route.ts';
15
+ import { isNoLocalTools } from './local-tools-guard.ts';
16
+
17
+ const LOCAL_NOT_IMPLEMENTED = 'Error: Local desktop tool execution is not yet implemented. Specify a "target" sidecar to route this command to a remote machine, or use list_sidecars to see available sidecars.';
18
+ const LOCAL_DISABLED_MSG = 'Error: Local tool execution is disabled (--no-local-tools). Specify a "target" sidecar to route this command to a remote machine. Use list_sidecars to see available sidecars.';
19
+
20
+ function localGuard(): string {
21
+ if (isNoLocalTools()) return LOCAL_DISABLED_MSG;
22
+ return LOCAL_NOT_IMPLEMENTED;
23
+ }
24
+
25
+ // --- Tool definitions ---
26
+
27
+ export const desktopListWindowsTool: ToolDefinition = {
28
+ name: 'desktop_list_windows',
29
+ description: 'List all visible windows on the desktop. Returns window titles, PIDs, class names, and positions. Use the PID with other desktop tools to target a specific window.',
30
+ category: 'desktop',
31
+ parameters: {
32
+ target: {
33
+ type: 'string',
34
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
35
+ required: false,
36
+ },
37
+ },
38
+ execute: async (params) => {
39
+ const target = params.target as string | undefined;
40
+ if (target) {
41
+ return routeToSidecar(target, 'list_windows', params, 'desktop');
42
+ }
43
+ return localGuard();
44
+ },
45
+ };
46
+
47
+ export const desktopSnapshotTool: ToolDefinition = {
48
+ name: 'desktop_snapshot',
49
+ description: 'Get the UI element tree of a window (like browser_snapshot but for desktop apps). Each element has an [id] you can use with desktop_click and desktop_type. If no pid is given, snapshots the active (focused) window.',
50
+ category: 'desktop',
51
+ parameters: {
52
+ target: {
53
+ type: 'string',
54
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
55
+ required: false,
56
+ },
57
+ pid: {
58
+ type: 'number',
59
+ description: 'Process ID of the window (from desktop_list_windows). Omit for the active window.',
60
+ required: false,
61
+ },
62
+ depth: {
63
+ type: 'number',
64
+ description: 'Max tree depth to walk (default: 8). Decrease for faster but shallower snapshots.',
65
+ required: false,
66
+ },
67
+ },
68
+ execute: async (params) => {
69
+ const target = params.target as string | undefined;
70
+ if (target) {
71
+ return routeToSidecar(target, 'get_window_tree', params, 'desktop');
72
+ }
73
+ return localGuard();
74
+ },
75
+ };
76
+
77
+ export const desktopClickTool: ToolDefinition = {
78
+ name: 'desktop_click',
79
+ description: 'Click or interact with a UI element by its [id] from the last desktop_snapshot or desktop_find_element. Default action is "click". Use the action parameter for richer interactions like double_click, right_click, invoke, toggle, set_value, expand, etc. Available actions vary by platform.',
80
+ category: 'desktop',
81
+ parameters: {
82
+ target: {
83
+ type: 'string',
84
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
85
+ required: false,
86
+ },
87
+ element_id: {
88
+ type: 'number',
89
+ description: 'The [id] of the element to interact with (from desktop_snapshot or desktop_find_element)',
90
+ required: true,
91
+ },
92
+ action: {
93
+ type: 'string',
94
+ description: 'Action to perform: click (default), double_click, right_click, invoke, toggle, select, set_value, get_value, get_text, expand, collapse, scroll_into_view, focus',
95
+ required: false,
96
+ },
97
+ value: {
98
+ type: 'string',
99
+ description: 'Value to set (only for set_value action)',
100
+ required: false,
101
+ },
102
+ },
103
+ execute: async (params) => {
104
+ const target = params.target as string | undefined;
105
+ if (target) {
106
+ return routeToSidecar(target, 'click_element', params, 'desktop');
107
+ }
108
+ return localGuard();
109
+ },
110
+ };
111
+
112
+ export const desktopTypeTool: ToolDefinition = {
113
+ name: 'desktop_type',
114
+ description: 'Type text into a UI element. Optionally provide an element_id to click and focus it first. Without element_id, types into whatever is currently focused.',
115
+ category: 'desktop',
116
+ parameters: {
117
+ target: {
118
+ type: 'string',
119
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
120
+ required: false,
121
+ },
122
+ text: {
123
+ type: 'string',
124
+ description: 'The text to type',
125
+ required: true,
126
+ },
127
+ element_id: {
128
+ type: 'number',
129
+ description: 'Optional [id] of element to click before typing (from desktop_snapshot)',
130
+ required: false,
131
+ },
132
+ },
133
+ execute: async (params) => {
134
+ const target = params.target as string | undefined;
135
+ if (target) {
136
+ return routeToSidecar(target, 'type_text', params, 'desktop');
137
+ }
138
+ return localGuard();
139
+ },
140
+ };
141
+
142
+ export const desktopPressKeysTool: ToolDefinition = {
143
+ name: 'desktop_press_keys',
144
+ description: 'Press a keyboard shortcut or key combination. Keys are pressed simultaneously (e.g., "ctrl,s" for save, "alt,f4" to close). Single keys also work: "enter", "tab", "escape".',
145
+ category: 'desktop',
146
+ parameters: {
147
+ target: {
148
+ type: 'string',
149
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
150
+ required: false,
151
+ },
152
+ keys: {
153
+ type: 'string',
154
+ description: 'Comma-separated key names (e.g., "ctrl,s" or "alt,f4" or "enter"). Modifiers: ctrl, alt, shift, win.',
155
+ required: true,
156
+ },
157
+ },
158
+ execute: async (params) => {
159
+ const target = params.target as string | undefined;
160
+ if (target) {
161
+ return routeToSidecar(target, 'press_keys', params, 'desktop');
162
+ }
163
+ return localGuard();
164
+ },
165
+ };
166
+
167
+ export const desktopLaunchAppTool: ToolDefinition = {
168
+ name: 'desktop_launch_app',
169
+ description: 'Launch an application by executable path or name (e.g., "notepad.exe", "calc.exe"). Returns the PID of the launched process.',
170
+ category: 'desktop',
171
+ parameters: {
172
+ target: {
173
+ type: 'string',
174
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
175
+ required: false,
176
+ },
177
+ executable: {
178
+ type: 'string',
179
+ description: 'Application executable path or name',
180
+ required: true,
181
+ },
182
+ args: {
183
+ type: 'string',
184
+ description: 'Optional command-line arguments',
185
+ required: false,
186
+ },
187
+ },
188
+ execute: async (params) => {
189
+ const target = params.target as string | undefined;
190
+ if (target) {
191
+ return routeToSidecar(target, 'launch_app', params, 'desktop');
192
+ }
193
+ return localGuard();
194
+ },
195
+ };
196
+
197
+ export const desktopScreenshotTool: ToolDefinition = {
198
+ name: 'desktop_screenshot',
199
+ description: 'Take a screenshot of the entire desktop or a specific window. The image is sent directly to the AI for visual analysis. Useful for complex UIs, graphics apps, or when the element tree is insufficient.',
200
+ category: 'desktop',
201
+ parameters: {
202
+ target: {
203
+ type: 'string',
204
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
205
+ required: false,
206
+ },
207
+ pid: {
208
+ type: 'number',
209
+ description: 'Process ID of window to capture. Omit for full desktop screenshot.',
210
+ required: false,
211
+ },
212
+ },
213
+ execute: async (params) => {
214
+ const target = params.target as string | undefined;
215
+ if (target) {
216
+ return routeToSidecar(target, 'capture_screen', params, 'screenshot');
217
+ }
218
+ return localGuard();
219
+ },
220
+ };
221
+
222
+ export const desktopFocusWindowTool: ToolDefinition = {
223
+ name: 'desktop_focus_window',
224
+ description: 'Bring a window to the foreground by its PID (from desktop_list_windows). Use this before interacting with a background window.',
225
+ category: 'desktop',
226
+ parameters: {
227
+ target: {
228
+ type: 'string',
229
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
230
+ required: false,
231
+ },
232
+ pid: {
233
+ type: 'number',
234
+ description: 'Process ID of the window to focus',
235
+ required: true,
236
+ },
237
+ },
238
+ execute: async (params) => {
239
+ const target = params.target as string | undefined;
240
+ if (target) {
241
+ return routeToSidecar(target, 'focus_window', params, 'desktop');
242
+ }
243
+ return localGuard();
244
+ },
245
+ };
246
+
247
+ export const desktopFindElementTool: ToolDefinition = {
248
+ name: 'desktop_find_element',
249
+ description: 'Search for UI elements by property (name, control type, class name, automation ID). Returns matching elements with [id] for use with desktop_click and desktop_type. Useful when you know what you are looking for without scanning the full tree.',
250
+ category: 'desktop',
251
+ parameters: {
252
+ target: {
253
+ type: 'string',
254
+ description: 'Sidecar name or ID to route this command to (omit for local execution)',
255
+ required: false,
256
+ },
257
+ pid: {
258
+ type: 'number',
259
+ description: 'Process ID of the window. Omit for the foreground window.',
260
+ required: false,
261
+ },
262
+ name: {
263
+ type: 'string',
264
+ description: 'Element name to search for (exact match)',
265
+ required: false,
266
+ },
267
+ control_type: {
268
+ type: 'string',
269
+ description: 'Control type to filter by (e.g., Button, Edit, Text, ComboBox, ListItem, TreeItem, MenuItem, Tab)',
270
+ required: false,
271
+ },
272
+ automation_id: {
273
+ type: 'string',
274
+ description: 'AutomationId to search for (Windows only, ignored on other platforms)',
275
+ required: false,
276
+ },
277
+ class_name: {
278
+ type: 'string',
279
+ description: 'Class name to search for',
280
+ required: false,
281
+ },
282
+ },
283
+ execute: async (params) => {
284
+ const target = params.target as string | undefined;
285
+ if (target) {
286
+ return routeToSidecar(target, 'find_element', params, 'desktop');
287
+ }
288
+ return localGuard();
289
+ },
290
+ };
291
+
292
+ /**
293
+ * All desktop tools in a single array — platform-agnostic.
294
+ */
295
+ export const DESKTOP_TOOLS: ToolDefinition[] = [
296
+ desktopListWindowsTool,
297
+ desktopSnapshotTool,
298
+ desktopClickTool,
299
+ desktopTypeTool,
300
+ desktopPressKeysTool,
301
+ desktopLaunchAppTool,
302
+ desktopScreenshotTool,
303
+ desktopFocusWindowTool,
304
+ desktopFindElementTool,
305
+ ];
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Manage Goals Tool — Chat-Driven Goal Pursuit
3
+ *
4
+ * Allows the agent to create, list, score, update, decompose, replan,
5
+ * and run daily rhythm from natural language chat.
6
+ */
7
+
8
+ import type { ToolDefinition } from './registry.ts';
9
+ import type { GoalService } from '../../goals/service.ts';
10
+ import type { NLGoalBuilder } from '../../goals/nl-builder.ts';
11
+ import type { GoalEstimator } from '../../goals/estimator.ts';
12
+ import type { DailyRhythm } from '../../goals/rhythm.ts';
13
+ import type { AccountabilityEngine } from '../../goals/accountability.ts';
14
+ import * as vault from '../../vault/goals.ts';
15
+
16
+ export type GoalToolDeps = {
17
+ goalService: GoalService;
18
+ nlBuilder: NLGoalBuilder;
19
+ estimator: GoalEstimator;
20
+ rhythm: DailyRhythm;
21
+ accountability: AccountabilityEngine;
22
+ };
23
+
24
+ export function createManageGoalsTool(deps: GoalToolDeps): ToolDefinition {
25
+ return {
26
+ name: 'manage_goals',
27
+ description: [
28
+ 'Manage OKR-style goals with hierarchical structure (objective → key_result → milestone → task → daily_action).',
29
+ 'Google-style 0.0-1.0 scoring (0.7 = good, 1.0 = aimed too low).',
30
+ '',
31
+ 'Actions: create, list, get, score, update_status, update, decompose, replan, estimate,',
32
+ ' morning_plan, evening_review, metrics, delete, tree, overdue, escalations',
33
+ ].join('\n'),
34
+ category: 'goals',
35
+ parameters: {
36
+ action: {
37
+ type: 'string',
38
+ description: 'The action to perform',
39
+ required: true,
40
+ },
41
+ text: {
42
+ type: 'string',
43
+ description: 'Natural language goal description (for "create")',
44
+ required: false,
45
+ },
46
+ goal_id: {
47
+ type: 'string',
48
+ description: 'Target goal ID',
49
+ required: false,
50
+ },
51
+ score: {
52
+ type: 'number',
53
+ description: 'Score value 0.0-1.0 (for "score")',
54
+ required: false,
55
+ },
56
+ reason: {
57
+ type: 'string',
58
+ description: 'Reason for score update',
59
+ required: false,
60
+ },
61
+ status: {
62
+ type: 'string',
63
+ description: 'New status (for "update_status"): draft, active, paused, completed, failed, killed',
64
+ required: false,
65
+ },
66
+ title: {
67
+ type: 'string',
68
+ description: 'Goal title (for quick "create" without NL)',
69
+ required: false,
70
+ },
71
+ level: {
72
+ type: 'string',
73
+ description: 'Goal level: objective, key_result, milestone, task, daily_action',
74
+ required: false,
75
+ },
76
+ parent_id: {
77
+ type: 'string',
78
+ description: 'Parent goal ID (for "create")',
79
+ required: false,
80
+ },
81
+ filter_status: {
82
+ type: 'string',
83
+ description: 'Filter by status (for "list")',
84
+ required: false,
85
+ },
86
+ filter_level: {
87
+ type: 'string',
88
+ description: 'Filter by level (for "list")',
89
+ required: false,
90
+ },
91
+ limit: {
92
+ type: 'number',
93
+ description: 'Max results (for "list")',
94
+ required: false,
95
+ },
96
+ },
97
+ execute: async (params) => {
98
+ const action = String(params.action ?? '').toLowerCase();
99
+
100
+ switch (action) {
101
+ case 'create': {
102
+ const text = params.text as string | undefined;
103
+ const title = params.title as string | undefined;
104
+
105
+ if (text) {
106
+ // NL goal creation
107
+ try {
108
+ const proposal = await deps.nlBuilder.parseGoal(text);
109
+
110
+ if (proposal.clarifying_questions?.length) {
111
+ return `Before creating this goal, I have some questions:\n${proposal.clarifying_questions.map((q, i) => `${i + 1}. ${q}`).join('\n')}\n\nPlease answer these and I'll create the full OKR breakdown.`;
112
+ }
113
+
114
+ const goals = deps.nlBuilder.createFromProposal(
115
+ proposal,
116
+ params.parent_id as string | undefined,
117
+ );
118
+
119
+ const summary = goals.map(g =>
120
+ `${' '.repeat(levelDepth(g.level))}${g.level}: ${g.title}`
121
+ ).join('\n');
122
+
123
+ return `Created ${goals.length} goals:\n${summary}`;
124
+ } catch (err) {
125
+ return `Error creating goal from NL: ${err instanceof Error ? err.message : err}`;
126
+ }
127
+ }
128
+
129
+ if (title) {
130
+ // Quick creation
131
+ const level = (params.level as string) ?? 'task';
132
+ const goal = deps.goalService.createGoal(title, level as any, {
133
+ parent_id: params.parent_id as string | undefined,
134
+ });
135
+ return `Created ${level}: "${goal.title}" (${goal.id})`;
136
+ }
137
+
138
+ return 'Error: Provide either "text" (NL description) or "title" (quick create).';
139
+ }
140
+
141
+ case 'list': {
142
+ const goals = vault.findGoals({
143
+ status: params.filter_status as any,
144
+ level: params.filter_level as any,
145
+ limit: params.limit as number ?? 20,
146
+ });
147
+
148
+ if (goals.length === 0) return 'No goals found matching filters.';
149
+
150
+ return goals.map(g =>
151
+ `[${g.status}] ${g.title} (${g.level}, score: ${g.score}, health: ${g.health})` +
152
+ (g.deadline ? ` — due ${new Date(g.deadline).toLocaleDateString()}` : '')
153
+ ).join('\n');
154
+ }
155
+
156
+ case 'get': {
157
+ const goalId = params.goal_id as string;
158
+ if (!goalId) return 'Error: "goal_id" is required.';
159
+
160
+ const goal = vault.getGoal(goalId);
161
+ if (!goal) return `Goal "${goalId}" not found.`;
162
+
163
+ const children = vault.getGoalChildren(goalId);
164
+ const progress = vault.getProgressHistory(goalId, 5);
165
+
166
+ let result = `**${goal.title}**\n`;
167
+ result += `Level: ${goal.level} | Status: ${goal.status} | Health: ${goal.health}\n`;
168
+ result += `Score: ${goal.score}${goal.score_reason ? ` (${goal.score_reason})` : ''}\n`;
169
+ result += `Time horizon: ${goal.time_horizon}\n`;
170
+ if (goal.description) result += `Description: ${goal.description}\n`;
171
+ if (goal.success_criteria) result += `Success criteria: ${goal.success_criteria}\n`;
172
+ if (goal.deadline) result += `Deadline: ${new Date(goal.deadline).toLocaleDateString()}\n`;
173
+ if (goal.tags.length) result += `Tags: ${goal.tags.join(', ')}\n`;
174
+ if (goal.escalation_stage !== 'none') result += `Escalation: ${goal.escalation_stage}\n`;
175
+
176
+ if (children.length > 0) {
177
+ result += `\nChildren (${children.length}):\n`;
178
+ result += children.map(c => ` - ${c.title} (${c.level}, ${c.score})`).join('\n');
179
+ }
180
+
181
+ if (progress.length > 0) {
182
+ result += `\nRecent progress:\n`;
183
+ result += progress.map(p =>
184
+ ` ${new Date(p.created_at).toLocaleDateString()}: ${p.score_before} → ${p.score_after} (${p.note})`
185
+ ).join('\n');
186
+ }
187
+
188
+ return result;
189
+ }
190
+
191
+ case 'score': {
192
+ const goalId = params.goal_id as string;
193
+ const score = params.score as number;
194
+ const reason = (params.reason as string) ?? '';
195
+ if (!goalId) return 'Error: "goal_id" is required.';
196
+ if (score === undefined || score === null) return 'Error: "score" is required (0.0-1.0).';
197
+
198
+ const goal = deps.goalService.scoreGoal(goalId, score, reason);
199
+ if (!goal) return `Goal "${goalId}" not found.`;
200
+ return `Updated score for "${goal.title}": ${goal.score} — ${reason}`;
201
+ }
202
+
203
+ case 'update_status': {
204
+ const goalId = params.goal_id as string;
205
+ const status = params.status as string;
206
+ if (!goalId) return 'Error: "goal_id" is required.';
207
+ if (!status) return 'Error: "status" is required.';
208
+
209
+ const goal = deps.goalService.updateStatus(goalId, status as any);
210
+ if (!goal) return `Goal "${goalId}" not found.`;
211
+ return `Updated "${goal.title}" status to: ${goal.status}`;
212
+ }
213
+
214
+ case 'update': {
215
+ const goalId = params.goal_id as string;
216
+ if (!goalId) return 'Error: "goal_id" is required.';
217
+
218
+ const updates: Record<string, unknown> = {};
219
+ if (params.title) updates.title = params.title;
220
+ if (params.reason) updates.description = params.reason; // overloaded field
221
+
222
+ const goal = deps.goalService.updateGoal(goalId, updates as any);
223
+ if (!goal) return `Goal "${goalId}" not found.`;
224
+ return `Updated "${goal.title}".`;
225
+ }
226
+
227
+ case 'decompose': {
228
+ const goalId = params.goal_id as string;
229
+ if (!goalId) return 'Error: "goal_id" is required.';
230
+
231
+ try {
232
+ const proposal = await deps.nlBuilder.decompose(goalId);
233
+ if (!proposal) return 'Could not decompose — goal not found or already at lowest level.';
234
+
235
+ const goals = deps.nlBuilder.createFromProposal(proposal, goalId);
236
+ const summary = goals.map(g =>
237
+ `${' '.repeat(levelDepth(g.level))}${g.level}: ${g.title}`
238
+ ).join('\n');
239
+ return `Decomposed into ${goals.length} sub-goals:\n${summary}`;
240
+ } catch (err) {
241
+ return `Decomposition error: ${err instanceof Error ? err.message : err}`;
242
+ }
243
+ }
244
+
245
+ case 'replan': {
246
+ const goalId = params.goal_id as string;
247
+ if (!goalId) return 'Error: "goal_id" is required.';
248
+
249
+ const goal = vault.getGoal(goalId);
250
+ if (!goal) return `Goal "${goalId}" not found.`;
251
+
252
+ try {
253
+ const analysis = await deps.accountability.generateReplanOptions(goal);
254
+ let result = `**Replan Analysis: ${goal.title}**\n\n`;
255
+ result += `Analysis: ${analysis.analysis}\n\n`;
256
+ result += `Options:\n`;
257
+ for (const opt of analysis.options) {
258
+ result += ` [${opt.impact.toUpperCase()}] ${opt.label}: ${opt.description}\n`;
259
+ }
260
+ result += `\nRecommendation: ${analysis.recommendation}`;
261
+ return result;
262
+ } catch (err) {
263
+ return `Replan error: ${err instanceof Error ? err.message : err}`;
264
+ }
265
+ }
266
+
267
+ case 'estimate': {
268
+ const goalId = params.goal_id as string;
269
+ if (!goalId) return 'Error: "goal_id" is required.';
270
+
271
+ try {
272
+ const estimate = await deps.estimator.estimate(goalId);
273
+ if (!estimate) return `Goal "${goalId}" not found.`;
274
+
275
+ return `**Estimate:** ${estimate.final_estimate_hours}h (confidence: ${(estimate.confidence * 100).toFixed(0)}%)\n` +
276
+ `LLM: ${estimate.llm_estimate_hours}h | Historical: ${estimate.historical_estimate_hours ?? 'N/A'}h\n` +
277
+ `${estimate.reasoning}` +
278
+ (estimate.similar_past_goals.length > 0
279
+ ? `\nBased on ${estimate.similar_past_goals.length} similar past goal(s)`
280
+ : '');
281
+ } catch (err) {
282
+ return `Estimation error: ${err instanceof Error ? err.message : err}`;
283
+ }
284
+ }
285
+
286
+ case 'morning_plan': {
287
+ try {
288
+ const result = await deps.rhythm.runMorningPlan();
289
+ let output = `**Morning Plan**\n\n`;
290
+ output += `${result.message}\n\n`;
291
+ if (result.warnings.length) {
292
+ output += `Warnings:\n${result.warnings.map(w => ` ⚠ ${w}`).join('\n')}\n\n`;
293
+ }
294
+ output += `Focus areas:\n${result.focusAreas.map(f => ` → ${f}`).join('\n')}\n\n`;
295
+ output += `Today's actions:\n${result.dailyActions.map(a => ` □ ${a}`).join('\n')}`;
296
+ return output;
297
+ } catch (err) {
298
+ return `Morning plan error: ${err instanceof Error ? err.message : err}`;
299
+ }
300
+ }
301
+
302
+ case 'evening_review': {
303
+ try {
304
+ const result = await deps.rhythm.runEveningReview();
305
+ let output = `**Evening Review**\n\n`;
306
+ output += `${result.message}\n\n`;
307
+ output += `Assessment: ${result.assessment}\n`;
308
+ if (result.scoreUpdates.length) {
309
+ output += `\nScore updates:\n${result.scoreUpdates.map(s => ` ${s.goalId}: ${s.newScore} — ${s.reason}`).join('\n')}`;
310
+ }
311
+ return output;
312
+ } catch (err) {
313
+ return `Evening review error: ${err instanceof Error ? err.message : err}`;
314
+ }
315
+ }
316
+
317
+ case 'metrics': {
318
+ const m = deps.goalService.getMetrics();
319
+ return `**Goal Metrics**\n` +
320
+ `Total: ${m.total} | Active: ${m.active} | Completed: ${m.completed}\n` +
321
+ `Failed: ${m.failed} | Killed: ${m.killed}\n` +
322
+ `Avg OKR Score: ${m.avg_score.toFixed(2)}\n` +
323
+ `On Track: ${m.on_track} | At Risk: ${m.at_risk} | Behind: ${m.behind} | Critical: ${m.critical}\n` +
324
+ `Overdue: ${m.overdue}`;
325
+ }
326
+
327
+ case 'delete': {
328
+ const goalId = params.goal_id as string;
329
+ if (!goalId) return 'Error: "goal_id" is required.';
330
+ const deleted = deps.goalService.deleteGoal(goalId);
331
+ return deleted ? `Goal deleted (and all children).` : `Goal "${goalId}" not found.`;
332
+ }
333
+
334
+ case 'tree': {
335
+ const goalId = params.goal_id as string;
336
+ if (!goalId) return 'Error: "goal_id" is required.';
337
+
338
+ const tree = vault.getGoalTree(goalId);
339
+ if (tree.length === 0) return `Goal "${goalId}" not found.`;
340
+
341
+ return tree.map(g =>
342
+ `${' '.repeat(levelDepth(g.level))}[${g.status}] ${g.title} (${g.level}, ${g.score})`
343
+ ).join('\n');
344
+ }
345
+
346
+ case 'overdue': {
347
+ const overdue = vault.getOverdueGoals();
348
+ if (overdue.length === 0) return 'No overdue goals. Keep it up.';
349
+ return `**${overdue.length} Overdue Goal(s):**\n` +
350
+ overdue.map(g =>
351
+ ` ${g.title} — due ${new Date(g.deadline!).toLocaleDateString()} (score: ${g.score})`
352
+ ).join('\n');
353
+ }
354
+
355
+ case 'escalations': {
356
+ const actions = deps.accountability.runEscalationCheck();
357
+ if (actions.length === 0) return 'No goals need escalation right now.';
358
+ return `**${actions.length} Escalation(s) Needed:**\n` +
359
+ actions.map(a =>
360
+ ` ${a.goalTitle}: ${a.currentStage} → ${a.newStage} (${a.weeksBehind} weeks behind)\n ${a.message}`
361
+ ).join('\n\n');
362
+ }
363
+
364
+ default:
365
+ return `Unknown action "${action}". Available: create, list, get, score, update_status, update, decompose, replan, estimate, morning_plan, evening_review, metrics, delete, tree, overdue, escalations`;
366
+ }
367
+ },
368
+ };
369
+ }
370
+
371
+ function levelDepth(level: string): number {
372
+ const depths: Record<string, number> = {
373
+ objective: 0, key_result: 1, milestone: 2, task: 3, daily_action: 4,
374
+ };
375
+ return depths[level] ?? 0;
376
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Local Tools Guard — Module-level flag for --no-local-tools mode.
3
+ *
4
+ * Separate file to avoid circular dependencies between builtin.ts and desktop.ts.
5
+ */
6
+
7
+ let _noLocalTools = false;
8
+
9
+ export function setNoLocalTools(enabled: boolean): void {
10
+ _noLocalTools = enabled;
11
+ if (enabled) {
12
+ console.log('[Tools] Local tool execution disabled (--no-local-tools). Tools require a target sidecar.');
13
+ }
14
+ }
15
+
16
+ export function isNoLocalTools(): boolean {
17
+ return _noLocalTools;
18
+ }
19
+
20
+ export const LOCAL_DISABLED_MSG = 'Error: Local tool execution is disabled (--no-local-tools). Specify a "target" sidecar to route this command to a remote machine. Use list_sidecars to see available sidecars.';