conductor-remote 1.91.0 → 1.92.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/index.html CHANGED
@@ -25,8 +25,8 @@
25
25
  <title>Conductor Remote</title>
26
26
  <!-- Runs before the module bundle so it can catch a stale shell that fails to boot. -->
27
27
  <script src="/self-heal.js"></script>
28
- <script type="module" crossorigin src="/assets/index-BmTEaKny.js"></script>
29
- <link rel="stylesheet" crossorigin href="/assets/index-yWUBiiEb.css">
28
+ <script type="module" crossorigin src="/assets/index-DUZ2ZaW9.js"></script>
29
+ <link rel="stylesheet" crossorigin href="/assets/index-DFclyO59.css">
30
30
  <link rel="manifest" href="/manifest.webmanifest"></head>
31
31
  <body>
32
32
  <div id="root"></div>
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 c={};const l=e=>i(e,o),t={module:{uri:o},exports:c,require:l};s[o]=Promise.all(n.map(e=>t[e]||l(e))).then(e=>(r(...e),c))}}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:"7c423b0aae6b050cf5e00e4b0bd9dcc6"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-yWUBiiEb.css",revision:null},{url:"assets/index-BmTEaKny.js",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\//]}))});
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:"c37a7b1ba1860e09f2d4ea15c7b8423f"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-DUZ2ZaW9.js",revision:null},{url:"assets/index-DFclyO59.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\//]}))});
@@ -1336,6 +1336,78 @@ on buttonsUnder(root, maxDepth)
1336
1336
  return acc
1337
1337
  end buttonsUnder
1338
1338
 
1339
+ on shallowButtonsNamed(root, maxDepth, wanted)
1340
+ -- Exact-name buttons at the first depth that contains one. Continue lives in
1341
+ -- Conductor's workspace action bar, while a chat transcript hangs much deeper
1342
+ -- off the same web area. Stopping at the shallowest hit both avoids walking a
1343
+ -- long transcript and keeps a similarly named control inside a message from
1344
+ -- impersonating the workspace action.
1345
+ set level to {root}
1346
+ set depth to 0
1347
+ tell application "System Events" to tell process "Conductor"
1348
+ repeat while (count of level) > 0 and depth < maxDepth
1349
+ set found to {}
1350
+ set nextLevel to {}
1351
+ repeat with entry in level
1352
+ set node to contents of entry
1353
+ try
1354
+ repeat with candidate in (get UI elements of node whose role is "AXButton")
1355
+ set controlEl to contents of candidate
1356
+ if (my axName(controlEl)) is wanted then set end of found to controlEl
1357
+ end repeat
1358
+ set nextLevel to nextLevel & (get UI elements of node)
1359
+ end try
1360
+ end repeat
1361
+ if (count of found) > 0 then return found
1362
+ set level to nextLevel
1363
+ set depth to depth + 1
1364
+ end repeat
1365
+ end tell
1366
+ return {}
1367
+ end shallowButtonsNamed
1368
+
1369
+ on hasSiblingButtonNamed(controlEl, wanted)
1370
+ -- The live merged action is the pair Continue + Archive. Requiring that pair
1371
+ -- prevents a generic Continue in a modal from impersonating it while the
1372
+ -- asserted workspace pane remains visible behind the modal.
1373
+ try
1374
+ tell application "System Events" to tell process "Conductor"
1375
+ set actionRow to value of attribute "AXParent" of controlEl
1376
+ repeat with peer in (get UI elements of actionRow whose role is "AXButton")
1377
+ if (my axName(contents of peer)) is wanted then return true
1378
+ end repeat
1379
+ end tell
1380
+ end try
1381
+ return false
1382
+ end hasSiblingButtonNamed
1383
+
1384
+ on continueWorkspaceOnNewBranch()
1385
+ -- Press Conductor's own merged-workspace action. Its handler owns all of the
1386
+ -- state that cannot be reproduced safely from the relay: it checks out a fresh
1387
+ -- branch at the PR's target, updates the workspace row, keeps every chat, and
1388
+ -- stages Branch continued.md in the selected chat. The relay never writes the
1389
+ -- database; server.ts watches `workspaces.branch` for the receipt.
1390
+ set strips to my tabGroups()
1391
+ if (count of strips) is 0 then error "couldn't find the chat pane to continue from"
1392
+ my assertWorkspace(item 1 of strips)
1393
+ set found to my shallowButtonsNamed(my webArea(), 10, "Continue")
1394
+ if (count of found) is 0 then error "Conductor isn't offering Continue for this workspace - it appears after its pull request is merged and refreshed"
1395
+ if (count of found) > 1 then error "more than one Continue button is visible - close any dialog and try again"
1396
+ set continueEl to item 1 of found
1397
+ if not (my hasSiblingButtonNamed(continueEl, "Archive")) then error "couldn't verify Conductor's merged-workspace Continue action - close any dialog and try again"
1398
+ set canPress to true
1399
+ try
1400
+ tell application "System Events" to tell process "Conductor"
1401
+ set canPress to (value of attribute "AXEnabled" of continueEl) as boolean
1402
+ end tell
1403
+ end try
1404
+ if not canPress then error "Conductor's Continue button is still busy - try again shortly"
1405
+ tell application "System Events" to tell process "Conductor"
1406
+ perform action "AXPress" of continueEl
1407
+ end tell
1408
+ delay 0.4
1409
+ end continueWorkspaceOnNewBranch
1410
+
1339
1411
  on dialogButtons()
