conductor-remote 1.39.0 → 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.html CHANGED
@@ -13,7 +13,7 @@
13
13
  <title>Conductor Remote</title>
14
14
  <!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
15
15
  <script src="/self-heal.js"></script>
16
- <script type="module" crossorigin src="/assets/index-CHhZJBVH.js"></script>
16
+ <script type="module" crossorigin src="/assets/index-BhYD1Ar2.js"></script>
17
17
  <link rel="stylesheet" crossorigin href="/assets/index-UopM0hHW.css">
18
18
  <link rel="manifest" href="/manifest.webmanifest"></head>
19
19
  <body>
package/dist/push-sw.js CHANGED
@@ -13,6 +13,25 @@
13
13
  // 2. **A tap focuses the existing window** rather than navigating it. The app is a
14
14
  // token-gated SPA; `openWindow` on a live client would remount the whole thing and
15
15
  // throw away in-progress composer text, so we focus and post a route instead.
16
+ // 3. **The route is also parked in Cache Storage**, because on iOS neither of the two
17
+ // direct routes survives. A backgrounded home-screen web app is resumed on whatever
18
+ // screen it was left on — `openWindow`'s path is ignored, and a `postMessage` to a
19
+ // frozen page is dropped (WebKit, reported from iOS 17.1 through 18.x and still
20
+ // open). The cache outlives both, so the app reads its target when it comes back to
21
+ // the front; see `usePushRouting` in web/src/hooks.ts.
22
+
23
+ /** One entry, overwritten per tap: only the newest tap can still be waiting to land. */
24
+ const ROUTE_CACHE = 'push-route'
25
+ const ROUTE_KEY = '/__push-route'
26
+
27
+ async function parkRoute(url) {
28
+ try {
29
+ const cache = await caches.open(ROUTE_CACHE)
30
+ await cache.put(ROUTE_KEY, new Response(JSON.stringify({ url, ts: Date.now() })))
31
+ } catch {
32
+ // Storage refused it (quota, a private window). The two direct routes below still stand.
33
+ }
34
+ }
16
35
 
