safari-pilot 0.1.24 → 0.1.28

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 (57) hide show
  1. package/README.md +63 -14
  2. package/bin/Safari Pilot.app/Contents/CodeResources +0 -0
  3. package/bin/Safari Pilot.app/Contents/Info.plist +2 -2
  4. package/bin/Safari Pilot.app/Contents/MacOS/Safari Pilot +0 -0
  5. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Info.plist +2 -2
  6. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/MacOS/Safari Pilot Extension +0 -0
  7. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/background.js +220 -1
  8. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/Resources/manifest.json +1 -1
  9. package/bin/Safari Pilot.app/Contents/PlugIns/Safari Pilot Extension.appex/Contents/_CodeSignature/CodeResources +4 -4
  10. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/Info.plist +0 -0
  11. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/MainMenu.nib +0 -0
  12. package/bin/Safari Pilot.app/Contents/Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib +0 -0
  13. package/bin/Safari Pilot.app/Contents/_CodeSignature/CodeResources +7 -7
  14. package/bin/Safari Pilot.zip +0 -0
  15. package/bin/SafariPilotd +0 -0
  16. package/dist/aria.js +4 -0
  17. package/dist/aria.js.map +1 -1
  18. package/dist/config.d.ts +6 -0
  19. package/dist/config.js +9 -0
  20. package/dist/config.js.map +1 -1
  21. package/dist/engines/daemon.js +25 -4
  22. package/dist/engines/daemon.js.map +1 -1
  23. package/dist/errors.d.ts +18 -0
  24. package/dist/errors.js +27 -0
  25. package/dist/errors.js.map +1 -1
  26. package/dist/locator.d.ts +80 -0
  27. package/dist/locator.js +324 -8
  28. package/dist/locator.js.map +1 -1
  29. package/dist/security/human-approval.js +15 -0
  30. package/dist/security/human-approval.js.map +1 -1
  31. package/dist/security/selector-pack-validator.d.ts +4 -0
  32. package/dist/security/selector-pack-validator.js +32 -0
  33. package/dist/security/selector-pack-validator.js.map +1 -0
  34. package/dist/server.js +3 -0
  35. package/dist/server.js.map +1 -1
  36. package/dist/tools/extraction.d.ts +1 -0
  37. package/dist/tools/extraction.js +126 -2
  38. package/dist/tools/extraction.js.map +1 -1
  39. package/dist/tools/interaction.js +29 -10
  40. package/dist/tools/interaction.js.map +1 -1
  41. package/dist/tools/selector-pack.d.ts +23 -0
  42. package/dist/tools/selector-pack.js +98 -0
  43. package/dist/tools/selector-pack.js.map +1 -0
  44. package/extension/background.js +231 -8
  45. package/extension/manifest.json +1 -1
  46. package/hooks/pre-publish-verify.sh +18 -1
  47. package/package.json +2 -1
  48. package/safari-pilot.config.json +4 -0
  49. package/scripts/pre-tag-check.sh +149 -0
  50. package/scripts/test-e2e-harness.sh +64 -0
  51. package/skills/safari-pilot/SKILL.md +30 -1
  52. package/dist/security/idpi-scanner.d.ts +0 -27
  53. package/dist/security/idpi-scanner.js +0 -111
  54. package/dist/security/idpi-scanner.js.map +0 -1
  55. package/dist/security/screenshot-redaction.d.ts +0 -42
  56. package/dist/security/screenshot-redaction.js +0 -134
  57. package/dist/security/screenshot-redaction.js.map +0 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Safari Pilot gives Claude Code direct control of Safari through AppleScript and a persistent Swift daemon — no Chrome, no Playwright, no third-party code touching your browser. Your real Safari, with all your logins, automated natively.
6
6
 
7
- > **74 tools** | **3 engine tiers** | **92x faster than raw AppleScript** | **9 security layers** | **macOS 12+**
7
+ > **82 tools** | **3 engine tiers** | **92x faster than raw AppleScript** | **9 security layers** | **macOS 14+ recommended** (12+ minimum)
8
8
 
9
9
  ---
10
10
 