1340
1412
  -- Both places Conductor could be drawing a confirmation, each at the depth that
1341
1413
  -- shape actually sits at. A real sheet hangs off the window a level or two down;
@@ -96,6 +96,8 @@ export const routes = {
96
96
  /** The worktree's own file list, which is what makes a chat's `src/foo.ts` a link. */
97
97
  workspaceFiles: param('GET', '/api/workspaces/:workspaceId/files'),
98
98
  merge: param('POST', '/api/workspaces/:workspaceId/merge'),
99
+ /** Keep this live workspace and its chats, but move its merged worktree onto a fresh branch. */
100
+ continueWorkspace: param('POST', '/api/workspaces/:workspaceId/continue'),
99
101
  workspaceStatus: param('POST', '/api/workspaces/:workspaceId/status'),
100
102
  /** Put the workspace away (Conductor's ⌘⇧A) — the one write that deletes a worktree. */
101
103
  archiveWorkspace: param('POST', '/api/workspaces/:workspaceId/archive'),
@@ -35,7 +35,7 @@ import { discardStagedAttachment, materializeStagedAttachments, pruneStagedAttac
35
35
  import { driftWarningLines, readExposeMode, tailscaleBin } from "./tailscale.js";
36
36
  import { renderTranscript, transcriptMessage, transcriptThrough } from "./transcript.js";
37
37
  import { autoJoinHotspotMode, currentSsid, looksLikeHotspot, preferredNetworks } from "./wifi.js";
38
- import { archiveWorkspace, closeChat, createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, lockBlocked, newChat, pickActuator, planSettingForUi, restartConductorApp, retryWontHelp, screenLocked, sendNeverStarted, setAgentOptions, setDefaultModel, setRestartGuard, setWorkspaceStatus, stopTurn, UiBusyError, uiQueueDepth, WORKSPACE_STATUS_LABELS, withUiPriority } from "./writes.js";
38
+ import { archiveWorkspace, closeChat, continueWorkspace, createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, lockBlocked, newChat, pickActuator, planSettingForUi, restartConductorApp, retryWontHelp, screenLocked, sendNeverStarted, setAgentOptions, setDefaultModel, setRestartGuard, setWorkspaceStatus, stopTurn, UiBusyError, uiQueueDepth, WORKSPACE_STATUS_LABELS, withUiPriority } from "./writes.js";
39
39
  // Before anything that logs: from here on every console line is also kept in memory for
40
40
  // `GET /api/logs`, so the phone can read why a send failed without ssh-ing into the Mac.
41
41
  installLogCapture();
@@ -1331,6 +1331,56 @@ const server = http.createServer(async (req, res) => {
1331
1331
  const result = await mergePr(ws);
1332
1332
  return json(req, res, result.ok ? 200 : 409, result);
1333
1333
  }
1334
+ // POST /api/workspaces/:id/continue { sessionId? } — press the Continue action
1335
+ // Conductor draws for a merged PR. The native handler checks out a fresh branch,
1336
+ // updates its own workspace record and stages Branch continued.md in the selected
1337
+ // chat. Only it can do all three consistently; this relay keeps its DB read-only.
1338
+ const continueOf = routeParam(routes.continueWorkspace, req.method, pathname);
1339
+ if (continueOf) {
1340
+ const workspaceId = continueOf;
1341
+ const ws = reads.getWorkspace(workspaceId);
1342
+ if (!ws) {
1343
+ const known = reads.getAnyWorkspace(workspaceId);
1344
+ if (known?.archived) {
1345
+ return json(req, res, 409, { ok: false, error: 'Archived workspaces cannot be continued.' });
1346
+ }
1347
+ return json(req, res, 404, { error: 'workspace not found' });
1348
+ }
1349
+ const body = JSON.parse((await readBody(req)) || '{}');
1350
+ const requestedSession = body.sessionId || ws.active_session_id;
1351
+ let tab;
1352
+ if (requestedSession) {
1353
+ const located = locateChat(ws, requestedSession);
1354
+ if ('error' in located)
1355
+ return json(req, res, 409, { ok: false, error: located.error });
1356
+ if (body.sessionId && !located.session) {
1357
+ return json(req, res, 409, { ok: false, error: 'chat is no longer one of the workspace’s tabs' });
1358
+ }
1359
+ tab = located.tab;
1360
+ }
1361
+ const previousBranch = ws.branch;
1362
+ if (!previousBranch)
1363
+ return json(req, res, 409, { ok: false, error: 'workspace has no branch to continue' });
1364
+ const result = await continueWorkspace({ workspace: ws, sessionId: requestedSession, tab });
1365
+ if (!result.ok)
1366
+ return json(req, res, 502, result);
1367
+ // AXPress only proves the button accepted a click. Conductor then fetches the
1368
+ // target and changes the worktree asynchronously, so the branch column is the
1369
+ // receipt. A generous wait covers the fetch without ever writing that column.
1370
+ let continued = reads.getWorkspace(workspaceId);
1371
+ for (let i = 0; i < 60 && (!continued?.branch || continued.branch === previousBranch); i++) {
1372
+ await sleep(500);
1373
+ continued = reads.getWorkspace(workspaceId);
1374
+ }
1375
+ if (!continued?.branch || continued.branch === previousBranch) {
1376
+ return json(req, res, 502, {
1377
+ ok: false,
1378
+ strategy: result.strategy,
1379
+ error: 'Conductor did not record a new branch within 30 seconds. Check it on your Mac before retrying.'
1380
+ });
1381
+ }
1382
+ return json(req, res, 200, { ok: true, previousBranch, workspace: continued });
1383
+ }
1334
1384
  // POST /api/workspaces/:id/status { status } — move it between the sidebar's status groups.
1335
1385
  // Conductor derives that status from a PR it sometimes never links (a PR merged inside its
1336
1386
  // poll window is invisible to it afterwards), which strands finished work in "In progress"
@@ -706,6 +706,43 @@ return "ok"`.trim();
706
706
  return { ok: false, strategy: 'applescript', error: osaError(err) };
707
707
  }
708
708
  }
709
+ /**
710
+ * Continue a merged workspace on a new branch through Conductor's own Continue
711
+ * button. This deliberately does not perform `git checkout` itself: the native
712
+ * action also updates Conductor's workspace identity, preserves the existing
713
+ * sessions, clears the merged PR metadata, and stages its Branch continued.md
714
+ * context in the selected chat. Reproducing only the git half would leave the DB
715
+ * and UI describing the branch we just moved away from; writing the DB is forbidden.
716
+ *
717
+ * The exact chat matters even though every chat survives. Conductor attaches the
718
+ * continuation note to the selected one, so the target carries the phone's current
719
+ * session and `selectChatTab` asserts it before the button is found. The button is
720
+ * available only in Conductor's merged action bar; an absent or ambiguous control
721
+ * fails closed. server.ts confirms success from the resulting branch change.
722
+ */
723
+ export async function continueWorkspace(target) {
724
+ if (!target.workspace.branch) {
725
+ return { ok: false, strategy: 'applescript', error: 'workspace has no branch to focus' };
726
+ }
727
+ const script = `
728
+ ${CONDUCTOR_HANDLERS}
729
+
730
+ my activateConductor()
731
+ my focusWorkspace()
732
+ my selectChatTab()
733
+ my continueWorkspaceOnNewBranch()
734
+ return "ok"`.trim();
735
+ try {
736
+ await withTargetEnvironment(target, targetEnvironment => uiTurn(() => exec('osascript', ['-e', script], {
737
+ env: { ...process.env, ...targetEnvironment },
738
+ timeout: SEND_ATTEMPT_MS
739
+ })));
740
+ return { ok: true, strategy: 'applescript' };
741
+ }
742
+ catch (err) {
743
+ return { ok: false, strategy: 'applescript', error: osaError(err) };
744
+ }
745
+ }
709
746
  /**
710
747
  * Quit Conductor and start it again because the phone asked — the one write here
711
748
  * whose subject is the app rather than anything inside it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.91.0",
3
+ "version": "1.92.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.",