conductor-remote 1.34.1 → 1.34.2
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-BvDsOp0S.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index--7Dp2aq8.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
|
|
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:"890f6a70e97d144f4d025254e90892a1"},{url:"index.html",revision:"3ba36ccccb232cf75c6793b748ad42c3"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-BvDsOp0S.js",revision:null},{url:"assets/index--7Dp2aq8.css",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/writes.js
CHANGED
|
@@ -33,9 +33,13 @@ function uiTurn(op) {
|
|
|
33
33
|
* ordinary sends were being killed mid-run and reported as "Conductor took too long
|
|
34
34
|
* to respond". The cost is Accessibility round trips, not waiting: activating a
|
|
35
35
|
* backgrounded Conductor and reading the pane cost ~10s cold, and finding the
|
|
36
|
-
* sidebar row to press another ~10s
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* sidebar row to press another ~10s.
|
|
37
|
+
*
|
|
38
|
+
* `openViaDeepLink` took the row scan out of the common path — a whole send now
|
|
39
|
+
* measures ~4s, and focusing alone ~2s against the ~18s the same focus costs when
|
|
40
|
+
* the link is unavailable — so this budget is really the *fallback's*, kept at the
|
|
41
|
+
* size that fallback still needs. A ceiling costs nothing when a send is fast; only
|
|
42
|
+
* a doomed one waits it out, and the caller's own deadline is what bounds that.
|
|
39
43
|
*/
|
|
40
44
|
export const SEND_ATTEMPT_MS = 28_000;
|
|
41
45
|
/**
|
|
@@ -108,9 +112,11 @@ export class SidecarActuator {
|
|
|
108
112
|
/**
|
|
109
113
|
* AppleScript handlers that pin down *which chat* the prompt goes to.
|
|
110
114
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
115
|
+
* `openViaDeepLink` normally selects it already, by id — these handlers are what
|
|
116
|
+
* *confirm* that, and what still does the work for the fallback focus paths,
|
|
117
|
+
* which only get us to the right workspace. A workspace holds several chats and
|
|
118
|
+
* Conductor keeps typing in whichever tab is already active, so a send addressed
|
|
119
|
+
* to a non-active chat would land in the wrong agent.
|
|
114
120
|
*
|
|
115
121
|
* Conductor's webview exposes the whole tree through macOS Accessibility. The
|
|
116
122
|
* chat strip is an AXTabGroup whose AXRadioButtons are the tabs (`AXValue`
|
|
@@ -118,7 +124,14 @@ export class SidecarActuator {
|
|
|
118
124
|
* chat). The strip's order matches `reads.listSessions` (created_at ASC), so
|
|
119
125
|
* the caller addresses a tab by 1-based index and we cross-check the label.
|
|
120
126
|
*
|
|
121
|
-
*
|
|
127
|
+
* Three traps this has to survive:
|
|
128
|
+
* - **The tabs are not the strip's direct children.** Each sits one AXGroup
|
|
129
|
+
* deep and is named for its own close action ("Close chat <title>"), while a
|
|
130
|
+
* "Close file view" radio button — not a chat — *is* a direct child. Reading
|
|
131
|
+
* the direct children and stopping there made every chat strip look like a
|
|
132
|
+
* one-tab strip called "Close file view", which then lost the scoring below to
|
|
133
|
+
* the terminal panel and failed every send with "chat tab 1 not found". So
|
|
134
|
+
* `stripRadios` reads both levels and `chatTabs` keeps the "Close chat" ones.
|
|
122
135
|
* - **The terminal panel is an AXTabGroup too** (radio buttons named Setup /
|
|
123
136
|
* Run / Terminal 1), a sibling of the chat strip in the same pane. Picking
|
|
124
137
|
* "the first tab group" would sometimes press a terminal tab, so we *score*
|
|
@@ -461,8 +474,44 @@ on atTargetWorkspace()
|
|
|
461
474
|
return my paneAgrees()
|
|
462
475
|
end atTargetWorkspace
|
|
463
476
|
|
|
477
|
+
on openViaDeepLink()
|
|
478
|
+
-- Conductor's *own* workspace link, the one its sidebar row menu copies under
|
|
479
|
+
-- "Copy link" (Cmd+Shift+C): one URL carrying the workspace id and the chat id,
|
|
480
|
+
-- addressed the way the relay already reads them rather than by any label a
|
|
481
|
+
-- release can rename. It replaces the entire focus dance below - no sidebar
|
|
482
|
+
-- scan, no palette keystrokes, no title precedence to reproduce - and measures
|
|
483
|
+
-- ~0.6s against the 5-10s the row press costs on a 30-workspace sidebar.
|
|
484
|
+
--
|
|
485
|
+
-- Two things it fixes outright rather than merely speeding up. It navigates by
|
|
486
|
+
-- *id*, so a collapsed sidebar section (which hides the row from Accessibility
|
|
487
|
+
-- entirely) stops mattering; and it opens the chat tab explicitly, so a pane
|
|
488
|
+
-- left on a file view or a terminal - where the chat strip does not exist in
|
|
489
|
+
-- the AX tree at all, and every send failed with "couldn't identify the chat
|
|
490
|
+
-- tab strip" - comes back to the composer on its own.
|
|
491
|
+
--
|
|
492
|
+
-- Still fail-closed: the URL is fire-and-forget, so what counts as success is
|
|
493
|
+
-- the same pane assertion every other path here has to pass. A Conductor
|
|
494
|
+
-- without the route (before 0.71) ignores it silently, and a scheme macOS
|
|
495
|
+
-- can't resolve fails the "open" outright - both fall through to the sidebar.
|
|
496
|
+
set linkURL to system attribute "RELAY_WS_LINK"
|
|
497
|
+
if linkURL is "" then return false
|
|
498
|
+
try
|
|
499
|
+
do shell script "open " & quoted form of linkURL
|
|
500
|
+
on error
|
|
501
|
+
return false
|
|
502
|
+
end try
|
|
503
|
+
-- Bounded on purpose: this poll is also the cost of *not* having the route, and
|
|
504
|
+
-- the fallback below still has to run inside the same budget afterwards.
|
|
505
|
+
repeat with attempt from 1 to 10
|
|
506
|
+
delay 0.15
|
|
507
|
+
if my paneAgrees() then return true
|
|
508
|
+
end repeat
|
|
509
|
+
return false
|
|
510
|
+
end openViaDeepLink
|
|
511
|
+
|
|
464
512
|
on focusWorkspace()
|
|
465
513
|
if (system attribute "RELAY_WS_QUERY") is "" then return
|
|
514
|
+
if my openViaDeepLink() then return
|
|
466
515
|
if my atTargetWorkspace() then return
|
|
467
516
|
if my focusViaSidebar() then
|
|
468
517
|
if my paneAgrees() then return
|
|
@@ -498,18 +547,46 @@ on tabGroups()
|
|
|
498
547
|
return {}
|
|
499
548
|
end tabGroups
|
|
500
549
|
|
|
501
|
-
on
|
|
550
|
+
on stripRadios(tg)
|
|
551
|
+
-- Every radio button in a tab strip, whether it is a direct child or sits one
|
|
552
|
+
-- group deep, in tree order. Both shapes are live at once: the terminal strip's
|
|
553
|
+
-- tabs are direct children, while a chat tab is wrapped in its own AXGroup with
|
|
554
|
+
-- the file-view toggle beside it as a plain child. Reading only the direct ones
|
|
555
|
+
-- (and stopping as soon as any turned up) is what made a chat strip look like a
|
|
556
|
+
-- one-tab strip called "Close file view".
|
|
502
557
|
tell application "System Events" to tell process "Conductor"
|
|
503
|
-
set
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
558
|
+
set acc to {}
|
|
559
|
+
repeat with k in (UI elements of tg)
|
|
560
|
+
set node to contents of k
|
|
561
|
+
if (role of node) is "AXRadioButton" then
|
|
562
|
+
set end of acc to node
|
|
563
|
+
else
|
|
564
|
+
try
|
|
565
|
+
repeat with r in (UI elements of node whose role is "AXRadioButton")
|
|
566
|
+
set end of acc to contents of r
|
|
567
|
+
end repeat
|
|
568
|
+
end try
|
|
569
|
+
end if
|
|
510
570
|
end repeat
|
|
511
|
-
return
|
|
571
|
+
return acc
|
|
512
572
|
end tell
|
|
573
|
+
end stripRadios
|
|
574
|
+
|
|
575
|
+
on chatTabs(tg)
|
|
576
|
+
-- Of those, a chat tab is the one whose accessible name is its own close action,
|
|
577
|
+
-- "Close chat <title>". The strip's other radio button is "Close file view",
|
|
578
|
+
-- which is not a chat and must not shift the indices this file addresses tabs
|
|
579
|
+
-- by — off by one is how a prompt lands in the wrong conversation. Falls back to
|
|
580
|
+
-- every radio button when none carries the prefix, so a renamed label costs the
|
|
581
|
+
-- filter rather than emptying the strip.
|
|
582
|
+
set radios to my stripRadios(tg)
|
|
583
|
+
set chats to {}
|
|
584
|
+
repeat with entry in radios
|
|
585
|
+
set node to contents of entry
|
|
586
|
+
if (my axName(node)) starts with "Close chat" then set end of chats to node
|
|
587
|
+
end repeat
|
|
588
|
+
if (count of chats) > 0 then return chats
|
|
589
|
+
return radios
|
|
513
590
|
end chatTabs
|
|
514
591
|
|
|
515
592
|
on tabLabel(t)
|
|
@@ -1031,6 +1108,46 @@ on setWorkspaceStatus()
|
|
|
1031
1108
|
end tell
|
|
1032
1109
|
delay 0.6
|
|
1033
1110
|
end setWorkspaceStatus`;
|
|
1111
|
+
/**
|
|
1112
|
+
* The URL scheme Conductor registers, which is per *release channel*: the
|
|
1113
|
+
* production build answers `conductor://`, the pre-release ones
|
|
1114
|
+
* `conductor-alpha://`, `conductor-beta://`, `conductor-dev://` and friends.
|
|
1115
|
+
* Everything else in this file addresses `application "Conductor"` by name, so
|
|
1116
|
+
* production is the only channel the write path works against anyway — the
|
|
1117
|
+
* override exists so a channel build needs a variable rather than a patch.
|
|
1118
|
+
*/
|
|
1119
|
+
const CONDUCTOR_SCHEME = process.env.RELAY_CONDUCTOR_SCHEME || 'conductor';
|
|
1120
|
+
/**
|
|
1121
|
+
* Conductor's own link to a workspace, and optionally to one chat inside it —
|
|
1122
|
+
* exactly what its sidebar row menu copies under "Copy link" (Cmd+Shift+C).
|
|
1123
|
+
*
|
|
1124
|
+
* `conductor://workspace?id=<workspace>&session=<chat>` is the shape that works,
|
|
1125
|
+
* and the near misses all fail *badly*, so this is the one place that builds it:
|
|
1126
|
+
* the parameters sit behind a real `?` (unlike the create-workspace links, which
|
|
1127
|
+
* are flat after the scheme), the workspace id is `id` rather than `workspace`,
|
|
1128
|
+
* and `workspace` must be the URL's **host** — `conductor:///workspace/<id>`,
|
|
1129
|
+
* with the id in the path, falls through to the flat-parameter parser and
|
|
1130
|
+
* **creates a new workspace in the first repo**. A path-shaped link with the
|
|
1131
|
+
* right host (`conductor://workspace/<id>`) is merely ignored.
|
|
1132
|
+
*
|
|
1133
|
+
* `session` is optional and names a chat by `sessions.id`, the same id the relay
|
|
1134
|
+
* already serves; omitted, Conductor opens whichever tab that workspace had.
|
|
1135
|
+
* A hidden chat (`sessions.is_hidden`) has no tab, and Conductor keeps the
|
|
1136
|
+
* workspace's current one rather than reporting anything, so the caller's own
|
|
1137
|
+
* tab assertion is still what catches it.
|
|
1138
|
+
*
|
|
1139
|
+
* The https form Conductor copies for sharing —
|
|
1140
|
+
* `https://app.conductor.build/workspace/<id>?session=<chat>` — reaches the same
|
|
1141
|
+
* handler, but only once macOS has decided to hand it to the app; the desktop
|
|
1142
|
+
* build declares no associated domain, so a browser gets it first. Locally the
|
|
1143
|
+
* scheme form is the one that always lands.
|
|
1144
|
+
*/
|
|
1145
|
+
function workspaceLink(workspaceId, sessionId) {
|
|
1146
|
+
const params = new URLSearchParams({ id: workspaceId });
|
|
1147
|
+
if (sessionId)
|
|
1148
|
+
params.set('session', sessionId);
|
|
1149
|
+
return `${CONDUCTOR_SCHEME}://workspace?${params.toString()}`;
|
|
1150
|
+
}
|
|
1034
1151
|
/** Conductor's command palette matches workspaces by branch — its unique key. A
|
|
1035
1152
|
* looser query (directory name) can match a command like unarchive, so prefer
|
|
1036
1153
|
* branch and only fall back when it's absent. */
|
|
@@ -1086,6 +1203,7 @@ function targetEnv(target) {
|
|
|
1086
1203
|
RELAY_WS_BRANCH: target.workspace.branch ?? '',
|
|
1087
1204
|
RELAY_WS_REPO: target.workspace.repo_name ?? '',
|
|
1088
1205
|
RELAY_WS_QUERY: focusQuery(target.workspace),
|
|
1206
|
+
RELAY_WS_LINK: workspaceLink(target.workspace.id, target.sessionId),
|
|
1089
1207
|
RELAY_WS_TITLES: sidebarTitles(target.workspace).join('\n')
|
|
1090
1208
|
};
|
|
1091
1209
|
}
|
|
@@ -1105,18 +1223,20 @@ function osaError(err) {
|
|
|
1105
1223
|
* permission mode the session already has (zero risk of altering the agent),
|
|
1106
1224
|
* which is why it's the default.
|
|
1107
1225
|
*
|
|
1108
|
-
* Precise targeting comes from
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
* lands in the right session regardless
|
|
1112
|
-
*
|
|
1226
|
+
* Precise targeting comes from opening Conductor's own workspace link first
|
|
1227
|
+
* (`conductor://workspace?id=…&session=…`, see `workspaceLink`) and then
|
|
1228
|
+
* confirming the pane and the chat tab through Accessibility (see
|
|
1229
|
+
* SELECT_CHAT_TAB_HANDLERS), so the prompt lands in the right session regardless
|
|
1230
|
+
* of what was focused. The link is public and id-addressed; the AX reads only
|
|
1231
|
+
* check it, and pressing the sidebar row or the command palette remains the
|
|
1232
|
+
* fallback for a Conductor that doesn't answer it.
|
|
1113
1233
|
*/
|
|
1114
1234
|
export class AppleScriptActuator {
|
|
1115
1235
|
name = 'applescript';
|
|
1116
|
-
caveat =
|
|
1236
|
+
caveat = "Opens the target workspace's own Conductor link, then confirms the chat tab before sending.";
|
|
1117
1237
|
precise = true;
|
|
1118
1238
|
async send(target, text, deadline = Date.now() + SEND_ATTEMPT_MS) {
|
|
1119
|
-
//
|
|
1239
|
+
// Open the target workspace's own link, confirm its chat tab, fill the composer, send.
|
|
1120
1240
|
// Filling is an Accessibility write (no keystrokes, no clipboard); the
|
|
1121
1241
|
// clipboard paste is kept only as a fallback, and stashes/restores around it.
|
|
1122
1242
|
const script = `
|
|
@@ -1316,7 +1436,7 @@ export async function createWorkspace(prompt, repoPath) {
|
|
|
1316
1436
|
try {
|
|
1317
1437
|
// Serialized with the AX writes: creating a workspace pulls Conductor forward and
|
|
1318
1438
|
// switches which one is showing, which is precisely what a concurrent send assumes.
|
|
1319
|
-
await uiTurn(() => exec('open', [
|
|
1439
|
+
await uiTurn(() => exec('open', [`${CONDUCTOR_SCHEME}://${query}`], { timeout: 15000 }));
|
|
1320
1440
|
return { ok: true, strategy: 'deeplink' };
|
|
1321
1441
|
}
|
|
1322
1442
|
catch (err) {
|
|
@@ -1325,8 +1445,8 @@ export async function createWorkspace(prompt, repoPath) {
|
|
|
1325
1445
|
}
|
|
1326
1446
|
/**
|
|
1327
1447
|
* Open a new chat in the target workspace — Conductor's "New chat, same files"
|
|
1328
|
-
* (Cmd+T). Focuses the workspace first (
|
|
1329
|
-
* caller detects the freshly-created session id from the DB.
|
|
1448
|
+
* (Cmd+T). Focuses the workspace first (its own link, see `workspaceLink`), then
|
|
1449
|
+
* Cmd+T; the caller detects the freshly-created session id from the DB.
|
|
1330
1450
|
*/
|
|
1331
1451
|
export async function newChat(workspace) {
|
|
1332
1452
|
if (!focusQuery(workspace))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.34.
|
|
3
|
+
"version": "1.34.2",
|
|
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.",
|