@agentdeck/bridge 1.0.2 → 1.0.4

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 (175) hide show
  1. package/assets/fm-helper/agentdeck-fm-helper +0 -0
  2. package/dist/apme/collector.d.ts.map +1 -1
  3. package/dist/apme/collector.js +5 -2
  4. package/dist/apme/collector.js.map +1 -1
  5. package/dist/apme/runner.d.ts +11 -0
  6. package/dist/apme/runner.d.ts.map +1 -1
  7. package/dist/apme/runner.js +60 -4
  8. package/dist/apme/runner.js.map +1 -1
  9. package/dist/apme/settings.d.ts +10 -0
  10. package/dist/apme/settings.d.ts.map +1 -1
  11. package/dist/apme/settings.js +14 -0
  12. package/dist/apme/settings.js.map +1 -1
  13. package/dist/awaiting-overlay.d.ts +44 -10
  14. package/dist/awaiting-overlay.d.ts.map +1 -1
  15. package/dist/awaiting-overlay.js +96 -28
  16. package/dist/awaiting-overlay.js.map +1 -1
  17. package/dist/bridge-core.d.ts +10 -0
  18. package/dist/bridge-core.d.ts.map +1 -1
  19. package/dist/bridge-core.js +47 -12
  20. package/dist/bridge-core.js.map +1 -1
  21. package/dist/card-feed.d.ts +107 -0
  22. package/dist/card-feed.d.ts.map +1 -0
  23. package/dist/card-feed.js +277 -0
  24. package/dist/card-feed.js.map +1 -0
  25. package/dist/card-modules.d.ts +87 -0
  26. package/dist/card-modules.d.ts.map +1 -0
  27. package/dist/card-modules.js +179 -0
  28. package/dist/card-modules.js.map +1 -0
  29. package/dist/cli.d.ts.map +1 -1
  30. package/dist/cli.js +232 -69
  31. package/dist/cli.js.map +1 -1
  32. package/dist/daemon-server.d.ts.map +1 -1
  33. package/dist/daemon-server.js +1008 -47
  34. package/dist/daemon-server.js.map +1 -1
  35. package/dist/daemon-ws-client.d.ts +2 -1
  36. package/dist/daemon-ws-client.d.ts.map +1 -1
  37. package/dist/daemon-ws-client.js +2 -1
  38. package/dist/daemon-ws-client.js.map +1 -1
  39. package/dist/daemon.js +1 -1
  40. package/dist/device-photo.d.ts +86 -0
  41. package/dist/device-photo.d.ts.map +1 -0
  42. package/dist/device-photo.js +200 -0
  43. package/dist/device-photo.js.map +1 -0
  44. package/dist/device-voice-reply.d.ts +124 -0
  45. package/dist/device-voice-reply.d.ts.map +1 -0
  46. package/dist/device-voice-reply.js +236 -0
  47. package/dist/device-voice-reply.js.map +1 -0
  48. package/dist/device-voice.d.ts +56 -0
  49. package/dist/device-voice.d.ts.map +1 -0
  50. package/dist/device-voice.js +134 -0
  51. package/dist/device-voice.js.map +1 -0
  52. package/dist/esp32-serial.d.ts +40 -0
  53. package/dist/esp32-serial.d.ts.map +1 -1
  54. package/dist/esp32-serial.js +109 -13
  55. package/dist/esp32-serial.js.map +1 -1
  56. package/dist/foundation-models-helper.d.ts +30 -0
  57. package/dist/foundation-models-helper.d.ts.map +1 -1
  58. package/dist/foundation-models-helper.js +45 -0
  59. package/dist/foundation-models-helper.js.map +1 -1
  60. package/dist/idotmatrix/idotmatrix-daemon-sync.d.ts.map +1 -1
  61. package/dist/idotmatrix/idotmatrix-daemon-sync.js +5 -18
  62. package/dist/idotmatrix/idotmatrix-daemon-sync.js.map +1 -1
  63. package/dist/idotmatrix/idotmatrix-discover.d.ts.map +1 -1
  64. package/dist/idotmatrix/idotmatrix-discover.js +6 -14
  65. package/dist/idotmatrix/idotmatrix-discover.js.map +1 -1
  66. package/dist/index.js +2 -2
  67. package/dist/index.js.map +1 -1
  68. package/dist/mdns.d.ts +11 -0
  69. package/dist/mdns.d.ts.map +1 -1
  70. package/dist/mdns.js +13 -0
  71. package/dist/mdns.js.map +1 -1
  72. package/dist/observed-inject.d.ts +119 -0
  73. package/dist/observed-inject.d.ts.map +1 -0
  74. package/dist/observed-inject.js +405 -0
  75. package/dist/observed-inject.js.map +1 -0
  76. package/dist/passive-observer.d.ts +14 -0
  77. package/dist/passive-observer.d.ts.map +1 -1
  78. package/dist/passive-observer.js +35 -5
  79. package/dist/passive-observer.js.map +1 -1
  80. package/dist/peripheral-mapping.d.ts +65 -0
  81. package/dist/peripheral-mapping.d.ts.map +1 -0
  82. package/dist/peripheral-mapping.js +115 -0
  83. package/dist/peripheral-mapping.js.map +1 -0
  84. package/dist/python-ble-runtime.d.ts +62 -0
  85. package/dist/python-ble-runtime.d.ts.map +1 -0
  86. package/dist/python-ble-runtime.js +215 -0
  87. package/dist/python-ble-runtime.js.map +1 -0
  88. package/dist/session-aggregator.d.ts +4 -1
  89. package/dist/session-aggregator.d.ts.map +1 -1
  90. package/dist/session-aggregator.js +5 -5
  91. package/dist/session-aggregator.js.map +1 -1
  92. package/dist/session-id-resolve.d.ts +17 -0
  93. package/dist/session-id-resolve.d.ts.map +1 -0
  94. package/dist/session-id-resolve.js +35 -0
  95. package/dist/session-id-resolve.js.map +1 -0
  96. package/dist/session-transcript-timeline.d.ts +7 -0
  97. package/dist/session-transcript-timeline.d.ts.map +1 -1
  98. package/dist/session-transcript-timeline.js +16 -6
  99. package/dist/session-transcript-timeline.js.map +1 -1
  100. package/dist/timebox/timebox-daemon-sync.d.ts.map +1 -1
  101. package/dist/timebox/timebox-daemon-sync.js +5 -20
  102. package/dist/timebox/timebox-daemon-sync.js.map +1 -1
  103. package/dist/timebox/timebox-discover.d.ts.map +1 -1
  104. package/dist/timebox/timebox-discover.js +6 -15
  105. package/dist/timebox/timebox-discover.js.map +1 -1
  106. package/dist/timeline-store.js +2 -2
  107. package/dist/timeline-store.js.map +1 -1
  108. package/dist/usage-event.d.ts.map +1 -1
  109. package/dist/usage-event.js +14 -7
  110. package/dist/usage-event.js.map +1 -1
  111. package/dist/voice.d.ts +13 -10
  112. package/dist/voice.d.ts.map +1 -1
  113. package/dist/voice.js +26 -272
  114. package/dist/voice.js.map +1 -1
  115. package/dist/ws-server.d.ts +14 -0
  116. package/dist/ws-server.d.ts.map +1 -1
  117. package/dist/ws-server.js +66 -1
  118. package/dist/ws-server.js.map +1 -1
  119. package/fm-helper/AgentDeckFMHelper.swift +303 -0
  120. package/package.json +8 -4
  121. package/python/requirements-ble.txt +5 -0
  122. package/src/idotmatrix/brightness.py +40 -0
  123. package/src/idotmatrix/scan.py +35 -0
  124. package/src/idotmatrix/sync.py +400 -0
  125. package/src/pysync/matrix_sync_common.py +89 -0
  126. package/src/timebox/scan_ble.py +52 -0
  127. package/src/timebox/sync_ble.py +394 -0
  128. package/dist/d200h/hid-protocol.d.ts +0 -62
  129. package/dist/d200h/hid-protocol.d.ts.map +0 -1
  130. package/dist/d200h/hid-protocol.js +0 -124
  131. package/dist/d200h/hid-protocol.js.map +0 -1
  132. package/dist/d200h/image-renderer.d.ts +0 -24
  133. package/dist/d200h/image-renderer.d.ts.map +0 -1
  134. package/dist/d200h/image-renderer.js +0 -321
  135. package/dist/d200h/image-renderer.js.map +0 -1
  136. package/dist/hook-migration.d.ts +0 -2
  137. package/dist/hook-migration.d.ts.map +0 -1
  138. package/dist/hook-migration.js +0 -104
  139. package/dist/hook-migration.js.map +0 -1
  140. package/dist/modules/d200h-module.d.ts +0 -65
  141. package/dist/modules/d200h-module.d.ts.map +0 -1
  142. package/dist/modules/d200h-module.js +0 -495
  143. package/dist/modules/d200h-module.js.map +0 -1
  144. package/dist/modules/d200h-renderer.d.ts +0 -7
  145. package/dist/modules/d200h-renderer.d.ts.map +0 -1
  146. package/dist/modules/d200h-renderer.js +0 -321
  147. package/dist/modules/d200h-renderer.js.map +0 -1
  148. package/dist/modules/trmnl-module.d.ts +0 -38
  149. package/dist/modules/trmnl-module.d.ts.map +0 -1
  150. package/dist/modules/trmnl-module.js +0 -111
  151. package/dist/modules/trmnl-module.js.map +0 -1
  152. package/dist/trmnl/byos-server.d.ts +0 -39
  153. package/dist/trmnl/byos-server.d.ts.map +0 -1
  154. package/dist/trmnl/byos-server.js +0 -301
  155. package/dist/trmnl/byos-server.js.map +0 -1
  156. package/dist/trmnl/frame-cache.d.ts +0 -37
  157. package/dist/trmnl/frame-cache.d.ts.map +0 -1
  158. package/dist/trmnl/frame-cache.js +0 -157
  159. package/dist/trmnl/frame-cache.js.map +0 -1
  160. package/dist/trmnl/image-renderer.d.ts +0 -27
  161. package/dist/trmnl/image-renderer.d.ts.map +0 -1
  162. package/dist/trmnl/image-renderer.js +0 -235
  163. package/dist/trmnl/image-renderer.js.map +0 -1
  164. package/dist/trmnl/trmnl-settings.d.ts +0 -96
  165. package/dist/trmnl/trmnl-settings.d.ts.map +0 -1
  166. package/dist/trmnl/trmnl-settings.js +0 -177
  167. package/dist/trmnl/trmnl-settings.js.map +0 -1
  168. package/dist/trmnl/trmnl-telemetry.d.ts +0 -47
  169. package/dist/trmnl/trmnl-telemetry.d.ts.map +0 -1
  170. package/dist/trmnl/trmnl-telemetry.js +0 -56
  171. package/dist/trmnl/trmnl-telemetry.js.map +0 -1
  172. package/dist/whisper-server-manager.d.ts +0 -19
  173. package/dist/whisper-server-manager.d.ts.map +0 -1
  174. package/dist/whisper-server-manager.js +0 -171
  175. package/dist/whisper-server-manager.js.map +0 -1