17
36
  self.addEventListener('push', event => {
18
37
  const fallback = { title: 'Conductor Remote', body: 'Something changed in a workspace.', url: '/', tag: 'conductor' }
@@ -28,8 +47,9 @@ self.addEventListener('push', event => {
28
47
  event.waitUntil(
29
48
  self.registration.showNotification(data.title, {
30
49
  body: data.body,
31
- // Tagged per workspace by the relay: a chatty agent replaces its own notification
32
- // instead of stacking. `renotify` keeps the replacement audible.
50
+ // Tagged per chat by the relay: a chatty agent replaces its own notification
51
+ // instead of stacking, while a sibling chat keeps its own (they open different
52
+ // screens). `renotify` keeps the replacement audible.
33
53
  tag: data.tag,
34
54
  renotify: true,
35
55
  icon: '/icon-192.png',
@@ -45,18 +65,23 @@ self.addEventListener('notificationclick', event => {
45
65
  const url = (event.notification.data && event.notification.data.url) || '/'
46
66
  event.waitUntil(
47
67
  (async () => {
68
+ // Park first. Everything below can succeed and still leave the phone on the wrong
69
+ // screen, and this is the copy the app reads when it wakes up.
70
+ await parkRoute(url)
48
71
  const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true })
49
72
  for (const client of clients) {
50
73
  if (new URL(client.url).origin !== self.location.origin) continue
74
+ // Handled in web/src/hooks.ts (usePushRouting) — an in-app route change, so the
75
+ // token gate and React state survive the tap. Posted before the focus, since a
76
+ // refused focus is no reason to skip a message the page may well receive.
77
+ client.postMessage({ type: 'push-navigate', url })
51
78
  try {
52
79
  await client.focus()
53
- // Handled in web/src/hooks.ts (usePushRouting) — an in-app route change,
54
- // so the token gate and React state survive the tap.
55
- client.postMessage({ type: 'push-navigate', url })
56
- return
57
80
  } catch {
58
- // focus() can be refused (no user activation on some platforms) — fall through to openWindow
81
+ // focus() can be refused (no user activation on some platforms); on iOS the
82
+ // system foregrounds the web app on the tap regardless.
59
83
  }
84
+ return
60
85
  }
61
86
  await self.clients.openWindow(url)
62
87
  })()
package/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let l={};const t=e=>i(e,o),c={module:{uri:o},exports:l,require:t};s[o]=Promise.all(n.map(e=>c[e]||t(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"890f6a70e97d144f4d025254e90892a1"},{url:"index.html",revision:"79c117a92e7635dd0174f5aeb1f3940c"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-UopM0hHW.css",revision:null},{url:"assets/index-CHhZJBVH.js",revision:null},{url:"apple-touch-icon.png",revision:"2b9301416b880d45d4bb655f2600d1f2"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
1
+ if(!self.define){let e,s={};const i=(i,n)=>(i=new URL(i+".js",n).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(n,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let l={};const c=e=>i(e,o),t={module:{uri:o},exports:l,require:c};s[o]=Promise.all(n.map(e=>t[e]||c(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";importScripts("/push-sw.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"push-sw.js",revision:"e1e682e2e5e88fa03b7808ae9db8e098"},{url:"index.html",revision:"1dca8269fa4358508f3ca90f0c216cdb"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-UopM0hHW.css",revision:null},{url:"assets/index-BhYD1Ar2.js",revision:null},{url:"apple-touch-icon.png",revision:"2b9301416b880d45d4bb655f2600d1f2"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
@@ -218,6 +218,15 @@ export async function notifyDevice(id, message) {
218
218
  export function deviceCount() {
219
219
  return load().devices.length;
220
220
  }
221
+ /**
222
+ * Where a tapped notification lands. The chat id rides along because the phone
223
+ * otherwise picks the tab itself (Conductor's active session, else the first one)
224
+ * — and on a multi-chat workspace that is rarely the chat that just finished.
225
+ * Kept here so the notifier and the parked-prompt queue can't drift apart.
226
+ */
227
+ export function chatRoute(workspaceId, sessionId) {
228
+ return `/w/${workspaceId}?session=${encodeURIComponent(sessionId)}`;
229
+ }
221
230
  /** Collapse a transcript entry to one lock-screen line: no code fences, no blank runs. */
222
231
  function oneLine(text) {
223
232
  return clip(text
@@ -287,9 +296,11 @@ async function fire(reads, sessionId, kind, state) {
287
296
  const sent = await notifyAll({
288
297
  title: state.repoName ? `${where} — ${state.repoName}` : where,
289
298
  body,
290
- // Per workspace, so a chatty agent replaces its own notification instead of stacking.
291
- tag: state.workspaceId,
292
- url: `/w/${state.workspaceId}`,
299
+ // Per chat, so a chatty agent replaces its own notification instead of stacking —
300
+ // and two chats in one workspace stay separately tappable, since each now lands
301
+ // somewhere different.
302
+ tag: sessionId,
303
+ url: chatRoute(state.workspaceId, sessionId),
293
304
  kind,
294
305
  ts: Date.now()
295
306
  });
@@ -12,7 +12,7 @@ import { workspaceDiff } from "./git.js";
12
12
  import { installLogCapture, isManaged, LOG_FILE_NAMES, logFiles, processStartedAt, recentLogs, redactSecrets, tailLogFile } from "./logbuf.js";
13
13
  import { mergePr } from "./merge.js";
14
14
  import { armNoSleep, disarmNoSleep, MAX_SECONDS as NOSLEEP_MAX_SECONDS, nosleepState, watchNoSleepExpiry } from "./nosleep.js";
15
- import { notifyAll, notifyDevice, pushConfig, startNotifier, subscribeDevice, unsubscribeDevice } from "./notify.js";
15
+ import { chatRoute, notifyAll, notifyDevice, pushConfig, startNotifier, subscribeDevice, unsubscribeDevice } from "./notify.js";
16
16
  import { ParkedPromptQueue } from "./parked.js";
17
17
  import { attachPrStatus } from "./pr.js";
18
18
  import { Reads } from "./reads.js";
@@ -288,7 +288,7 @@ const parkedPrompts = new ParkedPromptQueue(path.join(stateDir(), 'parked-prompt
288
288
  body: error ? `Parked prompt failed: ${error}` : `Sent after unlock: ${preview}`,
289
289
  // Per chat, so a second parked prompt replaces the first's notification.
290
290
  tag: `parked-${entry.sessionId}`,
291
- url: `/w/${entry.workspaceId}`,
291
+ url: chatRoute(entry.workspaceId, entry.sessionId),
292
292
  kind: error ? 'error' : 'done',
293
293
  ts: Date.now()
294
294
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.39.0",
3
+ "version": "1.40.0",
4
4
  "type": "module",
5
5
  "packageManager": "yarn@4.15.0",
6
6
  "description": "Phone control panel for local Conductor agents. Reads ride SQLite + git; prompts ride Conductor's own dispatch path.",