conductor-remote 1.27.4 → 1.28.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-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-ChC5JHf_.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-DnE5ulTu.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,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let l={};const c=e=>n(e,o),t={module:{uri:o},exports:l,require:c};i[o]=Promise.all(s.map(e=>t[e]||c(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"index.html",revision:"
|
|
1
|
+
if(!self.define){let e,i={};const n=(n,s)=>(n=new URL(n+".js",s).href,i[n]||new Promise(i=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=i,document.head.appendChild(e)}else e=n,importScripts(n),i()}).then(()=>{let e=i[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(s,r)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let l={};const c=e=>n(e,o),t={module:{uri:o},exports:l,require:c};i[o]=Promise.all(s.map(e=>t[e]||c(e))).then(e=>(r(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"self-heal.js",revision:"49bd63adb25a09341f8d2610e8bd3c76"},{url:"index.html",revision:"a23ebd6c333ee14357fb2a1cd7ab440f"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-DnE5ulTu.css",revision:null},{url:"assets/index-ChC5JHf_.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\//]}))});
|
package/dist-node/src/server.js
CHANGED
|
@@ -12,11 +12,17 @@ import { mergePr } from "./merge.js";
|
|
|
12
12
|
import { attachPrStatus } from "./pr.js";
|
|
13
13
|
import { Reads } from "./reads.js";
|
|
14
14
|
import { driftWarningLines, tailscaleBin } from "./tailscale.js";
|
|
15
|
-
import { createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, newChat, pickActuator, setAgentOptions } from "./writes.js";
|
|
15
|
+
import { createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, newChat, pickActuator, setAgentOptions, setRestartGuard } from "./writes.js";
|
|
16
16
|
const cfg = loadConfig();
|
|
17
17
|
const db = new ConductorDb(cfg.dbPath);
|
|
18
18
|
const reads = new Reads(db, cfg.workspacesRoot);
|
|
19
19
|
const actuator = pickActuator(cfg.writeStrategy);
|
|
20
|
+
// A windowless Conductor that ignores reopen *and* a Dock click can only be fixed
|
|
21
|
+
// by restarting it — and quitting takes any agent mid-turn down with it. So the
|
|
22
|
+
// write path may only do that while nothing is working, which is a DB fact, not
|
|
23
|
+
// something AppleScript can see. Read fresh each time: a session can start between
|
|
24
|
+
// the phone opening the app and the send landing.
|
|
25
|
+
setRestartGuard(() => !reads.listWorkspaces().some(w => w.session_status === 'working'));
|
|
20
26
|
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
21
27
|
/**
|
|
22
28
|
* Confirm a prompt actually reached the session by watching the transcript for a
|
package/dist-node/src/writes.js
CHANGED
|
@@ -109,6 +109,52 @@ on conductorRunning()
|
|
|
109
109
|
end try
|
|
110
110
|
end conductorRunning
|
|
111
111
|
|
|
112
|
+
on joinList(lst, sep)
|
|
113
|
+
set saved to AppleScript's text item delimiters
|
|
114
|
+
set AppleScript's text item delimiters to sep
|
|
115
|
+
set out to lst as text
|
|
116
|
+
set AppleScript's text item delimiters to saved
|
|
117
|
+
return out
|
|
118
|
+
end joinList
|
|
119
|
+
|
|
120
|
+
on processWindowCounts()
|
|
121
|
+
-- Every process whose name looks like Conductor, with its window count. If the
|
|
122
|
+
-- app is visibly on screen while "process Conductor" reports zero, the windows
|
|
123
|
+
-- belong to a process we are not addressing — and that only shows up here.
|
|
124
|
+
set out to {}
|
|
125
|
+
try
|
|
126
|
+
tell application "System Events"
|
|
127
|
+
repeat with proc in (every process whose name contains "Conductor")
|
|
128
|
+
set end of out to ((name of proc) & "=" & (count of windows of proc))
|
|
129
|
+
end repeat
|
|
130
|
+
end tell
|
|
131
|
+
end try
|
|
132
|
+
return out
|
|
133
|
+
end processWindowCounts
|
|
134
|
+
|
|
135
|
+
on menuTitles()
|
|
136
|
+
try
|
|
137
|
+
tell application "System Events" to tell process "Conductor"
|
|
138
|
+
return name of every menu bar item of menu bar 1
|
|
139
|
+
end tell
|
|
140
|
+
on error
|
|
141
|
+
return {}
|
|
142
|
+
end try
|
|
143
|
+
end menuTitles
|
|
144
|
+
|
|
145
|
+
on windowEvidence()
|
|
146
|
+
-- Zero windows is a dead end without knowing what the AX tree *does* show, and
|
|
147
|
+
-- guessing the next thing to press has already cost a round trip (there is no
|
|
148
|
+
-- "New Window" item — Conductor is single-window). So the error carries the
|
|
149
|
+
-- evidence instead: who macOS thinks Conductor is, and what its menus offer.
|
|
150
|
+
set procs to my processWindowCounts()
|
|
151
|
+
set titles to my menuTitles()
|
|
152
|
+
set ev to ""
|
|
153
|
+
if (count of procs) > 0 then set ev to ev & " [processes: " & my joinList(procs, ", ") & "]"
|
|
154
|
+
if (count of titles) > 0 then set ev to ev & " [menus: " & my joinList(titles, ", ") & "]"
|
|
155
|
+
return ev
|
|
156
|
+
end windowEvidence
|
|
157
|
+
|
|
112
158
|
on windowFailure()
|
|
113
159
|
-- "" when a window is there; otherwise why not, in words the phone can act on.
|
|
114
160
|
-- ASCII on purpose: these reach the phone, and osascript decodes -e by the
|
|
@@ -118,7 +164,7 @@ on windowFailure()
|
|
|
118
164
|
set probe to my windowProbe()
|
|
119
165
|
set winCount to item 1 of probe
|
|
120
166
|
if winCount > 0 then return ""
|
|
121
|
-
if winCount is 0 then return "Conductor
|
|
167
|
+
if winCount is 0 then return "Conductor is running but exposes no window - reopen and a Dock click both drew nothing. Open or restart it on your Mac and try again." & my windowEvidence()
|
|
122
168
|
set refusal to my refusalReason(probe)
|
|
123
169
|
if refusal is not "" then return refusal
|
|
124
170
|
if not (my conductorRunning()) then return "Conductor is not running - the relay started it, but it did not come up in time. Try again in a few seconds."
|
|
@@ -130,30 +176,55 @@ on requireWindow()
|
|
|
130
176
|
if reason is not "" then error reason
|
|
131
177
|
end requireWindow
|
|
132
178
|
|
|
133
|
-
on
|
|
134
|
-
-- Last resort when "reopen" draws nothing:
|
|
135
|
-
--
|
|
136
|
-
--
|
|
137
|
-
--
|
|
179
|
+
on dockClick()
|
|
180
|
+
-- Last resort when the "reopen" AppleEvent draws nothing: click the app's Dock
|
|
181
|
+
-- tile. Same intent, different delivery — the Dock raises it through the app's
|
|
182
|
+
-- own event loop rather than as an Apple event we send, which is what an app
|
|
183
|
+
-- that never wired up reopen can still answer. (There is no "New Window" menu
|
|
184
|
+
-- item to press: Conductor is single-window and single-instance.)
|
|
138
185
|
try
|
|
139
|
-
tell application "System Events" to tell process "
|
|
140
|
-
|
|
141
|
-
try
|
|
142
|
-
click (first menu item of menu 1 of topMenu whose name is "New Window")
|
|
143
|
-
return true
|
|
144
|
-
end try
|
|
145
|
-
end repeat
|
|
186
|
+
tell application "System Events" to tell application process "Dock"
|
|
187
|
+
click UI element "Conductor" of list 1
|
|
146
188
|
end tell
|
|
189
|
+
return true
|
|
190
|
+
on error
|
|
191
|
+
return false
|
|
147
192
|
end try
|
|
148
|
-
|
|
149
|
-
|
|
193
|
+
end dockClick
|
|
194
|
+
|
|
195
|
+
on restartConductor()
|
|
196
|
+
-- The last lever: Conductor is single-window and single-instance, so when a
|
|
197
|
+
-- running app answers neither reopen nor a Dock click there is nothing left to
|
|
198
|
+
-- press. Deliberately fire-and-forget — a cold launch is 5-10s on its own,
|
|
199
|
+
-- past what the phone's budget leaves once the send's own delays are counted —
|
|
200
|
+
-- so this returns as soon as the relaunch is under way and the caller tells the
|
|
201
|
+
-- phone to send again into a warm app. Gated by RELAY_ALLOW_RESTART, which the
|
|
202
|
+
-- relay only sets when the DB says no session is mid-turn.
|
|
203
|
+
try
|
|
204
|
+
tell application "Conductor" to quit
|
|
205
|
+
on error errText
|
|
206
|
+
return "couldn't quit Conductor to restart it: " & errText
|
|
207
|
+
end try
|
|
208
|
+
repeat with attempt from 1 to 16
|
|
209
|
+
if not (my conductorRunning()) then exit repeat
|
|
210
|
+
delay 0.25
|
|
211
|
+
end repeat
|
|
212
|
+
if my conductorRunning() then return "Conductor ignored the quit, so the relay left it alone - quit it on your Mac and try again."
|
|
213
|
+
try
|
|
214
|
+
tell application "Conductor" to activate
|
|
215
|
+
on error errText
|
|
216
|
+
return "quit Conductor but couldn't start it again: " & errText
|
|
217
|
+
end try
|
|
218
|
+
return ""
|
|
219
|
+
end restartConductor
|
|
150
220
|
|
|
151
221
|
on waitForWindow(attempts)
|
|
152
|
-
-- "reopen" is the dock-click event
|
|
153
|
-
-- "activate" on its own does not. Nudge a few times
|
|
154
|
-
-- launch can reach "process exists" long before it
|
|
155
|
-
-- has drawn nothing by ~3s
|
|
156
|
-
-- escalate to
|
|
222
|
+
-- "reopen" is the dock-click Apple event, and for most apps that is what
|
|
223
|
+
-- recreates a closed window ("activate" on its own does not). Nudge a few times
|
|
224
|
+
-- across the wait: a cold launch can reach "process exists" long before it
|
|
225
|
+
-- draws anything. If reopen has drawn nothing by ~3s the app isn't answering
|
|
226
|
+
-- that event, so escalate to a real Dock click rather than repeating what has
|
|
227
|
+
-- already failed twice.
|
|
157
228
|
repeat with attempt from 1 to attempts
|
|
158
229
|
if my hasWindow() then return true
|
|
159
230
|
if attempt is 2 or attempt is 8 or attempt is 20 then
|
|
@@ -162,7 +233,7 @@ on waitForWindow(attempts)
|
|
|
162
233
|
tell application "Conductor" to activate
|
|
163
234
|
end try
|
|
164
235
|
end if
|
|
165
|
-
if attempt is 12 or attempt is 28 then my
|
|
236
|
+
if attempt is 12 or attempt is 28 then my dockClick()
|
|
166
237
|
delay 0.25
|
|
167
238
|
end repeat
|
|
168
239
|
return false
|
|
@@ -196,6 +267,14 @@ on activateConductor()
|
|
|
196
267
|
set patience to 16
|
|
197
268
|
if not wasRunning then set patience to 36
|
|
198
269
|
if my waitForWindow(patience) then return
|
|
270
|
+
-- Running, readable, and still zero windows: reopen and the Dock click have
|
|
271
|
+
-- both drawn nothing, so a restart is the only thing left. "Open it on your
|
|
272
|
+
-- Mac" is not advice a phone can act on — that is the whole point of this app.
|
|
273
|
+
if (system attribute "RELAY_ALLOW_RESTART") is "1" and (item 1 of my windowProbe()) is 0 then
|
|
274
|
+
set restartError to my restartConductor()
|
|
275
|
+
if restartError is not "" then error restartError
|
|
276
|
+
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."
|
|
277
|
+
end if
|
|
199
278
|
my requireWindow()
|
|
200
279
|
end activateConductor
|
|
201
280
|
|
|
@@ -720,9 +799,33 @@ function sidebarTitles(ws) {
|
|
|
720
799
|
ws.directory_name
|
|
721
800
|
].filter((t) => Boolean(t));
|
|
722
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* May the actuator restart Conductor to force a window into existence?
|
|
804
|
+
*
|
|
805
|
+
* Restarting is the only lever left when a running, windowless Conductor ignores
|
|
806
|
+
* both `reopen` and a Dock click — it is single-window and single-instance, so
|
|
807
|
+
* there is nothing else to press. It is also the one step here that can destroy
|
|
808
|
+
* work: quitting takes any agent mid-turn down with it. "Nothing is running" is a
|
|
809
|
+
* fact only the read side has, so server.ts wires this to a DB read rather than
|
|
810
|
+
* writes.ts guessing. Unset → never restart, which is the safe default for any
|
|
811
|
+
* caller that hasn't opted in.
|
|
812
|
+
*/
|
|
813
|
+
let restartGuard = null;
|
|
814
|
+
export function setRestartGuard(guard) {
|
|
815
|
+
restartGuard = guard;
|
|
816
|
+
}
|
|
817
|
+
function restartAllowed() {
|
|
818
|
+
try {
|
|
819
|
+
return restartGuard?.() ?? false;
|
|
820
|
+
}
|
|
821
|
+
catch {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
723
825
|
/** The target rides in on the environment, like RELAY_WS_QUERY, to dodge AppleScript escaping. */
|
|
724
826
|
function targetEnv(target) {
|
|
725
827
|
return {
|
|
828
|
+
RELAY_ALLOW_RESTART: restartAllowed() ? '1' : '',
|
|
726
829
|
RELAY_TAB_INDEX: String(target.tab?.index ?? 0),
|
|
727
830
|
RELAY_TAB_COUNT: String(target.tab?.count ?? 0),
|
|
728
831
|
RELAY_TAB_TITLE: target.tab?.title ?? '',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.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.",
|