@@ -0,0 +1,405 @@
1
+ /**
2
+ * Deliver an option selection INTO an observed session's own UI — the rung of
3
+ * the observed-steering ladder that answers a live prompt without any
4
+ * hold-and-timeout machinery: the prompt is already on screen; the daemon does
5
+ * what the user would do, and if it cannot reach the UI **nothing happens**
6
+ * (the prompt keeps waiting — never an auto-proceed).
7
+ *
8
+ * Two families of host, resolved by the passive observer:
9
+ *
10
+ * Terminal-hosted (`tty` known) — the session runs in a real terminal, so
11
+ * the answer is keystrokes into that terminal's own input:
12
+ * 1. tmux — the pane whose `#{pane_tty}` matches gets `send-keys`.
13
+ * 2. iTerm2 — the session whose `tty` matches gets `write text` (no focus
14
+ * change; iTerm2 writes straight into the session).
15
+ * 3. Terminal.app — has no write API, so: select the tab owning that tty
16
+ * (scriptable, no activation) and post keys to Terminal's pid.
17
+ *
18
+ * App-hosted (`appName` known, no tty) — Claude.app / ChatGPT.app run the
19
+ * agent inside a native window, so the answer is that window's own control:
20
+ * 4. Press the button whose title matches the chosen option's label
21
+ * (AXPress — precise; works for native AppKit dialogs).
22
+ * 5. Post keys to the app's pid (focus-free).
23
+ * 6. Raise + keys + restore, only if the app ignores posted events.
24
+ *
25
+ * Every rung is focus-free except the last: measured 2026-07-26, keys posted
26
+ * with `CGEventPostToPid` reached a background Terminal tab while iTerm2
27
+ * stayed frontmost.
28
+ *
29
+ * Two measured dead ends, recorded so they are not retried:
30
+ * • TIOCSTI — the one kernel path that would cover every emulator at once —
31
+ * is EPERM on macOS unless the tty is the caller's own controlling
32
+ * terminal.
33
+ * • ChatGPT.app exposes no content in its accessibility tree (its window is
34
+ * three untitled AXButtons plus empty AXGroups, and
35
+ * `AXManualAccessibility` returns kAXErrorAttributeUnsupported), so the
36
+ * button rung cannot see its controls — key posting is the working path
37
+ * for that host.
38
+ *
39
+ * Node-daemon only by design: every rung needs a subprocess (tmux/osascript),
40
+ * which the App Store Swift daemon must never spawn. See
41
+ * docs/appstore-feature-matrix.md.
42
+ */
43
+ import { execFile } from 'child_process';
44
+ import { promisify } from 'util';
45
+ import { debug } from './logger.js';
46
+ const execFileAsync = promisify(execFile);
47
+ /** Known bundle ids for GUI agent hosts — matched before localized names,
48
+ * which differ per system language. */
49
+ const APP_BUNDLE_IDS = {
50
+ Claude: ['com.anthropic.claudefordesktop', 'com.anthropic.claude'],
51
+ ChatGPT: ['com.openai.chat'],
52
+ Terminal: ['com.apple.Terminal'],
53
+ iTerm2: ['com.googlecode.iterm2'],
54
+ };
55
+ /** AppleScript string literal escaping (quotes + backslashes). */
56
+ export function escapeAppleScript(s) {
57
+ return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
58
+ }
59
+ /** Parse `tmux list-panes -a -F "#{pane_tty}\t#{pane_id}"` output into a
60
+ * tty-suffix → paneId map ("/dev/ttys012" is matched by "ttys012"). */
61
+ export function parseTmuxPanes(output) {
62
+ const map = new Map();
63
+ for (const line of output.split('\n')) {
64
+ const [paneTty, paneId] = line.trim().split('\t');
65
+ if (!paneTty || !paneId)
66
+ continue;
67
+ map.set(paneTty.replace(/^\/dev\//, ''), paneId);
68
+ }
69
+ return map;
70
+ }
71
+ async function injectViaTmux(tty, downs) {
72
+ try {
73
+ const { stdout } = await execFileAsync('tmux', ['list-panes', '-a', '-F', '#{pane_tty}\t#{pane_id}'], { encoding: 'utf8', timeout: 2_000 });
74
+ const paneId = parseTmuxPanes(stdout).get(tty);
75
+ if (!paneId)
76
+ return false;
77
+ const keys = [...Array(downs).fill('Down'), 'Enter'];
78
+ await execFileAsync('tmux', ['send-keys', '-t', paneId, ...keys], { timeout: 2_000 });
79
+ return true;
80
+ }
81
+ catch {
82
+ return false; // no tmux server / pane gone — fall through the ladder
83
+ }
84
+ }
85
+ /** AppleScript that types the selection into the iTerm2 session owning `tty`. */
86
+ export function buildItermSelectScript(tty, downs) {
87
+ // ESC [ B per Down, then a bare `write text ""` for Enter (write text
88
+ // appends CR by default; the arrow writes suppress it with `newline NO`).
89
+ const writes = [];
90
+ for (let i = 0; i < downs; i++) {
91
+ writes.push('write s text ((character id 27) & "[B") newline NO');
92
+ }
93
+ writes.push('write s text ""');
94
+ return [
95
+ 'tell application "iTerm2"',
96
+ ' repeat with w in windows',
97
+ ' repeat with t in tabs of w',
98
+ ' repeat with s in sessions of t',
99
+ ` if tty of s is "/dev/${tty}" then`,
100
+ ...writes.map((l) => ` ${l}`),
101
+ ' return "ok"',
102
+ ' end if',
103
+ ' end repeat',
104
+ ' end repeat',
105
+ ' end repeat',
106
+ 'end tell',
107
+ 'return "notfound"',
108
+ ].join('\n');
109
+ }
110
+ /**
111
+ * AppleScript for Terminal.app: select the tab whose `tty` matches, WITHOUT
112
+ * activating the app. Keys are then posted straight to Terminal's pid
113
+ * (`buildPostKeysScript`), so answering a prompt never steals the user's
114
+ * focus — measured 2026-07-26: iTerm2 stayed frontmost while the Terminal
115
+ * tab received ESC[B + CR.
116
+ */
117
+ export function buildTerminalAppSelectTabScript(tty) {
118
+ return [
119
+ 'set found to false',
120
+ 'tell application "Terminal"',
121
+ ' repeat with w in windows',
122
+ ' repeat with t in tabs of w',
123
+ ` if tty of t is "/dev/${tty}" then`,
124
+ ' set selected of t to true',
125
+ ' set index of w to 1',
126
+ ' set found to true',
127
+ ' exit repeat',
128
+ ' end if',
129
+ ' end repeat',
130
+ ' if found then exit repeat',
131
+ ' end repeat',
132
+ 'end tell',
133
+ 'if found then',
134
+ ' return "ok"',
135
+ 'else',
136
+ ' return "notfound"',
137
+ 'end if',
138
+ ].join('\n');
139
+ }
140
+ /**
141
+ * JXA that posts Down×n + Return directly to a running app's process with
142
+ * `CGEventPostToPid` — the event reaches that app's key window without
143
+ * raising it, so the user's foreground work is untouched.
144
+ *
145
+ * The app is matched by bundle id first and localized name second: on a
146
+ * Korean system `Terminal` is "터미널", so name matching alone silently
147
+ * missed it.
148
+ *
149
+ * Requires Accessibility permission for whatever process runs the daemon
150
+ * (the terminal that launched it, or the LaunchAgent itself).
151
+ */
152
+ export function buildPostKeysScript(match, downs) {
153
+ const bundles = JSON.stringify(match.bundleIds ?? []);
154
+ const names = JSON.stringify(match.names ?? []);
155
+ return [
156
+ "ObjC.import('ApplicationServices'); ObjC.import('AppKit');",
157
+ `const wantBundles = ${bundles}; const wantNames = ${names};`,
158
+ 'function findPid() {',
159
+ ' const apps = $.NSWorkspace.sharedWorkspace.runningApplications;',
160
+ ' for (let i = 0; i < apps.count; i++) {',
161
+ ' const a = apps.objectAtIndex(i);',
162
+ ' const b = ObjC.unwrap(a.bundleIdentifier);',
163
+ ' if (b && wantBundles.indexOf(b) >= 0) return a.processIdentifier;',
164
+ ' }',
165
+ ' for (let i = 0; i < apps.count; i++) {',
166
+ ' const a = apps.objectAtIndex(i);',
167
+ ' const n = ObjC.unwrap(a.localizedName);',
168
+ ' if (n && wantNames.indexOf(n) >= 0) return a.processIdentifier;',
169
+ ' }',
170
+ ' return -1;',
171
+ '}',
172
+ 'const pid = findPid();',
173
+ 'if (pid < 0) { "notfound" } else {',
174
+ ' function key(code) {',
175
+ ' const d = $.CGEventCreateKeyboardEvent($(), code, true);',
176
+ ' const u = $.CGEventCreateKeyboardEvent($(), code, false);',
177
+ ' $.CGEventPostToPid(pid, d); $.CGEventPostToPid(pid, u);',
178
+ ' delay(0.12);',
179
+ ' }',
180
+ // A background app drops the first posted events until its input queue is
181
+ // warm: measured with 0.05s pacing and no lead-in, two Downs vanished and
182
+ // only the Return landed (which would silently pick the WRONG option).
183
+ ' delay(0.30);',
184
+ ` for (let i = 0; i < ${downs}; i++) key(125);`,
185
+ ' key(36);',
186
+ ' "ok"',
187
+ '}',
188
+ ].join('\n');
189
+ }
190
+ /**
191
+ * AppleScript for an app-hosted session: press the button whose title matches
192
+ * the option label inside the app's front window. Matching is
193
+ * case-insensitive and prefix-tolerant (native buttons often shorten a long
194
+ * option label). AXPress needs no focus change, so this is the preferred
195
+ * app-hosted rung.
196
+ */
197
+ export function buildAppButtonPressScript(appName, label) {
198
+ const app = escapeAppleScript(appName);
199
+ // AppleScript text comparison ignores case by default — no lowercasing dance.
200
+ const want = escapeAppleScript(label.trim());
201
+ // `entire contents` yields a flat element list; AppleScript cannot filter it
202
+ // with `every button of …`, so iterate and test the accessibility role.
203
+ return [
204
+ `tell application "System Events" to tell process "${app}"`,
205
+ ' if not (exists window 1) then return "nowindow"',
206
+ ' set target to missing value',
207
+ ' repeat with e in (entire contents of window 1)',
208
+ ' try',
209
+ ' if role of e is "AXButton" or role of e is "AXRadioButton" then',
210
+ ' set bt to name of e',
211
+ ' if bt is not missing value and bt is not "" then',
212
+ ` if bt is "${want}" or bt starts with "${want}" or "${want}" starts with bt then`,
213
+ ' set target to e',
214
+ ' exit repeat',
215
+ ' end if',
216
+ ' end if',
217
+ ' end if',
218
+ ' end try',
219
+ ' end repeat',
220
+ ' if target is missing value then return "nobutton"',
221
+ ' click target',
222
+ ' return "ok"',
223
+ 'end tell',
224
+ ].join('\n');
225
+ }
226
+ /**
227
+ * Last-resort key path: raise the app, post Down×n + Return through System
228
+ * Events, restore the previous frontmost app. Only used when the focus-free
229
+ * `CGEventPostToPid` route reports the app is not running or the app ignores
230
+ * posted events.
231
+ */
232
+ export function buildAppKeysScript(appName, downs) {
233
+ const app = escapeAppleScript(appName);
234
+ const keyLines = [];
235
+ for (let i = 0; i < downs; i++)
236
+ keyLines.push('key code 125');
237
+ keyLines.push('key code 36');
238
+ return [
239
+ 'set prevApp to ""',
240
+ 'try',
241
+ ' tell application "System Events" to set prevApp to name of first process whose frontmost is true',
242
+ 'end try',
243
+ `tell application "${app}" to activate`,
244
+ 'delay 0.15',
245
+ 'tell application "System Events"',
246
+ ...keyLines.map((l) => ` ${l}`),
247
+ 'end tell',
248
+ 'delay 0.05',
249
+ `if prevApp is not "" and prevApp is not "${app}" then`,
250
+ ' try',
251
+ ' tell application prevApp to activate',
252
+ ' end try',
253
+ 'end if',
254
+ 'return "ok"',
255
+ ].join('\n');
256
+ }
257
+ async function runJxa(script, timeoutMs = 8_000) {
258
+ try {
259
+ const { stdout } = await execFileAsync('osascript', ['-l', 'JavaScript', '-e', script], { encoding: 'utf8', timeout: timeoutMs });
260
+ return stdout.trim();
261
+ }
262
+ catch (err) {
263
+ debug('inject', `jxa failed: ${String(err).slice(0, 200)}`);
264
+ return null;
265
+ }
266
+ }
267
+ async function runOsa(script, timeoutMs = 8_000) {
268
+ try {
269
+ const { stdout } = await execFileAsync('osascript', ['-e', script], { encoding: 'utf8', timeout: timeoutMs });
270
+ return stdout.trim();
271
+ }
272
+ catch (err) {
273
+ debug('inject', `osascript failed: ${String(err).slice(0, 200)}`);
274
+ return null;
275
+ }
276
+ }
277
+ /**
278
+ * Answer the session's on-screen prompt by driving its host UI.
279
+ * `index` is the zero-based option position (Claude's pickers start with the
280
+ * cursor on the first option, so index == the number of Down presses).
281
+ */
282
+ export async function injectObservedSelection(target, index) {
283
+ if (index < 0 || index > 16)
284
+ return { ok: false, reason: 'index out of range' };
285
+ const { tty, appName, label } = target;
286
+ if (!tty && !appName)
287
+ return { ok: false, reason: 'no tty or app host for session' };
288
+ if (tty) {
289
+ if (await injectViaTmux(tty, index))
290
+ return { ok: true, via: 'tmux' };
291
+ if (await runOsa(buildItermSelectScript(tty, index), 5_000) === 'ok') {
292
+ return { ok: true, via: 'iterm2' };
293
+ }
294
+ // Terminal.app: select the owning tab (no activation), then post keys to
295
+ // its pid — focus-free.
296
+ if (await runOsa(buildTerminalAppSelectTabScript(tty), 5_000) === 'ok') {
297
+ const posted = await runJxa(buildPostKeysScript({ bundleIds: ['com.apple.Terminal'], names: ['Terminal', '터미널'] }, index));
298
+ if (posted === 'ok')
299
+ return { ok: true, via: 'terminal-app' };
300
+ debug('inject', `Terminal.app tab selected but key post failed: ${posted ?? 'error'}`);
301
+ }
302
+ return { ok: false, reason: `no reachable terminal for ${tty}` };
303
+ }
304
+ // App-hosted. Try the labelled control first (works for native AppKit
305
+ // dialogs); then focus-free key posting; raise+restore only as last resort.
306
+ if (label) {
307
+ const r = await runOsa(buildAppButtonPressScript(appName, label));
308
+ if (r === 'ok')
309
+ return { ok: true, via: 'app-button' };
310
+ debug('inject', `app-button press on ${appName}: ${r ?? 'error'}`);
311
+ }
312
+ const posted = await runJxa(buildPostKeysScript({ bundleIds: APP_BUNDLE_IDS[appName] ?? [], names: [appName] }, index));
313
+ if (posted === 'ok')
314
+ return { ok: true, via: 'app-keys' };
315
+ if (await runOsa(buildAppKeysScript(appName, index)) === 'ok') {
316
+ return { ok: true, via: 'app-keys-raised' };
317
+ }
318
+ return { ok: false, reason: `no reachable UI in ${appName}` };
319
+ }
320
+ /**
321
+ * Type a whole line of text into the session's terminal and submit it — the
322
+ * delivery path for a dictated prompt. Same host ladder as
323
+ * `injectObservedSelection`, but writing text instead of arrow keys.
324
+ *
325
+ * Only terminal hosts are supported: typing free text into a GUI app's focused
326
+ * field is far riskier (no way to know what has focus), and a spoken prompt
327
+ * that lands in the wrong place is worse than one that is refused.
328
+ */
329
+ export async function injectObservedText(target, text) {
330
+ const trimmed = text.trim();
331
+ if (!trimmed)
332
+ return { ok: false, reason: 'empty text' };
333
+ // Newlines would submit early and split the prompt across turns.
334
+ const line = trimmed.replace(/[\r\n]+/g, ' ');
335
+ const { tty } = target;
336
+ if (!tty)
337
+ return { ok: false, reason: 'no tty for session (text injection is terminal-only)' };
338
+ // tmux: -l sends the payload literally, then a separate Enter submits it.
339
+ try {
340
+ const { stdout } = await execFileAsync('tmux', ['list-panes', '-a', '-F', '#{pane_tty}\t#{pane_id}'], { encoding: 'utf8', timeout: 2_000 });
341
+ const paneId = parseTmuxPanes(stdout).get(tty);
342
+ if (paneId) {
343
+ await execFileAsync('tmux', ['send-keys', '-t', paneId, '-l', line], { timeout: 2_000 });
344
+ // Same beat as above: process spawn alone happens to provide ~50 ms, which
345
+ // is not a guarantee worth relying on for the keystroke that commits.
346
+ await new Promise((r) => setTimeout(r, 250));
347
+ await execFileAsync('tmux', ['send-keys', '-t', paneId, 'Enter'], { timeout: 2_000 });
348
+ return { ok: true, via: 'tmux' };
349
+ }
350
+ }
351
+ catch { /* no tmux server — fall through */ }
352
+ // iTerm2: type the line, pause, then submit with an explicit carriage return.
353
+ //
354
+ // `write text "..."` alone would do both — measured in raw mode it delivers
355
+ // the text followed by 0x0d, which is the right byte (0x0a would land as a
356
+ // newline *inside* the prompt instead of submitting it; the tty's ICRNL
357
+ // translation hides that difference from any canonical-mode probe, so test
358
+ // this with `stty raw` or the reading is meaningless). It is split anyway
359
+ // because a TUI that treats one burst as a paste can absorb a trailing return
360
+ // as content, and the same missing beat cost us the option keys earlier.
361
+ const iterm = [
362
+ 'tell application "iTerm2"',
363
+ ' repeat with w in windows',
364
+ ' repeat with t in tabs of w',
365
+ ' repeat with s in sessions of t',
366
+ ` if tty of s is "/dev/${tty}" then`,
367
+ ` write s text "${escapeAppleScript(line)}" newline NO`,
368
+ ' delay 0.25',
369
+ ' write s text (ASCII character 13) newline NO',
370
+ ' return "ok"',
371
+ ' end if',
372
+ ' end repeat',
373
+ ' end repeat',
374
+ ' end repeat',
375
+ 'end tell',
376
+ 'return "notfound"',
377
+ ].join('\n');
378
+ if (await runOsa(iterm, 5_000) === 'ok')
379
+ return { ok: true, via: 'iterm2' };
380
+ // Terminal.app: `do script … in <tab>` types into that tab and returns.
381
+ const term = [
382
+ 'set done to false',
383
+ 'tell application "Terminal"',
384
+ ' repeat with w in windows',
385
+ ' repeat with t in tabs of w',
386
+ ` if tty of t is "/dev/${tty}" then`,
387
+ ` do script "${escapeAppleScript(line)}" in t`,
388
+ ' set done to true',
389
+ ' exit repeat',
390
+ ' end if',
391
+ ' end repeat',
392
+ ' if done then exit repeat',
393
+ ' end repeat',
394
+ 'end tell',
395
+ 'if done then',
396
+ ' return "ok"',
397
+ 'else',
398
+ ' return "notfound"',
399
+ 'end if',
400
+ ].join('\n');
401
+ if (await runOsa(term, 5_000) === 'ok')
402
+ return { ok: true, via: 'terminal-app' };
403
+ return { ok: false, reason: `no reachable terminal for ${tty}` };
404
+ }
405
+ //# sourceMappingURL=observed-inject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observed-inject.js","sourceRoot":"","sources":["../src/observed-inject.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,8 @@ export type ObservedState = 'idle' | 'processing';
3
3
  export interface ProcInfo {
4
4
  pid: number;
5
5
  ppid: number;
6
+ /** Controlling terminal short name ("ttys008") or "??" for none. */
7
+ tty?: string;
6
8
  rssKb: number;
7
9
  command: string;
8
10
  }
@@ -22,6 +24,11 @@ export interface TranscriptSummary {
22
24
  */
23
25
  export declare function cleanGoal(raw: string): string;
24
26
  export interface ObservedSession extends EnrichedSession {
27
+ /** Controlling terminal ("ttys008") — the observed-answer injection target. */
28
+ tty?: string;
29
+ /** Owning .app for GUI-hosted sessions ("Claude", "ChatGPT") — the app-hosted
30
+ * injection target when there is no tty. */
31
+ appName?: string;
25
32
  controlMode: 'observed';
26
33
  pid: number;
27
34
  cwd?: string;
@@ -65,6 +72,13 @@ export declare function parseCodexRollout(raw: string): TranscriptSummary & {
65
72
  effort?: string;
66
73
  hasPendingCalls?: boolean;
67
74
  };
75
+ /** Extract the enclosing macOS app name from an executable path, e.g.
76
+ * "/Applications/ChatGPT.app/Contents/Resources/codex …" → "ChatGPT". */
77
+ export declare function appNameFromCommand(command: string): string | undefined;
78
+ /** Walk up the process tree (bounded) looking for the .app bundle that hosts
79
+ * this process. GUI-hosted agent sessions (Claude.app, ChatGPT.app) have no
80
+ * tty, so the owning app is how the injector finds their UI. */
81
+ export declare function resolveHostApp(pid: number, byPid: Map<number, ProcInfo>, maxDepth?: number): string | undefined;
68
82
  export declare function isAntigravityProcessCommand(command: string): boolean;
69
83
  export declare function parseLsofRollouts(output: string): Map<number, string>;
70
84
  //# sourceMappingURL=passive-observer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAW/D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAgBD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU7C;AASD,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,WAAW,EAAE,UAAU,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;sEAIkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,YAAY,CAAS;IAE7B;;0DAEsD;IACtD,WAAW,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAEtC;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE;YAYhD,IAAI;CAenB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAa5D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAoFpE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,CA6HvK;AA0KD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAapE;AAkID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrE"}
1
+ {"version":3,"file":"passive-observer.d.ts","sourceRoot":"","sources":["../src/passive-observer.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAW/D,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAgBD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAU7C;AASD,MAAM,WAAW,eAAgB,SAAQ,eAAe;IACtD,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;iDAC6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,UAAU,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;sEAIkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAQD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,YAAY,CAAS;IAE7B;;0DAEsD;IACtD,WAAW,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAEtC;;;;;;;;OAQG;IACH,OAAO,CAAC,eAAe,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE;YAYhD,IAAI;CAenB;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAc5D;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAoFpE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CAAE,CA6HvK;AAeD;0EAC0E;AAC1E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGtE;AAED;;iEAEiE;AACjE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,EAC5B,QAAQ,SAAI,GACX,MAAM,GAAG,SAAS,CASpB;AAqKD,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAapE;AAkID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYrE"}
@@ -5,7 +5,7 @@ import { closeSync, existsSync, lstatSync, openSync, readFileSync, readlinkSync,
5
5
  import { basename, join } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
7
  import { resolveProjectNameFromCwdCached } from './utils/project-name.js';
8
- import { stripUnsafeText } from '@agentdeck/shared';
8
+ import { stripUnsafeText, rawSessionId } from '@agentdeck/shared';
9
9
  import { parseAntigravityTranscript, antigravityDefaultModel, antigravityConversationId, antigravityTranscriptPath, } from './apme/antigravity-transcript.js';
10
10
  /** Pull display text out of a Claude user message (string or text blocks). */
11
11
  function claudeUserText(message) {
@@ -92,14 +92,15 @@ export class PassiveSessionObserver {
92
92
  export function parseProcessTable(output) {
93
93
  const rows = [];
94
94
  for (const line of output.split('\n')) {
95
- const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(.+?)\s*$/);
95
+ const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/);
96
96
  if (!match)
97
97
  continue;
98
98
  rows.push({
99
99
  pid: Number(match[1]),
100
100
  ppid: Number(match[2]),
101
101
  rssKb: Number(match[3]),
102
- command: match[4] ?? '',
102
+ tty: match[4] === '??' ? undefined : match[4],
103
+ command: match[5] ?? '',
103
104
  });
104
105
  }
105
106
  return rows.filter((p) => p.pid > 0 && p.command.length > 0);
@@ -332,7 +333,7 @@ export function parseCodexRollout(raw) {
332
333
  }
333
334
  async function collectProcessInfo() {
334
335
  try {
335
- const { stdout } = await execFileAsync('ps', ['-ww', '-eo', 'pid=,ppid=,rss=,command='], {
336
+ const { stdout } = await execFileAsync('ps', ['-ww', '-eo', 'pid=,ppid=,rss=,tty=,command='], {
336
337
  encoding: 'utf8',
337
338
  timeout: 2_000,
338
339
  maxBuffer: 2 * 1024 * 1024,
@@ -343,8 +344,30 @@ async function collectProcessInfo() {
343
344
  return [];
344
345
  }
345
346
  }
347
+ /** Extract the enclosing macOS app name from an executable path, e.g.
348
+ * "/Applications/ChatGPT.app/Contents/Resources/codex …" → "ChatGPT". */
349
+ export function appNameFromCommand(command) {
350
+ const m = command.match(/\/([^/]+)\.app\//);
351
+ return m ? m[1] : undefined;
352
+ }
353
+ /** Walk up the process tree (bounded) looking for the .app bundle that hosts
354
+ * this process. GUI-hosted agent sessions (Claude.app, ChatGPT.app) have no
355
+ * tty, so the owning app is how the injector finds their UI. */
356
+ export function resolveHostApp(pid, byPid, maxDepth = 6) {
357
+ let cur = byPid.get(pid);
358
+ for (let i = 0; i < maxDepth && cur; i++) {
359
+ const name = appNameFromCommand(cur.command);
360
+ if (name)
361
+ return name;
362
+ if (!cur.ppid || cur.ppid <= 1)
363
+ break;
364
+ cur = byPid.get(cur.ppid);
365
+ }
366
+ return undefined;
367
+ }
346
368
  function collectClaudeSessions(processes) {
347
369
  const configDirs = claudeConfigDirs();
370
+ const byPid = new Map(processes.map((p) => [p.pid, p]));
348
371
  const sessions = [];
349
372
  for (const proc of processes) {
350
373
  if (!cmdHasBinary(proc.command, 'claude') || proc.command.includes('--print'))
@@ -370,6 +393,8 @@ function collectClaudeSessions(processes) {
370
393
  startedAt: new Date(sessionFile.startedAt).toISOString(),
371
394
  controlMode: 'observed',
372
395
  cwd: sessionFile.cwd,
396
+ tty: proc.tty,
397
+ appName: proc.tty ? undefined : resolveHostApp(proc.pid, byPid),
373
398
  currentTask: summary.currentTask,
374
399
  goal: summary.goal,
375
400
  contextPercent: summary.contextPercent,
@@ -380,6 +405,7 @@ function collectClaudeSessions(processes) {
380
405
  return sessions;
381
406
  }
382
407
  async function collectCodexSessions(processes) {
408
+ const byPid = new Map(processes.map((p) => [p.pid, p]));
383
409
  const codex = processes.filter((p) => cmdHasBinary(p.command, 'codex') &&
384
410
  !p.command.includes('app-server') &&
385
411
  !p.command.includes('grep'));
@@ -410,6 +436,10 @@ async function collectCodexSessions(processes) {
410
436
  pid: proc.pid,
411
437
  projectName: cwd ? projectNameFromCwd(cwd) : 'Codex',
412
438
  agentType: 'codex-cli',
439
+ // Same injection targets as observed Claude: answering or dictating into
440
+ // a Codex session means typing into the terminal that owns it.
441
+ tty: proc.tty,
442
+ appName: proc.tty ? undefined : resolveHostApp(proc.pid, byPid),
413
443
  alive: true,
414
444
  state,
415
445
  modelName: parsed.modelName,
@@ -544,7 +574,7 @@ function dedupeObservedSessions(observed, managedSessions, processes) {
544
574
  return observed.filter((session) => {
545
575
  if (managedIds.has(session.id))
546
576
  return false;
547
- const rawId = session.id.replace(/^observed:(?:claude|codex|opencode|antigravity):/, '');
577
+ const rawId = rawSessionId(session.id);
548
578
  if (managedIds.has(rawId))
549
579
  return false;
550
580
  return !managedPids.some((pid) => pid === session.pid || isDescendantOf(session.pid, pid, byPid));