screenhand 0.2.0 → 0.3.1

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 (212) hide show
  1. package/README.md +165 -446
  2. package/bin/darwin-arm64/macos-bridge +0 -0
  3. package/dist/mcp-desktop.js +3615 -400
  4. package/dist/scripts/export-help-center.js +112 -0
  5. package/dist/scripts/marketing-loop.js +117 -0
  6. package/dist/scripts/observer-daemon.js +288 -0
  7. package/dist/scripts/orchestrator-daemon.js +399 -0
  8. package/dist/scripts/threads-campaign.js +208 -0
  9. package/dist/src/community/fetcher.js +109 -0
  10. package/dist/src/community/index.js +6 -0
  11. package/dist/src/community/publisher.js +191 -0
  12. package/dist/src/community/remote-api.js +121 -0
  13. package/dist/src/community/types.js +3 -0
  14. package/dist/src/community/validator.js +95 -0
  15. package/dist/src/context-tracker.js +489 -0
  16. package/dist/src/ingestion/coverage-auditor.js +233 -0
  17. package/dist/src/ingestion/doc-parser.js +164 -0
  18. package/dist/src/ingestion/index.js +8 -0
  19. package/dist/src/ingestion/menu-scanner.js +152 -0
  20. package/dist/src/ingestion/reference-merger.js +186 -0
  21. package/dist/src/ingestion/shortcut-extractor.js +180 -0
  22. package/dist/src/ingestion/tutorial-extractor.js +170 -0
  23. package/dist/src/ingestion/types.js +3 -0
  24. package/dist/src/jobs/manager.js +82 -14
  25. package/dist/src/jobs/runner.js +138 -15
  26. package/dist/src/learning/engine.js +356 -0
  27. package/dist/src/learning/index.js +9 -0
  28. package/dist/src/learning/locator-policy.js +120 -0
  29. package/dist/src/learning/pattern-policy.js +89 -0
  30. package/dist/src/learning/recovery-policy.js +116 -0
  31. package/dist/src/learning/sensor-policy.js +115 -0
  32. package/dist/src/learning/timing-model.js +204 -0
  33. package/dist/src/learning/topology-policy.js +90 -0
  34. package/dist/src/learning/types.js +9 -0
  35. package/dist/src/logging/timeline-logger.js +4 -1
  36. package/dist/src/memory/playbook-seeds.js +200 -0
  37. package/dist/src/memory/recall.js +60 -8
  38. package/dist/src/memory/service.js +30 -5
  39. package/dist/src/memory/store.js +34 -5
  40. package/dist/src/native/bridge-client.js +253 -31
  41. package/dist/src/observer/state.js +199 -0
  42. package/dist/src/observer/types.js +43 -0
  43. package/dist/src/orchestrator/state.js +68 -0
  44. package/dist/src/orchestrator/types.js +22 -0
  45. package/dist/src/perception/ax-source.js +162 -0
  46. package/dist/src/perception/cdp-source.js +162 -0
  47. package/dist/src/perception/coordinator.js +771 -0
  48. package/dist/src/perception/frame-differ.js +287 -0
  49. package/dist/src/perception/index.js +22 -0
  50. package/dist/src/perception/manager.js +199 -0
  51. package/dist/src/perception/types.js +47 -0
  52. package/dist/src/perception/vision-source.js +399 -0
  53. package/dist/src/planner/deterministic.js +298 -0
  54. package/dist/src/planner/executor.js +870 -0
  55. package/dist/src/planner/goal-store.js +92 -0
  56. package/dist/src/planner/index.js +21 -0
  57. package/dist/src/planner/planner.js +520 -0
  58. package/dist/src/planner/tool-registry.js +71 -0
  59. package/dist/src/planner/types.js +22 -0
  60. package/dist/src/platform/explorer.js +213 -0
  61. package/dist/src/platform/help-center-markdown.js +527 -0
  62. package/dist/src/platform/learner.js +257 -0
  63. package/dist/src/playbook/engine.js +296 -11
  64. package/dist/src/playbook/mcp-recorder.js +204 -0
  65. package/dist/src/playbook/recorder.js +3 -2
  66. package/dist/src/playbook/runner.js +1 -1
  67. package/dist/src/playbook/store.js +139 -10
  68. package/dist/src/recovery/detectors.js +156 -0
  69. package/dist/src/recovery/engine.js +327 -0
  70. package/dist/src/recovery/index.js +20 -0
  71. package/dist/src/recovery/strategies.js +274 -0
  72. package/dist/src/recovery/types.js +20 -0
  73. package/dist/src/runtime/accessibility-adapter.js +55 -18
  74. package/dist/src/runtime/applescript-adapter.js +8 -2
  75. package/dist/src/runtime/cdp-chrome-adapter.js +1 -1
  76. package/dist/src/runtime/executor.js +23 -3
  77. package/dist/src/runtime/locator-cache.js +24 -2
  78. package/dist/src/runtime/service.js +59 -15
  79. package/dist/src/runtime/session-manager.js +4 -1
  80. package/dist/src/runtime/vision-adapter.js +2 -1
  81. package/dist/src/state/app-map-types.js +72 -0
  82. package/dist/src/state/app-map.js +1974 -0
  83. package/dist/src/state/entity-tracker.js +108 -0
  84. package/dist/src/state/fusion.js +96 -0
  85. package/dist/src/state/index.js +21 -0
  86. package/dist/src/state/ladder-generator.js +236 -0
  87. package/dist/src/state/persistence.js +156 -0
  88. package/dist/src/state/types.js +17 -0
  89. package/dist/src/state/world-model.js +1456 -0
  90. package/dist/src/util/atomic-write.js +19 -4
  91. package/dist/src/util/sanitize.js +146 -0
  92. package/dist-app-maps/com.figma.Desktop.json +959 -0
  93. package/dist-app-maps/com.hnc.Discord.json +1146 -0
  94. package/dist-app-maps/notion.id.json +2831 -0
  95. package/dist-playbooks/canva-screenhand-carousel.json +445 -0
  96. package/dist-playbooks/codex-desktop.json +76 -0
  97. package/dist-playbooks/competitor-research-stack.json +122 -0
  98. package/dist-playbooks/davinci-color-grade.json +153 -0
  99. package/dist-playbooks/davinci-edit-timeline.json +162 -0
  100. package/dist-playbooks/davinci-render.json +114 -0
  101. package/dist-playbooks/devto.json +52 -0
  102. package/dist-playbooks/discord.json +41 -0
  103. package/dist-playbooks/google-flow-create-project.json +59 -0
  104. package/dist-playbooks/google-flow-edit-image.json +90 -0
  105. package/dist-playbooks/google-flow-edit-video.json +90 -0
  106. package/dist-playbooks/google-flow-generate-image.json +68 -0
  107. package/dist-playbooks/google-flow-generate-video.json +191 -0
  108. package/dist-playbooks/google-flow-open-project.json +48 -0
  109. package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
  110. package/dist-playbooks/google-flow-search-assets.json +64 -0
  111. package/dist-playbooks/instagram.json +57 -0
  112. package/dist-playbooks/linkedin.json +52 -0
  113. package/dist-playbooks/n8n.json +43 -0
  114. package/dist-playbooks/reddit.json +52 -0
  115. package/dist-playbooks/threads.json +59 -0
  116. package/dist-playbooks/x-twitter.json +59 -0
  117. package/dist-playbooks/youtube.json +59 -0
  118. package/dist-references/canva.json +646 -0
  119. package/dist-references/codex-desktop.json +305 -0
  120. package/dist-references/davinci-resolve-keyboard.json +594 -0
  121. package/dist-references/davinci-resolve-menu-map.json +1139 -0
  122. package/dist-references/davinci-resolve-menus-batch1.json +116 -0
  123. package/dist-references/davinci-resolve-menus-batch2.json +372 -0
  124. package/dist-references/davinci-resolve-menus-batch3.json +330 -0
  125. package/dist-references/davinci-resolve-menus-batch4.json +297 -0
  126. package/dist-references/davinci-resolve-shortcuts.json +333 -0
  127. package/dist-references/devpost.json +186 -0
  128. package/dist-references/devto.json +317 -0
  129. package/dist-references/discord.json +549 -0
  130. package/dist-references/figma.json +1186 -0
  131. package/dist-references/finder.json +146 -0
  132. package/dist-references/google-ads-transparency.json +95 -0
  133. package/dist-references/google-flow.json +649 -0
  134. package/dist-references/instagram.json +341 -0
  135. package/dist-references/linkedin.json +324 -0
  136. package/dist-references/meta-ad-library.json +86 -0
  137. package/dist-references/n8n.json +387 -0
  138. package/dist-references/notes.json +27 -0
  139. package/dist-references/notion.json +163 -0
  140. package/dist-references/reddit.json +341 -0
  141. package/dist-references/threads.json +337 -0
  142. package/dist-references/x-twitter.json +403 -0
  143. package/dist-references/youtube.json +373 -0
  144. package/native/macos-bridge/Package.swift +22 -0
  145. package/native/macos-bridge/Sources/AccessibilityBridge.swift +482 -0
  146. package/native/macos-bridge/Sources/AppManagement.swift +339 -0
  147. package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +537 -0
  148. package/native/macos-bridge/Sources/ObserverBridge.swift +120 -0
  149. package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
  150. package/native/macos-bridge/Sources/VisionBridge.swift +238 -0
  151. package/native/macos-bridge/Sources/main.swift +498 -0
  152. package/native/windows-bridge/AppManagement.cs +234 -0
  153. package/native/windows-bridge/InputBridge.cs +436 -0
  154. package/native/windows-bridge/Program.cs +270 -0
  155. package/native/windows-bridge/ScreenCapture.cs +453 -0
  156. package/native/windows-bridge/UIAutomationBridge.cs +571 -0
  157. package/native/windows-bridge/WindowsBridge.csproj +17 -0
  158. package/package.json +12 -1
  159. package/scripts/postinstall.cjs +127 -0
  160. package/dist/.audit-log.jsonl +0 -55
  161. package/dist/.screenhand/memory/.lock +0 -1
  162. package/dist/.screenhand/memory/actions.jsonl +0 -85
  163. package/dist/.screenhand/memory/errors.jsonl +0 -5
  164. package/dist/.screenhand/memory/errors.jsonl.bak +0 -4
  165. package/dist/.screenhand/memory/state.json +0 -35
  166. package/dist/.screenhand/memory/state.json.bak +0 -35
  167. package/dist/.screenhand/memory/strategies.jsonl +0 -12
  168. package/dist/agent/cli.js +0 -73
  169. package/dist/agent/loop.js +0 -258
  170. package/dist/config.js +0 -9
  171. package/dist/index.js +0 -56
  172. package/dist/logging/timeline-logger.js +0 -29
  173. package/dist/mcp/mcp-stdio-server.js +0 -448
  174. package/dist/mcp/server.js +0 -347
  175. package/dist/mcp-entry.js +0 -59
  176. package/dist/memory/recall.js +0 -160
  177. package/dist/memory/research.js +0 -98
  178. package/dist/memory/seeds.js +0 -89
  179. package/dist/memory/session.js +0 -161
  180. package/dist/memory/store.js +0 -391
  181. package/dist/memory/types.js +0 -4
  182. package/dist/monitor/codex-monitor.js +0 -377
  183. package/dist/monitor/task-queue.js +0 -84
  184. package/dist/monitor/types.js +0 -49
  185. package/dist/native/bridge-client.js +0 -174
  186. package/dist/native/macos-bridge-client.js +0 -5
  187. package/dist/npm-publish-helper.js +0 -117
  188. package/dist/npm-token-cdp.js +0 -113
  189. package/dist/npm-token-create.js +0 -135
  190. package/dist/npm-token-finish.js +0 -126
  191. package/dist/playbook/engine.js +0 -193
  192. package/dist/playbook/index.js +0 -4
  193. package/dist/playbook/recorder.js +0 -519
  194. package/dist/playbook/runner.js +0 -392
  195. package/dist/playbook/store.js +0 -166
  196. package/dist/playbook/types.js +0 -4
  197. package/dist/runtime/accessibility-adapter.js +0 -377
  198. package/dist/runtime/app-adapter.js +0 -48
  199. package/dist/runtime/applescript-adapter.js +0 -283
  200. package/dist/runtime/ax-role-map.js +0 -80
  201. package/dist/runtime/browser-adapter.js +0 -36
  202. package/dist/runtime/cdp-chrome-adapter.js +0 -505
  203. package/dist/runtime/composite-adapter.js +0 -205
  204. package/dist/runtime/executor.js +0 -250
  205. package/dist/runtime/locator-cache.js +0 -12
  206. package/dist/runtime/planning-loop.js +0 -47
  207. package/dist/runtime/service.js +0 -372
  208. package/dist/runtime/session-manager.js +0 -28
  209. package/dist/runtime/state-observer.js +0 -105
  210. package/dist/runtime/vision-adapter.js +0 -208
  211. package/dist/test-mcp-protocol.js +0 -138
  212. package/dist/types.js +0 -1
