conductor-remote 1.68.0 → 1.68.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.
- package/dist/assets/index-BqCdHovG.js +47 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/dist-node/src/conductor.applescript +3 -3
- package/dist-node/src/server.js +38 -5
- package/dist-node/src/shared.js +33 -0
- package/dist-node/src/writes.js +5 -3
- package/package.json +1 -1
- package/dist/assets/index-R0LB5-IQ.js +0 -47
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-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-BqCdHovG.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-YPRuGHP4.css">
|
|
18
18
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
19
19
|
<body>
|
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:"e1e682e2e5e88fa03b7808ae9db8e098"},{url:"index.html",revision:"
|
|
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:"e1e682e2e5e88fa03b7808ae9db8e098"},{url:"index.html",revision:"82beeb823118f5808af7be9a2f306f5d"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-YPRuGHP4.css",revision:null},{url:"assets/index-BqCdHovG.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\//]}))});
|
|
@@ -340,7 +340,7 @@ on activateConductor()
|
|
|
340
340
|
-- retry loop polls this cheaply until its deadline, so an unlock mid-budget
|
|
341
341
|
-- lands the send by itself — that is the observed recovery, not a theory.
|
|
342
342
|
if (item 1 of firstProbe) < 1 and my screenLocked() is "locked" then
|
|
343
|
-
error "The Mac is locked - the lock screen hides Conductor from the relay, so
|
|
343
|
+
error "The Mac is locked - the lock screen hides Conductor from the relay, so nothing can be sent or pressed. Unlock the Mac and try again." & my windowEvidence()
|
|
344
344
|
end if
|
|
345
345
|
try
|
|
346
346
|
tell application "Conductor" to activate
|
|
@@ -379,14 +379,14 @@ on activateConductor()
|
|
|
379
379
|
-- window server can tell the difference, so a window it CAN see blocks the
|
|
380
380
|
-- restart — this is a human's problem, said in words, not the relay's.
|
|
381
381
|
if my serverWindowCount() > 0 then
|
|
382
|
-
if lockState is "locked" then error "The Mac is locked - Conductor's window is open behind the lock screen, where the relay can't reach it. Unlock the Mac and
|
|
382
|
+
if lockState is "locked" then error "The Mac is locked - Conductor's window is open behind the lock screen, where the relay can't reach it. Unlock the Mac and try again." & ev
|
|
383
383
|
error "Conductor's window exists but is not reachable - it is probably full screen on another Space. Take Conductor out of full screen, then send again." & ev
|
|
384
384
|
end if
|
|
385
385
|
-- No window anywhere and the lock screen up: don't restart. Every relaunch
|
|
386
386
|
-- fired behind the lock screen in the live incident came up windowless, and
|
|
387
387
|
-- the last one came up wedged — answering deep links, hanging every AX
|
|
388
388
|
-- read. A restart here is not a lever, it is how the wreckage happened.
|
|
389
|
-
if lockState is "locked" then error "The Mac is locked and Conductor has no window. Relaunching behind the lock screen is what leaves it wedged, so the relay won't - unlock the Mac and
|
|
389
|
+
if lockState is "locked" then error "The Mac is locked and Conductor has no window. Relaunching behind the lock screen is what leaves it wedged, so the relay won't - unlock the Mac and try again." & ev
|
|
390
390
|
set restartError to my restartConductor()
|
|
391
391
|
if restartError is not "" then error restartError & ev
|
|
392
392
|
error "Conductor was running with no window and ignored both reopen and a Dock click, so the relay restarted it. Give it a few seconds and send again." & ev
|
package/dist-node/src/server.js
CHANGED
|
@@ -27,6 +27,7 @@ import { isRoute, routeParam, routes } from "./routes.js";
|
|
|
27
27
|
import { foldHits, queryTokens, SearchIndex } from "./search.js";
|
|
28
28
|
import { SendOnce } from "./sendonce.js";
|
|
29
29
|
import { readSettings, writeSettings } from "./settings.js";
|
|
30
|
+
import { withoutWindowEvidence } from "./shared.js";
|
|
30
31
|
import { discardStagedAttachment, materializeStagedAttachments, stageAttachment, stagedAttachments } from "./staged-attachments.js";
|
|
31
32
|
import { driftWarningLines, readExposeMode, tailscaleBin } from "./tailscale.js";
|
|
32
33
|
import { renderTranscript } from "./transcript.js";
|
|
@@ -573,6 +574,31 @@ function previewRefusal(filePath) {
|
|
|
573
574
|
const MAX_ATTACHMENT_BYTES = 25 * 1024 * 1024;
|
|
574
575
|
class PayloadTooLargeError extends Error {
|
|
575
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* The last thing that happens to an error before it leaves the relay: the diagnostic
|
|
579
|
+
* tail `windowEvidence()` appends comes off, and the full text goes to the log instead.
|
|
580
|
+
*
|
|
581
|
+
* One place rather than the dozen routes that hand back a UI-write failure, for the
|
|
582
|
+
* same reason `redactSecrets` sits in front of every served log line: a rule applied at
|
|
583
|
+
* the boundary cannot be forgotten by the next route someone adds. What it prevents is
|
|
584
|
+
* what a tap on Fork against a locked Mac used to answer with — the sentence, then
|
|
585
|
+
* "[window server: 6; screen: locked] [processes: conductor=0] [menus: Apple, Conductor,
|
|
586
|
+
* File, ...]", in 11px red, on a phone that can do nothing with any of it. The evidence
|
|
587
|
+
* is still the fastest way to tell a wedged Conductor from a hidden window, so it lands
|
|
588
|
+
* in relay.log, which `/api/logs` serves to the same phone on request.
|
|
589
|
+
*/
|
|
590
|
+
function forTheClient(body) {
|
|
591
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
592
|
+
return body;
|
|
593
|
+
const error = body.error;
|
|
594
|
+
if (typeof error !== 'string')
|
|
595
|
+
return body;
|
|
596
|
+
const clean = withoutWindowEvidence(error);
|
|
597
|
+
if (clean === error)
|
|
598
|
+
return body;
|
|
599
|
+
console.warn(`[relay] ${error}`);
|
|
600
|
+
return { ...body, error: clean };
|
|
601
|
+
}
|
|
576
602
|
/**
|
|
577
603
|
* Successful GETs are conditional + compressed to keep the phone's polling cheap.
|
|
578
604
|
* `no-cache` (not `no-store`) means the browser must revalidate on every tick —
|
|
@@ -581,7 +607,7 @@ class PayloadTooLargeError extends Error {
|
|
|
581
607
|
* ~1 KB go out gzipped. Errors and non-GETs stay unconditional `no-store`.
|
|
582
608
|
*/
|
|
583
609
|
function json(req, res, status, body) {
|
|
584
|
-
const payload = Buffer.from(JSON.stringify(body));
|
|
610
|
+
const payload = Buffer.from(JSON.stringify(forTheClient(body)));
|
|
585
611
|
if (status !== 200 || req.method !== 'GET') {
|
|
586
612
|
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
|
|
587
613
|
return void res.end(payload);
|
|
@@ -844,15 +870,21 @@ const server = http.createServer(async (req, res) => {
|
|
|
844
870
|
// instead of asking someone to type a network name from memory on a phone keyboard.
|
|
845
871
|
// `ssid` is best-effort and often null (macOS gates it behind Location Services).
|
|
846
872
|
if (isRoute(routes.settings, req.method, pathname)) {
|
|
847
|
-
//
|
|
873
|
+
// Five subprocesses, all concurrent: this is the one route that shells out more
|
|
848
874
|
// than once, and serialising them would put the phone's polls behind the sum.
|
|
849
|
-
const [known, current, autoJoinHotspot, nosleep] = await Promise.all([
|
|
875
|
+
const [known, current, autoJoinHotspot, nosleep, locked] = await Promise.all([
|
|
850
876
|
preferredNetworks(),
|
|
851
877
|
currentSsid(),
|
|
852
878
|
// macOS's own Auto-join Hotspot setting. On "Never" the Mac won't reach for
|
|
853
879
|
// your phone unprompted, which no amount of relay code can substitute for.
|
|
854
880
|
autoJoinHotspotMode(),
|
|
855
|
-
nosleepState()
|
|
881
|
+
nosleepState(),
|
|
882
|
+
// Read here rather than polled: this sheet is where someone goes when the Mac
|
|
883
|
+
// stopped answering, and a keep-awake window that says "automatic screen lock
|
|
884
|
+
// is off" beside a Mac that is locked right now reads as the relay lying. The
|
|
885
|
+
// assertion blocks the *idle* lock; it cannot lift one already up, and a lid
|
|
886
|
+
// close or a manual lock puts one up whatever it holds.
|
|
887
|
+
screenLocked()
|
|
856
888
|
]);
|
|
857
889
|
return json(req, res, 200, {
|
|
858
890
|
settings: readSettings(),
|
|
@@ -863,7 +895,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
863
895
|
likelyHotspots: known.filter(looksLikeHotspot),
|
|
864
896
|
autoJoinHotspot
|
|
865
897
|
},
|
|
866
|
-
nosleep: { ...nosleep, maxSeconds: NOSLEEP_MAX_SECONDS }
|
|
898
|
+
nosleep: { ...nosleep, maxSeconds: NOSLEEP_MAX_SECONDS },
|
|
899
|
+
screenLocked: locked
|
|
867
900
|
});
|
|
868
901
|
}
|
|
869
902
|
// PATCH /api/settings { fallbackSsids?, autoRejoin? } — merge and persist.
|
package/dist-node/src/shared.js
CHANGED
|
@@ -214,3 +214,36 @@ export function isPreviewableSource(filePath) {
|
|
|
214
214
|
export function isToolResult(e) {
|
|
215
215
|
return e.role === 'tool' && !e.tool && e.output !== undefined;
|
|
216
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* The sentence every locked-Mac refusal starts with (src/conductor.applescript), and
|
|
219
|
+
* the one thing two sides must agree it means.
|
|
220
|
+
*
|
|
221
|
+
* The relay decides control flow on it — `lockBlocked` (src/writes.ts) parks the prompt
|
|
222
|
+
* rather than burning a phone's retry budget against a screen that will not answer for
|
|
223
|
+
* hours — and the phone decides what to draw: a link to Screen Sharing, because the
|
|
224
|
+
* relay will never unlock the Mac itself. Two matchers over one phrase, spelled out in
|
|
225
|
+
* two files, is how one of them quietly stops matching. macOS's own wording is never
|
|
226
|
+
* matched anywhere here; this sentence is ours, so it cannot drift under us.
|
|
227
|
+
*/
|
|
228
|
+
export const MAC_LOCKED = 'The Mac is locked';
|
|
229
|
+
export function isLockedError(error) {
|
|
230
|
+
return (error ?? '').includes(MAC_LOCKED);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* The diagnostic tail `windowEvidence()` (src/conductor.applescript) appends to every
|
|
234
|
+
* window and lock refusal: the window server's count, the lock state, every process
|
|
235
|
+
* named Conductor with its window count, and the menu bar titles.
|
|
236
|
+
*
|
|
237
|
+
* It exists to separate "genuinely windowless" from "we are addressing the wrong
|
|
238
|
+
* process", which is a question for the relay's log. It reached the phone as well,
|
|
239
|
+
* where a tap on Fork against a locked Mac answered with four lines of red text ending
|
|
240
|
+
* in "[menus: Apple, Conductor, File, Edit, View, Window, Help]" and nothing to act on.
|
|
241
|
+
* So `json()` in src/server.ts cuts it on the way out and logs the full text instead.
|
|
242
|
+
*
|
|
243
|
+
* Anchored on our own format: the whole tail is one run of bracketed groups that starts
|
|
244
|
+
* at "[window server:", so a single cut takes all of it and never touches a message
|
|
245
|
+
* that carries none.
|
|
246
|
+
*/
|
|
247
|
+
export function withoutWindowEvidence(error) {
|
|
248
|
+
return error.replace(/\s*\[window server:.*$/s, '').trim();
|
|
249
|
+
}
|
package/dist-node/src/writes.js
CHANGED
|
@@ -3,7 +3,7 @@ import { execFile } from 'node:child_process';
|
|
|
3
3
|
import { readFileSync } from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import { modelPickerLabel } from "./shared.js";
|
|
6
|
+
import { isLockedError, modelPickerLabel } from "./shared.js";
|
|
7
7
|
import { sidecarAvailable, sidecarSendUserMessage } from "./sidecar.js";
|
|
8
8
|
const exec = promisify(execFile);
|
|
9
9
|
/** Waiting runs past this are refused rather than queued. */
|
|
@@ -145,10 +145,12 @@ export function retryWontHelp(error) {
|
|
|
145
145
|
* warm-up cost a retry loop should burn budget on — the caller parks the prompt
|
|
146
146
|
* instead (src/parked.ts) and the queue delivers it when the lock lifts. Matched
|
|
147
147
|
* on the phrase every lock refusal in conductor.applescript starts with; the
|
|
148
|
-
* words are ours, so they can't drift under us the way macOS's can.
|
|
148
|
+
* words are ours, so they can't drift under us the way macOS's can. The phrase
|
|
149
|
+
* itself lives in src/shared.ts because the phone matches it too — it is what
|
|
150
|
+
* puts the Screen Sharing link beside a refusal nobody else can clear.
|
|
149
151
|
*/
|
|
150
152
|
export function lockBlocked(error) {
|
|
151
|
-
return (error
|
|
153
|
+
return isLockedError(error);
|
|
152
154
|
}
|
|
153
155
|
/**
|
|
154
156
|
* A run that ended with the prompt still sitting in Conductor's composer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.68.
|
|
3
|
+
"version": "1.68.1",
|
|
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.",
|