conductor-remote 1.79.0 → 1.80.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/assets/{index-CV2YpJf7.js → index-ILQ9q-V7.js} +34 -34
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/dist-node/src/conductor.applescript +31 -0
- package/dist-node/src/routes.js +6 -0
- package/dist-node/src/server.js +31 -1
- package/dist-node/src/writes.js +43 -0
- package/package.json +1 -1
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-ILQ9q-V7.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-DaafT1eA.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 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:"e7ef44deca46c0539e6ff7bba5eb815e"},{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 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:"e7ef44deca46c0539e6ff7bba5eb815e"},{url:"index.html",revision:"4fa6b9e7cf25227330786aa264ae4ce6"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-ILQ9q-V7.js",revision:null},{url:"assets/index-DaafT1eA.css",revision:null},{url:"apple-touch-icon.png",revision:"1127bb396b4648add53dce3f22c92aee"},{url:"icon-192.png",revision:"c5e01ac58768627e18ee7b8b6a9239ef"},{url:"icon-512.png",revision:"a40638c55e310312457a621c9a0002c8"},{url:"icon-maskable-512.png",revision:"a9b0d962686287452216492cd2247499"},{url:"icon.svg",revision:"c1aee186821798733dd477e69a0ef243"},{url:"manifest.webmanifest",revision:"cf88fbc5755108a7fe0616fa160a8a15"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("/index.html"),{denylist:[/^\/api\//]}))});
|
|
@@ -293,6 +293,37 @@ on restartConductor()
|
|
|
293
293
|
return ""
|
|
294
294
|
end restartConductor
|
|
295
295
|
|
|
296
|
+
on restartApp()
|
|
297
|
+
-- The restart the phone asks for (POST /api/conductor/restart), as opposed to
|
|
298
|
+
-- the one activateConductor falls back to on its own. Same lever, different
|
|
299
|
+
-- entry, and the second entry is the whole point: that fallback fires only when
|
|
300
|
+
-- a *running* Conductor draws no window, so it is unreachable for the failure
|
|
301
|
+
-- this exists for — a Conductor whose window, sidebar and composer are all fine
|
|
302
|
+
-- while its agent runtime has stopped producing output. Measured live
|
|
303
|
+
-- (2026-09-02): prompts kept landing as `session_messages` rows for hours after
|
|
304
|
+
-- the last agent frame, so every window probe here would have said "healthy".
|
|
305
|
+
--
|
|
306
|
+
-- RELAY_ALLOW_RESTART is deliberately NOT consulted. That flag exists so the
|
|
307
|
+
-- fallback never destroys work the caller didn't ask about; here the caller is
|
|
308
|
+
-- a person who pressed a button, and server.ts has already counted the working
|
|
309
|
+
-- chats from the DB and refused without `stopAgents`.
|
|
310
|
+
set refusal to my refusalReason(my windowProbe())
|
|
311
|
+
if refusal is not "" then error refusal
|
|
312
|
+
-- Behind the lock screen a relaunch comes up windowless, and once came up
|
|
313
|
+
-- wedged (answering deep links, hanging every AX read) — the same reason the
|
|
314
|
+
-- fallback refuses there. The sentence starts with the phrase both sides match
|
|
315
|
+
-- on (MAC_LOCKED in src/shared.ts) so the phone draws its unlock link.
|
|
316
|
+
if my screenLocked() is "locked" then error "The Mac is locked - relaunching Conductor behind the lock screen is what leaves it wedged, so the relay won't. Unlock the Mac and try again." & my windowEvidence()
|
|
317
|
+
set restartError to my restartConductor()
|
|
318
|
+
if restartError is not "" then error restartError & my windowEvidence()
|
|
319
|
+
-- The fallback returns the moment the relaunch starts, because it is spending a
|
|
320
|
+
-- *send's* budget and a cold launch doesn't fit beside one. This run has nothing
|
|
321
|
+
-- else to do with its budget, so it waits for the window and the phone hears
|
|
322
|
+
-- what actually happened instead of "try again": ~5-10s for a cold launch here.
|
|
323
|
+
if not my waitForWindow(60) then error "Conductor was relaunched but hasn't drawn a window yet. Give it a few seconds, then try again." & my windowEvidence()
|
|
324
|
+
return "ok"
|
|
325
|
+
end restartApp
|
|
326
|
+
|
|
296
327
|
on waitForWindow(attempts)
|
|
297
328
|
-- "reopen" is the dock-click Apple event, and for most apps that is what
|
|
298
329
|
-- recreates a closed window ("activate" on its own does not). Nudge a few times
|
package/dist-node/src/routes.js
CHANGED
|
@@ -62,6 +62,12 @@ export const routes = {
|
|
|
62
62
|
/** Drop a staged file the user removed before creating its workspace. */
|
|
63
63
|
discardStagedAttachment: param('DELETE', '/api/attachments/:attachmentId'),
|
|
64
64
|
logs: flat('GET', '/api/logs'),
|
|
65
|
+
/**
|
|
66
|
+
* Quit Conductor and start it again. Not a workspace route: it is about the app,
|
|
67
|
+
* and its whole reason to exist is a Conductor that looks healthy from every other
|
|
68
|
+
* route while nothing behind it runs (src/writes.ts ▸ restartConductorApp).
|
|
69
|
+
*/
|
|
70
|
+
restartConductor: flat('POST', '/api/conductor/restart'),
|
|
65
71
|
settings: flat('GET', '/api/settings'),
|
|
66
72
|
updateSettings: flat('PATCH', '/api/settings'),
|
|
67
73
|
/** Durable, device-independent PWA state. localStorage remains its offline-first mirror. */
|
package/dist-node/src/server.js
CHANGED
|
@@ -32,7 +32,7 @@ import { discardStagedAttachment, materializeStagedAttachments, stageAttachment,
|
|
|
32
32
|
import { driftWarningLines, readExposeMode, tailscaleBin } from "./tailscale.js";
|
|
33
33
|
import { renderTranscript, transcriptThrough } from "./transcript.js";
|
|
34
34
|
import { autoJoinHotspotMode, currentSsid, looksLikeHotspot, preferredNetworks } from "./wifi.js";
|
|
35
|
-
import { archiveWorkspace, createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, lockBlocked, newChat, pickActuator, retryWontHelp, screenLocked, sendNeverStarted, setAgentOptions, setDefaultModel, setRestartGuard, setWorkspaceStatus, stopTurn, UiBusyError, uiQueueDepth, WORKSPACE_STATUS_LABELS, withUiPriority } from "./writes.js";
|
|
35
|
+
import { archiveWorkspace, createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, lockBlocked, newChat, pickActuator, restartConductorApp, retryWontHelp, screenLocked, sendNeverStarted, setAgentOptions, setDefaultModel, setRestartGuard, setWorkspaceStatus, stopTurn, UiBusyError, uiQueueDepth, WORKSPACE_STATUS_LABELS, withUiPriority } from "./writes.js";
|
|
36
36
|
// Before anything that logs: from here on every console line is also kept in memory for
|
|
37
37
|
// `GET /api/logs`, so the phone can read why a send failed without ssh-ing into the Mac.
|
|
38
38
|
installLogCapture();
|
|
@@ -957,6 +957,36 @@ const server = http.createServer(async (req, res) => {
|
|
|
957
957
|
const result = await disarmNoSleep();
|
|
958
958
|
return json(req, res, result.ok ? 200 : result.state.available ? 502 : 409, result);
|
|
959
959
|
}
|
|
960
|
+
// POST /api/conductor/restart { stopAgents? } — quit Conductor and start it again.
|
|
961
|
+
//
|
|
962
|
+
// The lever exists in the actuator already, but only as activateConductor's last
|
|
963
|
+
// resort, which fires exclusively for a *windowless* Conductor. This is for the
|
|
964
|
+
// other shape: window up, prompts landing as rows, and no agent output behind any
|
|
965
|
+
// of it (measured 2026-09-02 — 2h35m of user rows after the last agent frame).
|
|
966
|
+
// The running agents are counted from the DB before the UI is touched and refused
|
|
967
|
+
// unless the caller meant it, the same way archiving is: quitting ends every turn
|
|
968
|
+
// in flight. The lock screen is the actuator's own gate, since only it can ask.
|
|
969
|
+
if (isRoute(routes.restartConductor, req.method, pathname)) {
|
|
970
|
+
const body = JSON.parse((await readBody(req)) || '{}');
|
|
971
|
+
const working = reads.listSessionStates().filter(state => state.status === 'working').length;
|
|
972
|
+
if (working > 0 && body.stopAgents !== true) {
|
|
973
|
+
return json(req, res, 409, {
|
|
974
|
+
ok: false,
|
|
975
|
+
agentsRunning: true,
|
|
976
|
+
working,
|
|
977
|
+
error: `${working} chat${working === 1 ? ' is' : 's are'} mid-turn. Restarting Conductor ends ${working === 1 ? 'it' : 'them'}.`
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
const startedAt = Date.now();
|
|
981
|
+
const result = await restartConductorApp();
|
|
982
|
+
const ms = Date.now() - startedAt;
|
|
983
|
+
if (!result.ok) {
|
|
984
|
+
console.warn(`[restart] Conductor restart failed after ${(ms / 1000).toFixed(1)}s: ${result.error}`);
|
|
985
|
+
return json(req, res, 502, { ok: false, ms, error: result.error });
|
|
986
|
+
}
|
|
987
|
+
console.log(`[restart] quit Conductor and relaunched it in ${(ms / 1000).toFixed(1)}s`);
|
|
988
|
+
return json(req, res, 200, { ok: true, ms });
|
|
989
|
+
}
|
|
960
990
|
// GET /api/logs?file=&limit= — the relay's own log, so a phone can diagnose a failed send
|
|
961
991
|
// without reaching the Mac. Default is this process's captured console (ordered, timestamped);
|
|
962
992
|
// `file` tails the daemon's stdout/stderr on disk, which is the only place the *previous*
|
package/dist-node/src/writes.js
CHANGED
|
@@ -103,6 +103,13 @@ export function uiTurn(op) {
|
|
|
103
103
|
* a doomed one waits it out, and the caller's own deadline is what bounds that.
|
|
104
104
|
*/
|
|
105
105
|
export const SEND_ATTEMPT_MS = 28_000;
|
|
106
|
+
/**
|
|
107
|
+
* A restart's own ceiling. It is longer than a send's because it is mostly *waiting*:
|
|
108
|
+
* up to 4s for the quit to be honoured, then a cold launch, then `waitForWindow(60)`'s
|
|
109
|
+
* 15s for the first window — none of which can be hurried, and all of which the caller
|
|
110
|
+
* would rather wait through than be told "try again" about.
|
|
111
|
+
*/
|
|
112
|
+
export const RESTART_ATTEMPT_MS = 45_000;
|
|
106
113
|
/**
|
|
107
114
|
* A run's own ceiling, taken off the caller's deadline at the moment it actually
|
|
108
115
|
* starts.
|
|
@@ -649,6 +656,42 @@ return "ok"`.trim();
|
|
|
649
656
|
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
650
657
|
}
|
|
651
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Quit Conductor and start it again because the phone asked — the one write here
|
|
661
|
+
* whose subject is the app rather than anything inside it.
|
|
662
|
+
*
|
|
663
|
+
* The lever already existed and was unreachable. `activateConductor` restarts as its
|
|
664
|
+
* last resort, but only after a *running* Conductor has drawn no window through
|
|
665
|
+
* `reopen` and a Dock click, so it answers exactly one failure: a windowless app. The
|
|
666
|
+
* failure this is for looks healthy from every probe on that ladder — window up,
|
|
667
|
+
* sidebar drawing, composer taking prompts — while the agent runtime behind it has
|
|
668
|
+
* stopped producing anything. Measured on this Mac (2026-09-02): the last agent frame
|
|
669
|
+
* in `session_messages` was 20:47:44 and prompts kept landing as user rows for the
|
|
670
|
+
* next two and a half hours, each turn flipping `working → idle` having written
|
|
671
|
+
* nothing. Nothing on the read side can fix that, and "quit it on your Mac" is not
|
|
672
|
+
* advice a phone can act on.
|
|
673
|
+
*
|
|
674
|
+
* Two gates, and neither lives here. The **working chats** are counted from the DB by
|
|
675
|
+
* server.ts, which refuses without `stopAgents` — quitting takes every agent mid-turn
|
|
676
|
+
* down with it, so that has to be said out loud, exactly as it is for archiving. The
|
|
677
|
+
* **lock screen** is asked by `restartApp` itself, because a relaunch fired behind it
|
|
678
|
+
* comes up windowless (and once, wedged). What is left for this function is the UI
|
|
679
|
+
* lock: a restart is not a read, and letting one land while a send is mid-flight would
|
|
680
|
+
* quit the app between the composer write and the Enter.
|
|
681
|
+
*/
|
|
682
|
+
export async function restartConductorApp() {
|
|
683
|
+
const script = `
|
|
684
|
+
${CONDUCTOR_HANDLERS}
|
|
685
|
+
|
|
686
|
+
return my restartApp()`.trim();
|
|
687
|
+
try {
|
|
688
|
+
await uiTurn(() => exec('osascript', ['-e', script], { env: { ...process.env }, timeout: RESTART_ATTEMPT_MS }));
|
|
689
|
+
return { ok: true, strategy: 'applescript' };
|
|
690
|
+
}
|
|
691
|
+
catch (err) {
|
|
692
|
+
return { ok: false, strategy: 'applescript', error: osaError(err, 'Conductor didn’t come back in time') };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
652
695
|
/**
|
|
653
696
|
* The workspace statuses Conductor's sidebar groups by, mapped from the value it
|
|
654
697
|
* stores in `workspaces.manual_status` to the label on its own menu. `canceled`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.80.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.",
|