@@ -91,7 +91,8 @@ Without the extension, Safari Pilot still works for ~80% of use cases (navigatio
91
91
 
92
92
  ### System Requirements
93
93
 
94
- - **macOS 12.0 (Monterey)** or later
94
+ - **macOS 14.0 (Sonoma)** or later — recommended; required for the extension engine (the daemon's HTTP poll server uses Hummingbird, which requires macOS 14+)
95
+ - **macOS 12.0 (Monterey)** — minimum; daemon + AppleScript engines work, extension features are unavailable
95
96
  - **Safari** (pre-installed on every Mac)
96
97
  - **Node.js 20+**
97
98
 
@@ -119,7 +120,7 @@ Monitor news.ycombinator.com for any post about our company
119
120
  Open my X.com bookmarks and extract the top 5 posts with author profiles
120
121
  ```
121
122
 
122
- ## Tool Catalog (74 Tools)
123
+ ## Tool Catalog (82 Tools)
123
124
 
124
125
  ### Navigation (7)
125
126
  `safari_navigate` | `safari_navigate_back` | `safari_navigate_forward` | `safari_reload` | `safari_new_tab` | `safari_close_tab` | `safari_list_tabs`
@@ -127,15 +128,21 @@ Open my X.com bookmarks and extract the top 5 posts with author profiles
127
128
  ### Interaction (11)
128
129
  `safari_click` | `safari_double_click` | `safari_fill` | `safari_select_option` | `safari_check` | `safari_hover` | `safari_type` | `safari_press_key` | `safari_scroll` | `safari_drag` | `safari_handle_dialog`
129
130
 
131
+ ### File Upload (1)
132
+ `safari_file_upload` — programmatic upload to standard `<input type=file>` elements, including hidden inputs behind `<label>` (use `force: true`). 25 MiB / file × 4 / call. Path B architecture: out-of-band byte transport via daemon staging → extension fetch. Does NOT support drag-and-drop dropzones, custom pickers, or native OS dialogs.
133
+
130
134
  ### Extraction (7)
131
135
  `safari_snapshot` | `safari_get_text` | `safari_get_html` | `safari_get_attribute` | `safari_evaluate` | `safari_take_screenshot` | `safari_get_console_messages`
132
136
 
133
- ### Network (8)
134
- `safari_list_network_requests` | `safari_get_network_request` | `safari_intercept_requests` | `safari_network_throttle` | `safari_network_offline` | `safari_mock_request` | `safari_websocket_listen` | `safari_websocket_filter`
137
+ ### Network (10)
138
+ `safari_list_network_requests` | `safari_get_network_request` | `safari_intercept_requests` | `safari_network_throttle` | `safari_network_offline` | `safari_mock_request` | `safari_websocket_listen` | `safari_websocket_filter` | `safari_dump_har` | `safari_route_from_har`
135
139
 
136
140
  ### Storage (11)
137
141
  `safari_get_cookies` | `safari_set_cookie` | `safari_delete_cookie` | `safari_storage_state_export` | `safari_storage_state_import` | `safari_local_storage_get` | `safari_local_storage_set` | `safari_session_storage_get` | `safari_session_storage_set` | `safari_idb_list` | `safari_idb_get`
138
142
 
143
+ ### Authentication (2)
144
+ `safari_authenticate` | `safari_clear_authentication` — HTTP Basic auth via DNR header injection (extension required).
145
+
139
146
  ### Shadow DOM (2)
140
147
  `safari_query_shadow` | `safari_click_shadow`
141
148
 
@@ -160,9 +167,18 @@ Open my X.com bookmarks and extract the top 5 posts with author profiles
160
167
  ### Compound Workflows (4)
161
168
  `safari_test_flow` | `safari_monitor_page` | `safari_paginate_scrape` | `safari_media_control`
162
169
 
170
+ ### Downloads (1)
171
+ `safari_wait_for_download` — wait for download triggered by a click, capture metadata + optional `saveAs`.
172
+
173
+ ### PDF (1)
174
+ `safari_export_pdf` — export the frontmost Safari tab as a PDF via WKWebView.
175
+
163
176
  ### Wait (1)
164
177
  `safari_wait_for` — 7 condition types: selector, selectorHidden, text, textGone, urlMatch, networkidle, function
165
178
 
179
+ ### Diagnostics (2)
180
+ `safari_extension_health` | `safari_extension_debug_dump` — observability for the extension engine. Read-only; safe to call any time.
181
+
166
182
  ### System (2)
167
183
  `safari_health_check` | `safari_emergency_stop`
168
184
 
@@ -290,19 +306,27 @@ bash scripts/build-extension.sh
290
306
  ### Testing
291
307
 
292
308
  ```bash
293
- # All tests
294
- npm test
309
+ # Default — unit tests, no Safari required
310
+ npm test # 398 unit tests
311
+ npm run test:unit # alias for above
295
312
 
296
- # By category
297
- npm run test:unit # 777 tests, no Safari needed
298
- npm run test:integration # Multi-component workflows
299
- npm run test:security # 27 security-focused tests
300
- npm run test:e2e # Against real Safari (optional)
313
+ # Real Safari required (production stack must be running)
314
+ npm run test:e2e # ~30 e2e tests across 12+ files
315
+ npm run test:e2e:harness # 5 tests requiring DEBUG_HARNESS build (auto-rebuilds release after)
301
316
 
302
- # Swift daemon tests
303
- cd daemon && swift run SafariPilotdTests
317
+ # Both
318
+ npm run test:all # unit + e2e
319
+
320
+ # Swift daemon (real Swift types, mocked at NSAppleScript boundary only)
321
+ cd daemon && swift test # 153 tests
304
322
  ```
305
323
 
324
+ **Test policy:**
325
+ - Unit tests (`test/unit/`) cover pure logic; can mock Node boundaries (`fs`, `net`, `child_process`) but never internal modules.
326
+ - E2E tests (`test/e2e/`) spawn a real MCP server, drive Safari through the real stack, and use ZERO mocks (enforced by pre-commit hook). They fail closed on any `vi.mock` or direct `import from '../../src/'`.
327
+ - The harness-dependent tests (`t21`, `t22`, `t27`, `t44`, `t55a`) require `SAFARI_PILOT_TEST_MODE=1` build markers stripped from production. `npm run test:e2e:harness` automates the test build → run → release-rebuild flow. Local-only (refuses on CI).
328
+ - See `CLAUDE.md` "End-to-End Testing (HARD RULES)" for the full contract.
329
+
306
330
  ### Adding a New Tool
307
331
 
308
332
  1. Add the handler to the appropriate module in `src/tools/`
@@ -310,6 +334,31 @@ cd daemon && swift run SafariPilotdTests
310
334
  3. Write tests in `test/unit/tools/`
311
335
  4. The server auto-registers tools from all modules in `initialize()`
312
336
  5. Add the tool name to `skills/safari-pilot/SKILL.md` allowed-tools
337
+ 6. If touching `extension/*` or `daemon/Sources/*`, follow `CLAUDE.md` "Extension Build: Hard Rules" — version bump in lockstep, ditto with metadata-stripping flags, run `bash scripts/pre-tag-check.sh` before any tag push.
338
+
339
+ ### Releasing a new version
340
+
341
+ The release pipeline is automated via `.github/workflows/release.yml` on tag push. Before tagging, run the local SOP gate:
342
+
343
+ ```bash
344
+ # 1. Bump versions in lockstep
345
+ # Edit package.json + extension/manifest.json (must match)
346
+
347
+ # 2. Rebuild extension if extension/* changed
348
+ bash scripts/build-extension.sh
349
+
350
+ # 3. Local install rehearsal
351
+ open "bin/Safari Pilot.app" # verify in Safari Settings
352
+
353
+ # 4. Mandatory pre-tag check (mirrors every CI verify step)
354
+ bash scripts/pre-tag-check.sh # must print "ALL CHECKS PASSED"
355
+
356
+ # 5. Commit, tag, push
357
+ git tag -a v0.1.X -m "..."
358
+ git push origin main && git push origin v0.1.X
359
+ ```
360
+
361
+ The pre-tag check catches: AppleDouble (`._*`) metadata in zip, codesign --deep --strict failures, missing entitlements, version mismatch, dangling tag, prepublish hook misconfiguration, unit test regressions. It runs in seconds and saves CI round-trips.
313
362
 
314
363
  ## What Safari Pilot Does NOT Replace
315
364
 
@@ -23,13 +23,13 @@
23
23
  <key>CFBundlePackageType</key>
24
24
  <string>APPL</string>
25
25
  <key>CFBundleShortVersionString</key>
26
- <string>0.1.24</string>
26
+ <string>0.1.28</string>
27
27
  <key>CFBundleSupportedPlatforms</key>
28
28
  <array>
29
29
  <string>MacOSX</string>
30
30
  </array>
31
31
  <key>CFBundleVersion</key>
32
- <string>202605031834</string>
32
+ <string>202605050412</string>
33
33
  <key>DTCompiler</key>
34
34
  <string>com.apple.compilers.llvm.clang.1_0</string>
35
35
  <key>DTPlatformBuild</key>
@@ -19,13 +19,13 @@
19
19
  <key>CFBundlePackageType</key>
20
20
  <string>XPC!</string>
21
21
  <key>CFBundleShortVersionString</key>
22
- <string>0.1.24</string>
22
+ <string>0.1.28</string>
23
23
  <key>CFBundleSupportedPlatforms</key>
24
24
  <array>
25
25
  <string>MacOSX</string>
26
26
  </array>
27
27
  <key>CFBundleVersion</key>
28
- <string>202605031834</string>
28
+ <string>202605050412</string>
29
29
  <key>DTCompiler</key>
30
30
  <string>com.apple.compilers.llvm.clang.1_0</string>
31
31
  <key>DTPlatformBuild</key>
@@ -22,6 +22,12 @@ let wakePending = false;
22
22
  // initialize() is awaiting a fetch promise that will never resolve, and
23
23
  // its `finally` never runs to clear isWakeRunning).
24
24
  let pollLoopController = null;
25
+ // T73: timestamp of the last successful httpPoll resolution. Updated inside
26
+ // pollLoop's success try-block, read by supersedePollLoop to skip the abort
27
+ // cascade when the prior pollLoop is healthy. Default 0 → first supersede
28
+ // after extension load always runs (correct: there's nothing to be healthy
29
+ // about yet).
30
+ let lastSuccessfulPoll = 0;
25
31
 
26
32
  // ─── Tab Cache ──────────────────────────────────────────────────────────────
27
33
  // Safari's browser.tabs.query({}) returns [] when called from alarm-triggered
@@ -82,6 +88,72 @@ browser.tabs.onRemoved.addListener((tabId) => {
82
88
  saveTabCache();
83
89
  });
84
90
 
91
+ // T79: clear tab-scoped selectorPack storage on tab close. Keys live under
92
+ // prefix `sp_pack_<tabId>_<name>`. Multiple listeners can be added to the
93
+ // same event; this one runs independently of the tab-cache listener above.
94
+ browser.tabs.onRemoved.addListener(async (tabId) => {
95
+ try {
96
+ const all = await browser.storage.local.get(null);
97
+ const toRemove = Object.keys(all).filter((k) => k.startsWith('sp_pack_' + tabId + '_'));
98
+ if (toRemove.length > 0) {
99
+ await browser.storage.local.remove(toRemove);
100
+ emitTrace('__cleanup__', 'selector_pack_cleared', { layer: 'extension-bg', data: { tabId, count: toRemove.length } });
101
+ }
102
+ } catch (e) {
103
+ emitTrace('__cleanup__', 'selector_pack_clear_failed', { layer: 'extension-bg', data: { tabId, error: e && e.message ? e.message : String(e) } });
104
+ }
105
+ });
106
+
107
+ // T79 Cluster D: re-inject persisted packs into window.__sp_pack on every
108
+ // completed navigation. Runs purely from the extension-bg context — no MCP
109
+ // command involved. Reads sp_pack_<tabId>_<name> keys and re-injects each
110
+ // via the same storage-bus execute_script flow used everywhere else.
111
+ browser.tabs.onUpdated.addListener(async (tabId, changeInfo) => {
112
+ if (changeInfo.status !== 'complete') return;
113
+ let stored;
114
+ try {
115
+ stored = await browser.storage.local.get(null);
116
+ } catch (e) {
117
+ emitTrace('__rehydrate__', 'pack_rehydrate_storage_get_failed', { layer: 'extension-bg', data: { tabId, error: e && e.message ? e.message : String(e) } });
118
+ return;
119
+ }
120
+ const prefix = 'sp_pack_' + tabId + '_';
121
+ const keys = Object.keys(stored).filter((k) => k.startsWith(prefix));
122
+ if (keys.length === 0) return;
123
+ for (const key of keys) {
124
+ const name = key.slice(prefix.length);
125
+ const body = stored[key];
126
+ if (typeof body !== 'string' || !body) continue;
127
+ const injectionScript =
128
+ 'window.__sp_pack=window.__sp_pack||{};' +
129
+ 'try{' +
130
+ 'window.__sp_pack[' + JSON.stringify(name) + ']=new Function(\'root\',\'arg\',' + JSON.stringify(body) + ');' +
131
+ 'return JSON.stringify({ok:true,rehydrated:true});' +
132
+ '}catch(e){' +
133
+ 'return JSON.stringify({ok:false,error:e&&e.message?e.message:String(e)});' +
134
+ '}';
135
+ const rehydrateCmdId = '__rehydrate_' + tabId + '_' + name + '_' + Date.now();
136
+ const cmdKey = 'sp_cmd_' + rehydrateCmdId;
137
+ const storageCmd = {
138
+ commandId: rehydrateCmdId,
139
+ tabId,
140
+ method: 'execute_script',
141
+ params: { script: injectionScript, commandId: rehydrateCmdId },
142
+ timestamp: Date.now(),
143
+ deadline: Date.now() + 5000,
144
+ };
145
+ try {
146
+ await browser.storage.local.set({ [cmdKey]: storageCmd });
147
+ emitTrace(rehydrateCmdId, 'pack_rehydrated', { layer: 'extension-bg', data: { tabId, name } });
148
+ // Fire-and-forget — content-isolated picks it up, runs the injection.
149
+ // Cleanup the cmd key after a short delay to keep storage lean.
150
+ setTimeout(() => { browser.storage.local.remove([cmdKey, 'sp_result_' + rehydrateCmdId]).catch(() => {}); }, 6000);
151
+ } catch (e) {
152
+ emitTrace(rehydrateCmdId, 'pack_rehydrate_failed', { layer: 'extension-bg', data: { tabId, name, error: e && e.message ? e.message : String(e) } });
153
+ }
154
+ }
155
+ });
156
+
85
157
  // ─── HTTP IPC to daemon ─────────────────────────────────────────────────────
86
158
  const HTTP_URL = 'http://127.0.0.1:19475';
87
159
 
@@ -471,6 +543,98 @@ async function executeCommand(cmd) {
471
543
  return result;
472
544
  }
473
545
 
546
+ // T79 Cluster D: selectorPack register/unregister sentinels.
547
+ // Pack registration writes both to (a) page-scope window.__sp_pack[name] for
548
+ // immediate use AND (b) browser.storage.local sp_pack_<tabId>_<name>=body for
549
+ // persistence across navigations. The tabs.onUpdated listener below
550
+ // re-injects (a) from (b) on every navigation. The existing tabs.onRemoved
551
+ // listener cleans up (b) on tab close.
552
+ //
553
+ // Body must be a string already validated upstream by validatePackBody (the
554
+ // MCP-side selector-pack tool). The extension does NOT re-validate — it
555
+ // trusts that ANY script that reaches this sentinel passed validation.
556
+ // Single source of truth on the MCP side avoids drift.
557
+ if (typeof cmd.script === 'string' && cmd.script.startsWith('__SP_PACK_')) {
558
+ const colonIdx = cmd.script.indexOf(':');
559
+ const sentinel = colonIdx > 0 ? cmd.script.slice(0, colonIdx) : cmd.script;
560
+ let parsed = {};
561
+ if (colonIdx > 0) {
562
+ try { parsed = JSON.parse(cmd.script.slice(colonIdx + 1)); }
563
+ catch (e) {
564
+ const result = { ok: false, error: { name: 'PACK_PARAM_PARSE', message: `Failed to parse pack params: ${e?.message ?? String(e)}` } };
565
+ await updatePendingEntry(commandId, { status: 'completed', result });
566
+ return result;
567
+ }
568
+ }
569
+
570
+ const name = parsed.name;
571
+ const body = parsed.body;
572
+ if (typeof name !== 'string' || !name) {
573
+ const result = { ok: false, error: { name: 'PACK_INVALID_NAME', message: 'pack sentinel requires non-empty name' } };
574
+ await updatePendingEntry(commandId, { status: 'completed', result });
575
+ return result;
576
+ }
577
+
578
+ const storageKey = 'sp_pack_' + tab.id + '_' + name;
579
+
580
+ if (sentinel === '__SP_PACK_REGISTER__') {
581
+ if (typeof body !== 'string' || !body) {
582
+ const result = { ok: false, error: { name: 'PACK_INVALID_BODY', message: 'pack register sentinel requires non-empty body' } };
583
+ await updatePendingEntry(commandId, { status: 'completed', result });
584
+ return result;
585
+ }
586
+ try {
587
+ // Persist for re-injection on navigation.
588
+ await browser.storage.local.set({ [storageKey]: body });
589
+ } catch (e) {
590
+ const result = { ok: false, error: { name: 'PACK_STORAGE_WRITE_FAILED', message: e?.message ?? String(e) } };
591
+ await updatePendingEntry(commandId, { status: 'completed', result });
592
+ return result;
593
+ }
594
+ // Inject into page now via the standard storage-bus execute path. The
595
+ // page-side evaluator (content-main.js) wraps scripts in `new
596
+ // Function(script)()`, so the script must use a top-level `return` —
597
+ // an IIFE expression statement would have its result discarded.
598
+ // Embed name + body via JSON.stringify so quotes / backslashes survive
599
+ // the round-trip without bespoke escaping.
600
+ const injectionScript =
601
+ 'window.__sp_pack=window.__sp_pack||{};' +
602
+ 'try{' +
603
+ 'window.__sp_pack[' + JSON.stringify(name) + ']=new Function(\'root\',\'arg\',' + JSON.stringify(body) + ');' +
604
+ 'return JSON.stringify({ok:true,name:' + JSON.stringify(name) + '});' +
605
+ '}catch(e){' +
606
+ 'return JSON.stringify({ok:false,error:e&&e.message?e.message:String(e)});' +
607
+ '}';
608
+ // Replace cmd.script and fall through to the regular execute path so
609
+ // the inject runs through the storage-bus content-script flow.
610
+ cmd.script = injectionScript;
611
+ emitTrace(commandId, 'pack_registered', { layer: 'extension-bg', data: { tabId: tab.id, name, bodyBytes: body.length } });
612
+ // Fall through — the regular execute path runs `cmd.script` in-page.
613
+ } else if (sentinel === '__SP_PACK_UNREGISTER__') {
614
+ try {
615
+ await browser.storage.local.remove([storageKey]);
616
+ } catch (e) {
617
+ const result = { ok: false, error: { name: 'PACK_STORAGE_REMOVE_FAILED', message: e?.message ?? String(e) } };
618
+ await updatePendingEntry(commandId, { status: 'completed', result });
619
+ return result;
620
+ }
621
+ const removalScript =
622
+ 'var __removed=false;' +
623
+ 'if(window.__sp_pack&&window.__sp_pack[' + JSON.stringify(name) + ']){' +
624
+ 'delete window.__sp_pack[' + JSON.stringify(name) + '];' +
625
+ '__removed=true;' +
626
+ '}' +
627
+ 'return JSON.stringify({ok:true,removed:__removed});';
628
+ cmd.script = removalScript;
629
+ emitTrace(commandId, 'pack_unregistered', { layer: 'extension-bg', data: { tabId: tab.id, name } });
630
+ // Fall through — regular execute path runs the removal.
631
+ } else {
632
+ const r = { ok: false, error: { name: 'UNKNOWN_PACK_SENTINEL', message: `Unknown pack sentinel: ${sentinel}` } };
633
+ await updatePendingEntry(commandId, { status: 'completed', result: r });
634
+ return r;
635
+ }
636
+ }
637
+
474
638
  // T55a: validate frameId at dispatch time. Missing frame → fast-fail
475
639
  // before any storage-bus traffic. Re-resolve frame.url so content-isolated.js's
476
640
  // mutation guard has the authoritative value when comparing to location.href.
@@ -790,12 +954,32 @@ async function pollLoop(abortSignal) {
790
954
  const BACKOFF_MS = [0, 250, 500, 1000, 2000];
791
955
  const MAX_ATTEMPTS = 5;
792
956
  let attempts = 0;
957
+ // T72: pre-launch the FIRST /poll fetch BEFORE entering the while loop so
958
+ // a fetch is always in flight by the time the loop body runs. Combined
959
+ // with the in-loop reassignment below, this guarantees that MV3's event
960
+ // page never sees an idle moment between iterations — preventing the
961
+ // suspension that drove T71's 80% multi-file e2e sweep flake rate. The
962
+ // in-flight variable is awaited inside the existing BACKOFF_MS retry
963
+ // ladder so cold-start fetch errors still hit the retry path.
964
+ let inflightPoll = httpPoll(abortSignal);
793
965
  // T60: honor the abort signal threaded from pollLoopController so a fresh
794
966
  // alarm wake can forcibly stop a prior pollLoop instance — even one whose
795
967
  // fetch is wedged from event-page suspension recovery.
796
968
  while (!(abortSignal && abortSignal.aborted)) {
797
969
  try {
798
- const data = await httpPoll(abortSignal);
970
+ const data = await inflightPoll;
971
+ // T73: mark this pollLoop as healthy. supersedePollLoop reads this
972
+ // timestamp on each keepalive alarm and skips the abort cascade when
973
+ // recent (<30s). Set BEFORE the next-fetch reassignment so a successful
974
+ // resolution is recorded even if the next httpPoll throws synchronously.
975
+ lastSuccessfulPoll = Date.now();
976
+ // T72: kick the NEXT /poll fetch IMMEDIATELY — before processing this
977
+ // batch's commands — so a fetch is in flight while executeCommand
978
+ // runs. The MV3 event page stays alive on the in-flight fetch even
979
+ // when a tool call takes seconds (e.g. safari_navigate waiting for
980
+ // page load). Without this, a long-running executeCommand can let
981
+ // the page suspend before the next /poll iteration starts.
982
+ inflightPoll = httpPoll(abortSignal);
799
983
  if (attempts > 0) {
800
984
  emitTrace('__pollloop__', 'pollloop_recovered', { attempts });
801
985
  attempts = 0;
@@ -816,8 +1000,13 @@ async function pollLoop(abortSignal) {
816
1000
  // TimeoutError = the per-fetch 10 s timeout fired with no command —
817
1001
  // this is the NORMAL idle case (the daemon long-polls up to 5 s).
818
1002
  // Pre-T22 this killed the loop, requiring an alarm to re-arm: bug.
1003
+ // T72: re-arm the inflight fetch before continuing so the next iter
1004
+ // has a fetch to await (otherwise we'd await a stale resolved value).
819
1005
  if (err.name === 'TimeoutError') {
820
1006
  attempts = 0;
1007
+ if (!(abortSignal && abortSignal.aborted)) {
1008
+ inflightPoll = httpPoll(abortSignal);
1009
+ }
821
1010
  continue;
822
1011
  }
823
1012
  attempts++;
@@ -829,6 +1018,11 @@ async function pollLoop(abortSignal) {
829
1018
  const baseMs = BACKOFF_MS[Math.min(attempts, BACKOFF_MS.length - 1)];
830
1019
  const jitter = Math.random() * 250;
831
1020
  await new Promise((r) => setTimeout(r, baseMs + jitter));
1021
+ // T72: re-arm after backoff so the next iter awaits a fresh fetch
1022
+ // rather than the (already-rejected) prior one.
1023
+ if (!(abortSignal && abortSignal.aborted)) {
1024
+ inflightPoll = httpPoll(abortSignal);
1025
+ }
832
1026
  }
833
1027
  }
834
1028
  emitTrace('__pollloop__', 'pollloop_aborted', { reason: 'signal_aborted_pre_iter' });
@@ -837,7 +1031,32 @@ async function pollLoop(abortSignal) {
837
1031
  // T60: idempotent supersede. Aborts any prior pollLoop instance (releasing
838
1032
  // stuck fetches) and starts a fresh one with a new AbortController. The
839
1033
  // previous loop returns via its AbortError catch; this one runs free.
1034
+ //
1035
+ // T73: skip the abort cascade when the prior pollLoop is healthy. A healthy
1036
+ // pollLoop has resolved an httpPoll within the last 30s — its in-flight
1037
+ // /poll is registered in the daemon's waitingPolls and ready to fast-path
1038
+ // the next execute. Aborting it kills that registration; commands queued
1039
+ // during the supersede transition wait until the new pollLoop establishes
1040
+ // a fresh waitingPoll AND a new execute arrives — observed as a 10s gap in
1041
+ // e2e sweeps (T72-partial validation, 40% residual flake rate). Skipping
1042
+ // preserves the daemon-side registration through the keepalive nudge.
1043
+ //
1044
+ // 30s threshold rationale: daemon long-poll holds 5s normally; bursts of
1045
+ // activity reset lastSuccessfulPoll on every resolution. Idle systems with
1046
+ // no commands resolve every 5s (TimeoutError doesn't update the timestamp,
1047
+ // but the SUCCESS try-block does — and 204 responses count as success here:
1048
+ // they reach the resolution point in pollLoop). 30s gives 6x headroom.
840
1049
  function supersedePollLoop(reason) {
1050
+ // T73 health check — skip when prior pollLoop is alive AND has resolved
1051
+ // recently. Only the unhealthy path (wedged fetch, never-resolved poll,
1052
+ // or first-load) goes through the abort+restart cascade.
1053
+ if (pollLoopController && lastSuccessfulPoll > 0 && Date.now() - lastSuccessfulPoll < 30_000) {
1054
+ emitTrace('__pollloop__', 'pollloop_supersede_skipped', {
1055
+ reason,
1056
+ sinceLastPollMs: Date.now() - lastSuccessfulPoll,
1057
+ });
1058
+ return;
1059
+ }
841
1060
  if (pollLoopController) {
842
1061
  try { pollLoopController.abort(); } catch { /* ignore */ }
843
1062
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Safari Pilot",
4
- "version": "0.1.24",
4
+ "version": "0.1.28",
5
5
  "description": "Native Safari automation for AI agents",
6
6
  "permissions": [
7
7
  "activeTab",
@@ -6,7 +6,7 @@
6
6
  <dict>
7
7
  <key>Resources/background.js</key>
8
8
  <data>
9
- EfU46b8UhO6kbuDbXm4OmUTSPYA=
9
+ S+yhMSyT4yM0o29NjtVWT9pto7E=
10
10
  </data>
11
11
  <key>Resources/build.config.js</key>
12
12
  <data>
@@ -46,7 +46,7 @@
46
46
  </data>
47
47
  <key>Resources/manifest.json</key>
48
48
  <data>
49
- ddWr2VcPW3Z6yh+d9OYtkFXrbJI=
49
+ N9cYkZyI/ckF3TWIR2w2ujwqOSQ=
50
50
  </data>
51
51
  <key>Resources/native/SafariWebExtensionHandler.swift</key>
52
52
  <data>
@@ -59,7 +59,7 @@
59
59
  <dict>
60
60
  <key>hash2</key>
61
61
  <data>
62
- SQH/U7ba1WUn8mA/L86c18vZYkrgktstFu5rvfNWMK8=
62
+ wGGys1VHHjcourMlcj2w6IUjnjahEI8V1cj6WRb+/lU=
63
63
  </data>
64
64
  </dict>
65
65
  <key>Resources/build.config.js</key>
@@ -129,7 +129,7 @@
129
129
  <dict>
130
130
  <key>hash2</key>
131
131
  <data>
132
- Kz43qkUZwVRigVDVso531Bn1o1oW0TUoFfL7GF+oWpo=
132
+ Vh4l0sBEAXf+Jc3DaBwYM5wwB9LUiM1LhuR2p1VYK/4=
133
133
  </data>
134
134
  </dict>
135
135
  <key>Resources/native/SafariWebExtensionHandler.swift</key>
@@ -18,15 +18,15 @@
18
18
  </data>
19
19
  <key>Resources/Base.lproj/Main.storyboardc/Info.plist</key>
20
20
  <data>
21
- 24wt8BICLppM5oDAunyJiqKG+Ns=
21
+ Lk+CPfhfIGRA8ShZZFI+A0rJ2Fk=
22
22
  </data>
23
23
  <key>Resources/Base.lproj/Main.storyboardc/MainMenu.nib</key>
24
24
  <data>
25
- BINyoRyPY+Yacu4QIfgj0vK0I0c=
25
+ gfejsxd2k48cV+t1Ave1GMgeWeU=
26
26
  </data>
27
27
  <key>Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib</key>
28
28
  <data>
29
- 3mInKCWD+C6LI7RNH49sedCbgwo=
29
+ 9ykXQmnGGwCAJL2MPMLC7qPCpkQ=
30
30
  </data>
31
31
  <key>Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib</key>
32
32
  <data>
@@ -51,7 +51,7 @@
51
51
  <dict>
52
52
  <key>cdhash</key>
53
53
  <data>
54
- XruSftTDnGYDUY+ZeW8N2M8oLoE=
54
+ bWtJ8Gsg4i9gGuKEQ4SYB+FcDso=
55
55
  </data>
56
56
  <key>requirement</key>
57
57
  <string>anchor apple generic and identifier "com.safari-pilot.app.Extension" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = V37WLKRXUJ)</string>
@@ -81,21 +81,21 @@
81
81
  <dict>
82
82
  <key>hash2</key>
83
83
  <data>
84
- qVeY4D+fbP7JEwv1n85ZAqd2romyT1Ffoz/a/dBG9Oc=
84
+ 8J9pOXoi9UK5I6qZRMloMaWPxQoW+OciXbGvdnqtq0Q=
85
85
  </data>
86
86
  </dict>
87
87
  <key>Resources/Base.lproj/Main.storyboardc/MainMenu.nib</key>
88
88
  <dict>
89
89
  <key>hash2</key>
90
90
  <data>
91
- Zs70xyx+1AhDvMcjFgGmZ5SlohsV/ijDn5DiPJX0gUo=
91
+ pXqB05PG+ZwnFVMoJVsDjhgig8YjRG8grrBPWsZG/YI=
92
92
  </data>
93
93
  </dict>
94
94
  <key>Resources/Base.lproj/Main.storyboardc/NSWindowController-B8D-0N-5wS.nib</key>
95
95
  <dict>
96
96
  <key>hash2</key>
97
97
  <data>
98
- RsJOKk3V8PxtqyeGin8uKDP9U629uGXL0/RBWj7V8bc=
98
+ LAAQ0mIs7zxLtn87eO30xhqHEkEP/lblzyrpTD3gv0s=
99
99
  </data>
100
100
  </dict>
101
101
  <key>Resources/Base.lproj/Main.storyboardc/XfG-lQ-9wD-view-m2S-Jp-Qdl.nib</key>
Binary file
package/bin/SafariPilotd CHANGED
Binary file
package/dist/aria.js CHANGED
@@ -15,6 +15,10 @@
15
15
  * Safe for use in `querySelector()` or tool parameters.
16
16
  */
17
17
  export function buildRefSelector(ref) {
18
+ // T78: passthrough for fully-qualified data-sp-ref selectors so callers can
19
+ // hand in either the bare ref or the resolved selector form.
20
+ if (ref.startsWith('[data-sp-ref='))
21
+ return ref;
18
22
  return '[data-sp-ref="' + ref + '"]';
19
23
  }
20
24
  /**
package/dist/aria.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"aria.js","sourceRoot":"","sources":["../src/aria.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"aria.js","sourceRoot":"","sources":["../src/aria.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -31,6 +31,11 @@ export interface ExtensionConfig {
31
31
  enabled: boolean;
32
32
  killSwitchVersion: string;
33
33
  }
34
+ export interface SelectorPackConfig {
35
+ /** T79: feature-flag for safari_register_selector / safari_unregister_selector tools.
36
+ * Defaults to false. JS-injection surface — only enable for trusted environments. */
37
+ enabled: boolean;
38
+ }
34
39
  export interface SafariPilotConfig {
35
40
  schemaVersion: string;
36
41
  rateLimit: RateLimitConfig;
@@ -41,6 +46,7 @@ export interface SafariPilotConfig {
41
46
  daemon: DaemonConfig;
42
47
  healthCheck: HealthCheckConfig;
43
48
  extension: ExtensionConfig;
49
+ selectorPack: SelectorPackConfig;
44
50
  screenshotPolicy?: {
45
51
  blockedPatterns?: string[];
46
52
  };
package/dist/config.js CHANGED
@@ -37,6 +37,9 @@ export const DEFAULT_CONFIG = {
37
37
  enabled: true,
38
38
  killSwitchVersion: '0.1.5',
39
39
  },
40
+ selectorPack: {
41
+ enabled: false,
42
+ },
40
43
  };
41
44
  // ─── Deep merge ──────────────────────────────────────────────────────────────
42
45
  function isPlainObject(val) {
@@ -166,6 +169,12 @@ export function loadConfig(configPath) {
166
169
  // File not found or unreadable → use defaults silently
167
170
  }
168
171
  const merged = deepMerge(DEFAULT_CONFIG, userConfig);
172
+ // T79: selectorPack.enabled is a JS-injection-surface flag. Enforce strict
173
+ // boolean — anything other than literal `true` reverts to false (fail-safe).
174
+ const mergedRecord = merged;
175
+ const sp = mergedRecord['selectorPack'];
176
+ if (sp)
177
+ sp['enabled'] = sp['enabled'] === true;
169
178
  validate(merged);
170
179
  return deepFreeze(resolveConfigPaths(merged));
171
180
  }