@@ -1,377 +0,0 @@
1
- /**
2
- * Codex Monitor — watches VS Code terminals for AI agent activity
3
- *
4
- * Uses the native bridge to:
5
- * 1. Find VS Code windows + terminal panels via accessibility tree
6
- * 2. Poll terminal content via OCR (screenshot + vision.ocr)
7
- * 3. Detect agent status (running/idle/error) via pattern matching
8
- * 4. Auto-assign queued tasks when a terminal goes idle
9
- *
10
- * The monitor runs as a background polling loop, similar to PlaybookRecorder.
11
- */
12
- import { TaskQueue } from "./task-queue.js";
13
- import { DEFAULT_MONITOR_CONFIG } from "./types.js";
14
- export class CodexMonitor {
15
- bridge;
16
- terminals = new Map();
17
- pollTimer = null;
18
- config;
19
- running = false;
20
- assignTimers = new Map();
21
- queue;
22
- /** Callback when a terminal status changes */
23
- onStatusChange;
24
- /** Callback when a task is auto-assigned */
25
- onTaskAssigned;
26
- /** Callback for log messages */
27
- onLog;
28
- constructor(bridge, config = {}) {
29
- this.bridge = bridge;
30
- this.config = { ...DEFAULT_MONITOR_CONFIG, ...config };
31
- this.queue = new TaskQueue();
32
- }
33
- log(msg) {
34
- if (this.onLog)
35
- this.onLog(msg);
36
- }
37
- /**
38
- * Start monitoring a VS Code terminal.
39
- * Finds VS Code by PID, identifies terminal panels, begins polling.
40
- */
41
- async addTerminal(opts) {
42
- const id = "term_" + opts.vscodePid + "_" + Date.now().toString(36);
43
- const state = {
44
- id,
45
- vscodePid: opts.vscodePid,
46
- ...(opts.windowId != null ? { windowId: opts.windowId } : {}),
47
- terminalLabel: opts.label ?? "Terminal",
48
- status: "unknown",
49
- lastOutput: "",
50
- lastTask: null,
51
- startedAt: new Date().toISOString(),
52
- lastPollAt: new Date().toISOString(),
53
- tasksCompleted: 0,
54
- taskHistory: [],
55
- };
56
- this.terminals.set(id, state);
57
- this.log(`Added terminal ${id} (pid=${opts.vscodePid})`);
58
- // Do an initial poll
59
- await this.pollTerminal(state);
60
- return state;
61
- }
62
- /** Remove a terminal from monitoring */
63
- removeTerminal(terminalId) {
64
- const timer = this.assignTimers.get(terminalId);
65
- if (timer) {
66
- clearTimeout(timer);
67
- this.assignTimers.delete(terminalId);
68
- }
69
- return this.terminals.delete(terminalId);
70
- }
71
- /** Start the polling loop */
72
- start() {
73
- if (this.running)
74
- return;
75
- this.running = true;
76
- this.pollTimer = setInterval(async () => {
77
- if (!this.running)
78
- return;
79
- for (const terminal of this.terminals.values()) {
80
- try {
81
- await this.pollTerminal(terminal);
82
- }
83
- catch (err) {
84
- this.log(`Poll error for ${terminal.id}: ${err instanceof Error ? err.message : String(err)}`);
85
- }
86
- }
87
- }, this.config.pollIntervalMs);
88
- this.log(`Monitor started (poll every ${this.config.pollIntervalMs}ms)`);
89
- }
90
- /** Stop the polling loop */
91
- stop() {
92
- this.running = false;
93
- if (this.pollTimer) {
94
- clearInterval(this.pollTimer);
95
- this.pollTimer = null;
96
- }
97
- for (const timer of this.assignTimers.values()) {
98
- clearTimeout(timer);
99
- }
100
- this.assignTimers.clear();
101
- this.log("Monitor stopped");
102
- }
103
- /** Get all monitored terminals */
104
- getTerminals() {
105
- return [...this.terminals.values()];
106
- }
107
- /** Get a specific terminal */
108
- getTerminal(id) {
109
- return this.terminals.get(id);
110
- }
111
- get isRunning() {
112
- return this.running;
113
- }
114
- // ── Core polling logic ──
115
- async pollTerminal(terminal) {
116
- terminal.lastPollAt = new Date().toISOString();
117
- // Strategy: use OCR on the VS Code window to read terminal content.
118
- // This is more reliable than AX tree for terminal text content.
119
- const output = await this.readTerminalContent(terminal);
120
- if (output === null)
121
- return; // couldn't read
122
- const oldStatus = terminal.status;
123
- terminal.lastOutput = output;
124
- // Detect status from the last few lines of output
125
- const lastLines = output.split("\n").slice(-15).join("\n");
126
- terminal.status = this.detectStatus(lastLines);
127
- // Status transition handling
128
- if (oldStatus !== terminal.status) {
129
- this.log(`Terminal ${terminal.id}: ${oldStatus} -> ${terminal.status}`);
130
- if (this.onStatusChange) {
131
- this.onStatusChange(terminal, oldStatus);
132
- }
133
- // If terminal just went idle, handle task completion + auto-assign
134
- if (terminal.status === "idle" && (oldStatus === "running" || oldStatus === "unknown")) {
135
- this.handleTerminalIdle(terminal);
136
- }
137
- }
138
- }
139
- /**
140
- * Read terminal content via screenshot + OCR of the VS Code window.
141
- */
142
- async readTerminalContent(terminal) {
143
- try {
144
- let shotPath;
145
- if (terminal.windowId) {
146
- // Capture specific window
147
- const shot = await this.bridge.call("cg.captureWindow", {
148
- windowId: terminal.windowId,
149
- });
150
- shotPath = shot.path;
151
- }
152
- else {
153
- // Try to find VS Code window
154
- const wins = await this.bridge.call("app.windows");
155
- const vscodeWin = wins.find((w) => w.pid === terminal.vscodePid || w.bundleId === "com.microsoft.VSCode");
156
- if (!vscodeWin) {
157
- this.log(`VS Code window not found for pid=${terminal.vscodePid}`);
158
- return null;
159
- }
160
- terminal.windowId = vscodeWin.windowId;
161
- const shot = await this.bridge.call("cg.captureWindow", {
162
- windowId: vscodeWin.windowId,
163
- });
164
- shotPath = shot.path;
165
- }
166
- // OCR the screenshot
167
- const ocr = await this.bridge.call("vision.ocr", {
168
- imagePath: shotPath,
169
- });
170
- return ocr.text;
171
- }
172
- catch (err) {
173
- this.log(`OCR failed for ${terminal.id}: ${err instanceof Error ? err.message : String(err)}`);
174
- // Fallback: try AX tree to read terminal content
175
- return this.readTerminalViaAX(terminal);
176
- }
177
- }
178
- /**
179
- * Fallback: read terminal content from accessibility tree.
180
- */
181
- async readTerminalViaAX(terminal) {
182
- try {
183
- const tree = await this.bridge.call("ax.getElementTree", {
184
- pid: terminal.vscodePid,
185
- maxDepth: 6,
186
- });
187
- // Find terminal text areas in the AX tree
188
- const terminalText = this.extractTerminalText(tree);
189
- return terminalText;
190
- }
191
- catch {
192
- return null;
193
- }
194
- }
195
- /**
196
- * Recursively search AX tree for terminal content.
197
- * VS Code terminals usually show up as AXTextArea or AXGroup with role "terminal".
198
- */
199
- extractTerminalText(node, depth = 0) {
200
- if (depth > 8)
201
- return null;
202
- const role = (node.role || "").toLowerCase();
203
- const title = (node.title || "").toLowerCase();
204
- const desc = (node.description || "").toLowerCase();
205
- // Look for terminal-like elements
206
- const isTerminal = role.includes("terminal") ||
207
- title.includes("terminal") ||
208
- desc.includes("terminal") ||
209
- (role === "textarea" && (title.includes("terminal") || desc.includes("terminal")));
210
- if (isTerminal && node.value) {
211
- return node.value;
212
- }
213
- if (node.children) {
214
- for (const child of node.children) {
215
- const found = this.extractTerminalText(child, depth + 1);
216
- if (found)
217
- return found;
218
- }
219
- }
220
- return null;
221
- }
222
- /**
223
- * Detect Codex status from terminal output text.
224
- */
225
- detectStatus(text) {
226
- const lower = text.toLowerCase();
227
- // Check error patterns first (highest priority)
228
- for (const pattern of this.config.errorPatterns) {
229
- if (lower.includes(pattern.toLowerCase())) {
230
- // Only if it appears in the last few lines
231
- const lastLines = text.split("\n").slice(-5).join("\n").toLowerCase();
232
- if (lastLines.includes(pattern.toLowerCase())) {
233
- return "error";
234
- }
235
- }
236
- }
237
- // Check idle patterns (prompt waiting for input)
238
- const lastLines = text.split("\n").filter((l) => l.trim().length > 0);
239
- const lastLine = lastLines[lastLines.length - 1] ?? "";
240
- const lastLineTrimmed = lastLine.trim();
241
- for (const pattern of this.config.idlePatterns) {
242
- if (lastLineTrimmed.includes(pattern) || lastLineTrimmed.endsWith(pattern.trim())) {
243
- return "idle";
244
- }
245
- }
246
- // Check running patterns
247
- for (const pattern of this.config.runningPatterns) {
248
- if (lower.includes(pattern.toLowerCase())) {
249
- return "running";
250
- }
251
- }
252
- // If we can read content but can't determine status
253
- return "unknown";
254
- }
255
- /**
256
- * Handle a terminal going idle — complete current task and maybe assign next.
257
- */
258
- handleTerminalIdle(terminal) {
259
- // Record task completion if there was an active task
260
- if (terminal.lastTask) {
261
- const completed = {
262
- task: terminal.lastTask,
263
- startedAt: terminal.taskHistory.length > 0
264
- ? terminal.taskHistory[terminal.taskHistory.length - 1]?.completedAt ?? terminal.startedAt
265
- : terminal.startedAt,
266
- completedAt: new Date().toISOString(),
267
- output: terminal.lastOutput.split("\n").slice(-20).join("\n"),
268
- };
269
- terminal.taskHistory.push(completed);
270
- terminal.tasksCompleted++;
271
- terminal.lastTask = null;
272
- // Complete the task in the queue
273
- const runningTask = this.queue.all().find((t) => t.status === "running" && t.terminalId === terminal.id);
274
- if (runningTask) {
275
- this.queue.complete(runningTask.id, completed.output);
276
- }
277
- this.log(`Terminal ${terminal.id}: task completed (${terminal.tasksCompleted} total)`);
278
- }
279
- // Auto-assign next task if enabled
280
- if (this.config.autoAssign) {
281
- // Clear any existing assign timer
282
- const existingTimer = this.assignTimers.get(terminal.id);
283
- if (existingTimer)
284
- clearTimeout(existingTimer);
285
- const timer = setTimeout(() => {
286
- this.assignTimers.delete(terminal.id);
287
- this.tryAssignTask(terminal);
288
- }, this.config.assignDelayMs);
289
- this.assignTimers.set(terminal.id, timer);
290
- }
291
- }
292
- /**
293
- * Try to assign the next queued task to a terminal.
294
- */
295
- async tryAssignTask(terminal) {
296
- // Only assign if still idle
297
- if (terminal.status !== "idle")
298
- return false;
299
- const task = this.queue.next(terminal.id);
300
- if (!task) {
301
- this.log(`No queued tasks for terminal ${terminal.id}`);
302
- return false;
303
- }
304
- this.queue.assign(task.id, terminal.id);
305
- terminal.lastTask = task.prompt;
306
- this.log(`Assigning task "${task.prompt.slice(0, 50)}" to terminal ${terminal.id}`);
307
- if (this.onTaskAssigned) {
308
- this.onTaskAssigned(terminal.id, task);
309
- }
310
- // Type the task into the terminal
311
- try {
312
- await this.typeIntoTerminal(terminal, task.prompt);
313
- this.queue.markRunning(task.id);
314
- terminal.status = "running";
315
- return true;
316
- }
317
- catch (err) {
318
- this.log(`Failed to type task: ${err instanceof Error ? err.message : String(err)}`);
319
- this.queue.fail(task.id, String(err));
320
- return false;
321
- }
322
- }
323
- /**
324
- * Type a command into the terminal by focusing VS Code and using keyboard input.
325
- */
326
- async typeIntoTerminal(terminal, text) {
327
- // 1. Focus VS Code
328
- await this.bridge.call("app.focus", { bundleId: "com.microsoft.VSCode" });
329
- await sleep(300);
330
- // 2. If we know the terminal label, try to focus that specific terminal pane
331
- // Use accessibility to find and click the terminal
332
- try {
333
- await this.bridge.call("ax.findElement", {
334
- pid: terminal.vscodePid,
335
- title: terminal.terminalLabel,
336
- exact: false,
337
- });
338
- }
339
- catch {
340
- // Terminal pane might already be focused, continue
341
- }
342
- // 3. Type the command
343
- await this.bridge.call("cg.typeText", { text });
344
- await sleep(100);
345
- // 4. Press Enter to execute
346
- await this.bridge.call("cg.keyCombo", { keys: ["enter"] });
347
- }
348
- /**
349
- * Manually assign a task to a terminal (bypasses queue).
350
- */
351
- async assignDirect(terminalId, prompt) {
352
- const terminal = this.terminals.get(terminalId);
353
- if (!terminal)
354
- return false;
355
- terminal.lastTask = prompt;
356
- try {
357
- await this.typeIntoTerminal(terminal, prompt);
358
- terminal.status = "running";
359
- return true;
360
- }
361
- catch {
362
- return false;
363
- }
364
- }
365
- /** Update monitor config */
366
- updateConfig(config) {
367
- this.config = { ...this.config, ...config };
368
- // Restart polling if interval changed
369
- if (config.pollIntervalMs && this.running) {
370
- this.stop();
371
- this.start();
372
- }
373
- }
374
- }
375
- function sleep(ms) {
376
- return new Promise((resolve) => setTimeout(resolve, ms));
377
- }
@@ -1,84 +0,0 @@
1
- /**
2
- * Task Queue — manages tasks to be assigned to Codex terminals
3
- */
4
- export class TaskQueue {
5
- tasks = [];
6
- /** Add a task to the queue */
7
- enqueue(prompt, options = {}) {
8
- const task = {
9
- id: "task_" + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
10
- prompt,
11
- priority: options.priority ?? 10,
12
- terminalId: options.terminalId ?? null,
13
- status: "queued",
14
- createdAt: new Date().toISOString(),
15
- assignedAt: null,
16
- completedAt: null,
17
- result: null,
18
- };
19
- this.tasks.push(task);
20
- this.tasks.sort((a, b) => a.priority - b.priority);
21
- return task;
22
- }
23
- /** Get the next queued task for a specific terminal (or any terminal) */
24
- next(terminalId) {
25
- // First try tasks assigned to this specific terminal
26
- const specific = this.tasks.find((t) => t.status === "queued" && t.terminalId === terminalId);
27
- if (specific)
28
- return specific;
29
- // Then try unassigned tasks
30
- const any = this.tasks.find((t) => t.status === "queued" && t.terminalId === null);
31
- return any ?? null;
32
- }
33
- /** Mark a task as assigned */
34
- assign(taskId, terminalId) {
35
- const task = this.tasks.find((t) => t.id === taskId);
36
- if (task) {
37
- task.status = "assigned";
38
- task.terminalId = terminalId;
39
- task.assignedAt = new Date().toISOString();
40
- }
41
- }
42
- /** Mark a task as running */
43
- markRunning(taskId) {
44
- const task = this.tasks.find((t) => t.id === taskId);
45
- if (task)
46
- task.status = "running";
47
- }
48
- /** Mark a task as completed */
49
- complete(taskId, result) {
50
- const task = this.tasks.find((t) => t.id === taskId);
51
- if (task) {
52
- task.status = "completed";
53
- task.completedAt = new Date().toISOString();
54
- task.result = result;
55
- }
56
- }
57
- /** Mark a task as failed */
58
- fail(taskId, result) {
59
- const task = this.tasks.find((t) => t.id === taskId);
60
- if (task) {
61
- task.status = "failed";
62
- task.completedAt = new Date().toISOString();
63
- task.result = result;
64
- }
65
- }
66
- /** Get all tasks */
67
- all() {
68
- return [...this.tasks];
69
- }
70
- /** Get queued tasks count */
71
- queuedCount() {
72
- return this.tasks.filter((t) => t.status === "queued").length;
73
- }
74
- /** Remove completed/failed tasks older than given ms */
75
- cleanup(olderThanMs = 3600000) {
76
- const cutoff = Date.now() - olderThanMs;
77
- const before = this.tasks.length;
78
- this.tasks = this.tasks.filter((t) => t.status === "queued" ||
79
- t.status === "assigned" ||
80
- t.status === "running" ||
81
- (t.completedAt && new Date(t.completedAt).getTime() > cutoff));
82
- return before - this.tasks.length;
83
- }
84
- }
@@ -1,49 +0,0 @@
1
- /**
2
- * Codex Monitor types — track AI coding agents in VS Code terminals
3
- */
4
- export const DEFAULT_MONITOR_CONFIG = {
5
- pollIntervalMs: 3000,
6
- runningPatterns: [
7
- "Thinking",
8
- "Working",
9
- "Generating",
10
- "Analyzing",
11
- "Reading",
12
- "Writing",
13
- "Searching",
14
- "Running",
15
- "Executing",
16
- "...",
17
- "spinning",
18
- "in progress",
19
- ],
20
- idlePatterns: [
21
- "codex>",
22
- "Codex>",
23
- "> ",
24
- "$ ",
25
- "Done",
26
- "Complete",
27
- "Finished",
28
- "Task completed",
29
- "All done",
30
- "ready",
31
- "waiting for input",
32
- "What would you like",
33
- "How can I help",
34
- ],
35
- errorPatterns: [
36
- "Error:",
37
- "error:",
38
- "FAILED",
39
- "failed",
40
- "Exception",
41
- "Traceback",
42
- "panic:",
43
- "FATAL",
44
- "Cannot",
45
- "could not",
46
- ],
47
- autoAssign: true,
48
- assignDelayMs: 2000,
49
- };
@@ -1,174 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { EventEmitter } from "node:events";
3
- import path from "node:path";
4
- import { createInterface } from "node:readline";
5
- /**
6
- * Per-method timeout overrides (ms).
7
- * Methods not listed here use the default 10s timeout.
8
- */
9
- const METHOD_TIMEOUTS = {
10
- "app.launch": 30_000,
11
- "cg.captureScreen": 15_000,
12
- "cg.captureWindow": 15_000,
13
- "vision.ocr": 20_000,
14
- "vision.findText": 20_000,
15
- };
16
- /**
17
- * Resolves the correct native bridge binary path for the current platform.
18
- */
19
- function defaultBinaryPath() {
20
- // import.meta.dirname is Node 20+; for Node 18 derive from import.meta.url
21
- const base = import.meta.dirname ?? path.dirname(new URL(import.meta.url).pathname);
22
- if (process.platform === "win32") {
23
- return path.resolve(base, "../../native/windows-bridge/bin/Release/net8.0-windows/windows-bridge.exe");
24
- }
25
- // macOS (default)
26
- return path.resolve(base, "../../native/macos-bridge/.build/release/macos-bridge");
27
- }
28
- /**
29
- * Platform-aware native bridge client.
30
- * Spawns the correct bridge binary (macOS Swift or Windows C#) based on the OS,
31
- * communicating via the same JSON-RPC-over-stdio protocol.
32
- *
33
- * Drop-in replacement for the original MacOSBridgeClient.
34
- */
35
- export class BridgeClient extends EventEmitter {
36
- process = null;
37
- nextId = 1;
38
- pending = new Map();
39
- binaryPath;
40
- restarting = false;
41
- started = false;
42
- constructor(binaryPath) {
43
- super();
44
- this.binaryPath = binaryPath ?? defaultBinaryPath();
45
- }
46
- async start() {
47
- if (this.started)
48
- return;
49
- await this.spawn();
50
- this.started = true;
51
- }
52
- async stop() {
53
- this.started = false;
54
- if (this.process) {
55
- this.process.kill();
56
- this.process = null;
57
- }
58
- // Reject all pending
59
- for (const [id, pending] of this.pending) {
60
- clearTimeout(pending.timer);
61
- pending.reject(new Error("Bridge stopped"));
62
- this.pending.delete(id);
63
- }
64
- }
65
- async call(method, params, timeoutMs) {
66
- const effectiveTimeout = timeoutMs ?? METHOD_TIMEOUTS[method] ?? 10_000;
67
- if (!this.process || this.process.exitCode !== null) {
68
- await this.restart();
69
- }
70
- const id = this.nextId++;
71
- const request = { id, method };
72
- if (params) {
73
- request.params = params;
74
- }
75
- return new Promise((resolve, reject) => {
76
- const timer = setTimeout(() => {
77
- this.pending.delete(id);
78
- reject(new Error(`Bridge call "${method}" timed out after ${effectiveTimeout}ms`));
79
- }, effectiveTimeout);
80
- this.pending.set(id, {
81
- resolve: resolve,
82
- reject,
83
- timer,
84
- });
85
- const line = JSON.stringify(request) + "\n";
86
- this.process.stdin.write(line);
87
- });
88
- }
89
- async ping() {
90
- return this.call("ping");
91
- }
92
- async checkPermissions() {
93
- return this.call("check_permissions");
94
- }
95
- async spawn() {
96
- const child = spawn(this.binaryPath, [], {
97
- stdio: ["pipe", "pipe", "pipe"],
98
- });
99
- child.on("error", (err) => {
100
- this.emit("error", err);
101
- if (this.started) {
102
- this.restart().catch(() => { });
103
- }
104
- });
105
- child.on("exit", (code) => {
106
- this.emit("exit", code);
107
- if (this.started && !this.restarting) {
108
- this.restart().catch(() => { });
109
- }
110
- });
111
- // Parse stdout line by line
112
- const rl = createInterface({ input: child.stdout });
113
- rl.on("line", (line) => {
114
- this.handleLine(line);
115
- });
116
- // Log stderr
117
- child.stderr?.on("data", (data) => {
118
- this.emit("stderr", data.toString());
119
- });
120
- this.process = child;
121
- }
122
- handleLine(line) {
123
- let response;
124
- try {
125
- response = JSON.parse(line);
126
- }
127
- catch {
128
- return; // Ignore malformed lines
129
- }
130
- // Event (streaming notification from observer)
131
- if (response.event) {
132
- this.emit("ax-event", response.event);
133
- return;
134
- }
135
- // Response to a pending request
136
- const pending = this.pending.get(response.id);
137
- if (!pending)
138
- return;
139
- this.pending.delete(response.id);
140
- clearTimeout(pending.timer);
141
- if (response.error) {
142
- pending.reject(new Error(response.error.message));
143
- }
144
- else {
145
- pending.resolve(response.result);
146
- }
147
- }
148
- async restart() {
149
- if (this.restarting)
150
- return;
151
- this.restarting = true;
152
- // Reject all pending requests
153
- for (const [id, pending] of this.pending) {
154
- clearTimeout(pending.timer);
155
- pending.reject(new Error("Bridge process crashed, restarting"));
156
- this.pending.delete(id);
157
- }
158
- try {
159
- if (this.process) {
160
- this.process.kill();
161
- this.process = null;
162
- }
163
- await this.spawn();
164
- this.emit("restart");
165
- }
166
- finally {
167
- this.restarting = false;
168
- }
169
- }
170
- }
171
- /**
172
- * @deprecated Use BridgeClient instead. This alias exists for backward compatibility.
173
- */
174
- export const MacOSBridgeClient = BridgeClient;
@@ -1,5 +0,0 @@
1
- /**
2
- * @deprecated Import from "./bridge-client.js" instead.
3
- * This file re-exports for backward compatibility.
4
- */
5
- export { BridgeClient, BridgeClient as MacOSBridgeClient } from "./bridge-client.js";