conductor-remote 1.32.0 → 1.33.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-C0YbLkns.js +41 -0
- package/dist/assets/index-DUP290RU.css +1 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/src/reads.js +21 -6
- package/dist-node/src/server.js +164 -38
- package/dist-node/src/writes.js +309 -27
- package/package.json +1 -1
- package/dist/assets/index-CqXxJtQ2.js +0 -41
- package/dist/assets/index-DwrzkcPG.css +0 -1
package/dist-node/src/writes.js
CHANGED
|
@@ -25,6 +25,55 @@ function uiTurn(op) {
|
|
|
25
25
|
uiTail = turn.catch(() => undefined);
|
|
26
26
|
return turn;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* How long one AppleScript run may take before it's killed.
|
|
30
|
+
*
|
|
31
|
+
* Sized from measurement, not taste. A send that *worked* measured 23.6s end to
|
|
32
|
+
* end on a 30-workspace sidebar — past the 20s ceiling this replaces, which is why
|
|
33
|
+
* ordinary sends were being killed mid-run and reported as "Conductor took too long
|
|
34
|
+
* to respond". The cost is Accessibility round trips, not waiting: activating a
|
|
35
|
+
* backgrounded Conductor and reading the pane cost ~10s cold, and finding the
|
|
36
|
+
* sidebar row to press another ~10s (see `atTargetWorkspace`, which is why a warm
|
|
37
|
+
* send is now ~2s). A ceiling costs nothing when a send is fast — only a doomed one
|
|
38
|
+
* waits it out, and the caller's own deadline is what bounds that.
|
|
39
|
+
*/
|
|
40
|
+
export const SEND_ATTEMPT_MS = 28_000;
|
|
41
|
+
/**
|
|
42
|
+
* A run's own ceiling, taken off the caller's deadline at the moment it actually
|
|
43
|
+
* starts.
|
|
44
|
+
*
|
|
45
|
+
* Both halves matter. `uiTurn` above means a run can sit in the queue behind
|
|
46
|
+
* another write, so a duration computed when it was *requested* would let a queued
|
|
47
|
+
* run overshoot a deadline the caller is still holding a phone open on.
|
|
48
|
+
* `SEND_ATTEMPT_MS` then caps it, because a caller with a minute of budget still
|
|
49
|
+
* shouldn't spend all of it on one doomed run when a retry is the thing that works.
|
|
50
|
+
* The floor keeps a squeezed run honest instead of passing `timeout: 0`, which node
|
|
51
|
+
* reads as "no timeout at all".
|
|
52
|
+
*/
|
|
53
|
+
function runCeiling(deadline) {
|
|
54
|
+
return Math.max(5_000, Math.min(SEND_ATTEMPT_MS, deadline - Date.now()));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Failures no amount of retrying will fix, so a caller that retries can stop at
|
|
58
|
+
* once instead of spending a whole budget to arrive at the same sentence.
|
|
59
|
+
*
|
|
60
|
+
* Matched on phrases this file writes itself — the first two from `refusalReason`,
|
|
61
|
+
* the rest from the target checks below — never on macOS's own wording, which we
|
|
62
|
+
* quote verbatim precisely because it drifts. The refusals are the ones a node
|
|
63
|
+
* upgrade causes by silently revoking Accessibility: they fail instantly and
|
|
64
|
+
* identically every time, so making the phone sit through a whole retry budget to
|
|
65
|
+
* be told a permission is missing is worse than being told at once. The rest are
|
|
66
|
+
* malformed targets — no session, no branch — which no attempt can turn into one.
|
|
67
|
+
*/
|
|
68
|
+
const TERMINAL_ERRORS = [
|
|
69
|
+
'not trusted for Accessibility',
|
|
70
|
+
'blocked the relay from controlling the UI',
|
|
71
|
+
'no session id to target',
|
|
72
|
+
'workspace has no branch to focus'
|
|
73
|
+
];
|
|
74
|
+
export function retryWontHelp(error) {
|
|
75
|
+
return error !== undefined && TERMINAL_ERRORS.some(phrase => error.includes(phrase));
|
|
76
|
+
}
|
|
28
77
|
/**
|
|
29
78
|
* The sidecar IPC path — the precise, per-session write. Delivers straight to
|
|
30
79
|
* `sessionId` over Conductor's own dispatch socket (see sidecar.ts), so it needs
|
|
@@ -42,7 +91,8 @@ export class SidecarActuator {
|
|
|
42
91
|
available() {
|
|
43
92
|
return sidecarAvailable();
|
|
44
93
|
}
|
|
45
|
-
|
|
94
|
+
/** `deadline` is ignored — the sidecar is one socket write, with no UI to wait on. */
|
|
95
|
+
async send(target, text, _deadline) {
|
|
46
96
|
const sessionId = target.sessionId ?? target.workspace.active_session_id;
|
|
47
97
|
if (!sessionId)
|
|
48
98
|
return { ok: false, strategy: this.name, error: 'no session id to target' };
|
|
@@ -333,34 +383,39 @@ on sidebarLinks()
|
|
|
333
383
|
end tell
|
|
334
384
|
end sidebarLinks
|
|
335
385
|
|
|
336
|
-
on
|
|
337
|
-
--
|
|
338
|
-
--
|
|
339
|
-
--
|
|
340
|
-
--
|
|
341
|
-
-- unique hit — anything ambiguous falls back to the palette. Being wrong is
|
|
342
|
-
-- survivable here: assertWorkspace re-checks before we type.
|
|
386
|
+
on findSidebarRow()
|
|
387
|
+
-- The workspace's own sidebar row. Only rows that are actually rendered exist
|
|
388
|
+
-- in the AX tree (a collapsed section has none), and the title follows
|
|
389
|
+
-- Conductor's precedence, so we try each candidate and require a *unique* hit —
|
|
390
|
+
-- anything ambiguous returns nothing rather than guessing at a neighbour.
|
|
343
391
|
set titles to my splitLines(system attribute "RELAY_WS_TITLES")
|
|
344
|
-
if (count of titles) is 0 then return
|
|
392
|
+
if (count of titles) is 0 then return missing value
|
|
345
393
|
set repoName to system attribute "RELAY_WS_REPO"
|
|
346
394
|
set rows to my sidebarLinks()
|
|
347
|
-
set hit to missing value
|
|
348
395
|
repeat with candidate in titles
|
|
349
396
|
if (candidate as text) is not "" then
|
|
350
397
|
set matches to {}
|
|
351
398
|
repeat with entry in rows
|
|
352
399
|
set row to contents of entry
|
|
353
|
-
|
|
400
|
+
-- Guarded read: the sidebar re-renders constantly (every commit
|
|
401
|
+
-- changes a row's "+adds -dels"), and one row that goes stale
|
|
402
|
+
-- mid-scan must not take the whole search down with it.
|
|
403
|
+
set rowName to my axName(row)
|
|
354
404
|
if rowName contains (candidate as text) then
|
|
355
405
|
if repoName is "" or rowName contains repoName then set end of matches to row
|
|
356
406
|
end if
|
|
357
407
|
end repeat
|
|
358
|
-
if (count of matches) is 1 then
|
|
359
|
-
set hit to item 1 of matches
|
|
360
|
-
exit repeat
|
|
361
|
-
end if
|
|
408
|
+
if (count of matches) is 1 then return item 1 of matches
|
|
362
409
|
end if
|
|
363
410
|
end repeat
|
|
411
|
+
return missing value
|
|
412
|
+
end findSidebarRow
|
|
413
|
+
|
|
414
|
+
on focusViaSidebar()
|
|
415
|
+
-- Press that row: no keystrokes at all, so nothing can be swallowed by a
|
|
416
|
+
-- focused field or fire a palette *command*. Being wrong is survivable here —
|
|
417
|
+
-- assertWorkspace re-checks before we type — and a miss falls back to the palette.
|
|
418
|
+
set hit to my findSidebarRow()
|
|
364
419
|
if hit is missing value then return false
|
|
365
420
|
tell application "System Events" to tell process "Conductor"
|
|
366
421
|
perform action "AXPress" of hit
|
|
@@ -382,16 +437,35 @@ on focusViaPalette()
|
|
|
382
437
|
end tell
|
|
383
438
|
end focusViaPalette
|
|
384
439
|
|
|
440
|
+
on paneAgrees()
|
|
441
|
+
-- Nothing in the open pane contradicts the target: a chat pane is there and its
|
|
442
|
+
-- header assertion passed (which is a no-op when there's no branch to check it
|
|
443
|
+
-- against). This is what confirms a sidebar press landed.
|
|
444
|
+
try
|
|
445
|
+
set strips to my tabGroups()
|
|
446
|
+
if (count of strips) is 0 then return false
|
|
447
|
+
my assertWorkspace(item 1 of strips)
|
|
448
|
+
return true
|
|
449
|
+
end try
|
|
450
|
+
return false
|
|
451
|
+
end paneAgrees
|
|
452
|
+
|
|
453
|
+
on atTargetWorkspace()
|
|
454
|
+
-- "Are we already there?" — the *strict* form of the question, so it needs a
|
|
455
|
+
-- branch to have been checked and not merely skipped. Worth asking first because
|
|
456
|
+
-- the pane header answers in ~0.5s where reading the sidebar's rows to press one
|
|
457
|
+
-- measured 5-7s, the single largest cost in a send: a phone sends to the same
|
|
458
|
+
-- workspace over and over, and Conductor stays where it was left. Safe as a
|
|
459
|
+
-- shortcut because it *is* the assertion the send makes before typing anyway.
|
|
460
|
+
if (system attribute "RELAY_WS_BRANCH") is "" then return false
|
|
461
|
+
return my paneAgrees()
|
|
462
|
+
end atTargetWorkspace
|
|
463
|
+
|
|
385
464
|
on focusWorkspace()
|
|
386
465
|
if (system attribute "RELAY_WS_QUERY") is "" then return
|
|
466
|
+
if my atTargetWorkspace() then return
|
|
387
467
|
if my focusViaSidebar() then
|
|
388
|
-
|
|
389
|
-
set strips to my tabGroups()
|
|
390
|
-
if (count of strips) > 0 then
|
|
391
|
-
my assertWorkspace(item 1 of strips)
|
|
392
|
-
return
|
|
393
|
-
end if
|
|
394
|
-
end try
|
|
468
|
+
if my paneAgrees() then return
|
|
395
469
|
end if
|
|
396
470
|
my focusViaPalette()
|
|
397
471
|
end focusWorkspace
|
|
@@ -799,7 +873,164 @@ on applyAgentOptions()
|
|
|
799
873
|
if wantEffort is not "" then my setEffort(wantEffort)
|
|
800
874
|
if wantPlan is not "" then my setPlan(wantPlan)
|
|
801
875
|
if wantFast is "1" then my pressFast()
|
|
802
|
-
end applyAgentOptions
|
|
876
|
+
end applyAgentOptions
|
|
877
|
+
|
|
878
|
+
on axRole(el)
|
|
879
|
+
tell application "System Events" to tell process "Conductor"
|
|
880
|
+
try
|
|
881
|
+
return role of el
|
|
882
|
+
on error
|
|
883
|
+
return ""
|
|
884
|
+
end try
|
|
885
|
+
end tell
|
|
886
|
+
end axRole
|
|
887
|
+
|
|
888
|
+
on axKids(el)
|
|
889
|
+
tell application "System Events" to tell process "Conductor"
|
|
890
|
+
try
|
|
891
|
+
return UI elements of el
|
|
892
|
+
on error
|
|
893
|
+
return {}
|
|
894
|
+
end try
|
|
895
|
+
end tell
|
|
896
|
+
end axKids
|
|
897
|
+
|
|
898
|
+
on menusUnder(root, maxDepth)
|
|
899
|
+
-- Every AXMenu at or below root, breadth-first and bounded. The status
|
|
900
|
+
-- submenu opens *nested inside* the row menu rather than beside it, so a
|
|
901
|
+
-- search that stops at the first hit (the way setModel scans the web area's
|
|
902
|
+
-- direct children) never sees it.
|
|
903
|
+
set level to {root}
|
|
904
|
+
set found to {}
|
|
905
|
+
set depth to 0
|
|
906
|
+
repeat while (count of level) > 0 and depth < maxDepth
|
|
907
|
+
set nextLevel to {}
|
|
908
|
+
repeat with entry in level
|
|
909
|
+
set node to contents of entry
|
|
910
|
+
if (my axRole(node)) is "AXMenu" then set end of found to node
|
|
911
|
+
set nextLevel to nextLevel & (my axKids(node))
|
|
912
|
+
end repeat
|
|
913
|
+
set level to nextLevel
|
|
914
|
+
set depth to depth + 1
|
|
915
|
+
end repeat
|
|
916
|
+
return found
|
|
917
|
+
end menusUnder
|
|
918
|
+
|
|
919
|
+
on axName(el)
|
|
920
|
+
-- Guarded: a menu re-renders under us, and reading the name of an element that
|
|
921
|
+
-- has since gone throws an object-specifier error from wherever we happened to be.
|
|
922
|
+
tell application "System Events" to tell process "Conductor"
|
|
923
|
+
try
|
|
924
|
+
set n to name of el
|
|
925
|
+
if n is missing value then return ""
|
|
926
|
+
return n as text
|
|
927
|
+
on error
|
|
928
|
+
return ""
|
|
929
|
+
end try
|
|
930
|
+
end tell
|
|
931
|
+
end axName
|
|
932
|
+
|
|
933
|
+
on menuItemNamed(m, wanted)
|
|
934
|
+
repeat with mi in (my axKids(m))
|
|
935
|
+
set node to contents of mi
|
|
936
|
+
if (my axRole(node)) is "AXMenuItem" and (my axName(node)) is wanted then return node
|
|
937
|
+
end repeat
|
|
938
|
+
return missing value
|
|
939
|
+
end menuItemNamed
|
|
940
|
+
|
|
941
|
+
on waitForMenuWith(root, maxDepth, itemName, attempts)
|
|
942
|
+
-- Identify the menu by an item it *contains*, never as "the first AXMenu on
|
|
943
|
+
-- screen": a model picker left open, or a menu from the run before, is also an
|
|
944
|
+
-- AXMenu, and picking it produced "no Set status item" on a menu that had never
|
|
945
|
+
-- been the workspace's. A menu is drawn by the webview, not by us, so how long
|
|
946
|
+
-- it takes varies with what Conductor is busy doing — poll rather than guess
|
|
947
|
+
-- one delay; the common case costs a single sweep.
|
|
948
|
+
repeat with i from 1 to attempts
|
|
949
|
+
repeat with entry in my menusUnder(root, maxDepth)
|
|
950
|
+
set node to contents of entry
|
|
951
|
+
if (my menuItemNamed(node, itemName)) is not missing value then return node
|
|
952
|
+
end repeat
|
|
953
|
+
delay 0.5
|
|
954
|
+
end repeat
|
|
955
|
+
return missing value
|
|
956
|
+
end waitForMenuWith
|
|
957
|
+
|
|
958
|
+
on dismissMenus()
|
|
959
|
+
-- Two escapes: one for the submenu, one for the row menu. Leaving either open
|
|
960
|
+
-- would swallow the next run's keystrokes.
|
|
961
|
+
tell application "System Events"
|
|
962
|
+
key code 53
|
|
963
|
+
delay 0.25
|
|
964
|
+
key code 53
|
|
965
|
+
end tell
|
|
966
|
+
end dismissMenus
|
|
967
|
+
|
|
968
|
+
on setWorkspaceStatus()
|
|
969
|
+
-- Conductor has no menu-bar or palette command for this, so the only lever is
|
|
970
|
+
-- the sidebar row's own context menu (Mark as unread / Pin / Set status /
|
|
971
|
+
-- Rename / Copy link / Archive). Right-clicking the row needs no focus change,
|
|
972
|
+
-- so unlike a send this never disturbs which workspace is on screen.
|
|
973
|
+
set wanted to system attribute "RELAY_SET_STATUS"
|
|
974
|
+
if wanted is "" then error "no status requested"
|
|
975
|
+
set theRow to my findSidebarRow()
|
|
976
|
+
if theRow is missing value then
|
|
977
|
+
error "couldn't find this workspace in the sidebar — a collapsed section hides its row from Accessibility"
|
|
978
|
+
end if
|
|
979
|
+
-- Scroll it into view first. A row that exists in the AX tree but sits outside
|
|
980
|
+
-- the sidebar's visible strip accepts AXShowMenu and draws nothing — which is
|
|
981
|
+
-- exactly what happens right after a status change moves it to another group.
|
|
982
|
+
tell application "System Events" to tell process "Conductor"
|
|
983
|
+
try
|
|
984
|
+
perform action "AXScrollToVisible" of theRow
|
|
985
|
+
end try
|
|
986
|
+
end tell
|
|
987
|
+
delay 0.4
|
|
988
|
+
-- Clear anything already open (a picker, or a menu a previous run left behind)
|
|
989
|
+
-- so the sweep below can only match the one this right-click draws.
|
|
990
|
+
tell application "System Events" to key code 53
|
|
991
|
+
delay 0.3
|
|
992
|
+
tell application "System Events" to tell process "Conductor"
|
|
993
|
+
perform action "AXShowMenu" of theRow
|
|
994
|
+
end tell
|
|
995
|
+
-- Depth 4, not the whole tree: the row menu is a portal near the top of the web
|
|
996
|
+
-- area, and the *transcript* hangs off that same root — a deep sweep walks every
|
|
997
|
+
-- message bubble and costs more than the rest of this write put together (it
|
|
998
|
+
-- grows with the conversation, so it degrades as you use the app).
|
|
999
|
+
set rowMenu to my waitForMenuWith(my webArea(), 4, "Set status", 6)
|
|
1000
|
+
if rowMenu is missing value then
|
|
1001
|
+
my dismissMenus()
|
|
1002
|
+
error "the workspace's menu didn't open — or it no longer offers Set status"
|
|
1003
|
+
end if
|
|
1004
|
+
set statusItem to my menuItemNamed(rowMenu, "Set status")
|
|
1005
|
+
tell application "System Events" to tell process "Conductor"
|
|
1006
|
+
perform action "AXPress" of statusItem
|
|
1007
|
+
end tell
|
|
1008
|
+
-- The submenu is an AXMenu nested a few levels *inside* the row menu (Conductor
|
|
1009
|
+
-- expands it in place rather than opening a sibling), so search from that menu —
|
|
1010
|
+
-- searching the web area again would re-walk the world. Two things make this a
|
|
1011
|
+
-- poll rather than a read: the row-menu handle goes stale across the expansion
|
|
1012
|
+
-- (it reports no children instead of erroring), so it is re-found each pass; and
|
|
1013
|
+
-- the submenu's *items* arrive a beat after the submenu itself, so the thing we
|
|
1014
|
+
-- wait for is the wanted status, not the container that will hold it.
|
|
1015
|
+
set subMenu to missing value
|
|
1016
|
+
repeat with attempt from 1 to 8
|
|
1017
|
+
set liveMenu to my waitForMenuWith(my webArea(), 4, "Set status", 1)
|
|
1018
|
+
if liveMenu is not missing value then
|
|
1019
|
+
set subMenu to my waitForMenuWith(liveMenu, 5, wanted, 1)
|
|
1020
|
+
end if
|
|
1021
|
+
if subMenu is not missing value then exit repeat
|
|
1022
|
+
delay 0.4
|
|
1023
|
+
end repeat
|
|
1024
|
+
if subMenu is missing value then
|
|
1025
|
+
my dismissMenus()
|
|
1026
|
+
error "Conductor never offered a status called " & wanted
|
|
1027
|
+
end if
|
|
1028
|
+
set choice to my menuItemNamed(subMenu, wanted)
|
|
1029
|
+
tell application "System Events" to tell process "Conductor"
|
|
1030
|
+
perform action "AXPress" of choice
|
|
1031
|
+
end tell
|
|
1032
|
+
delay 0.6
|
|
1033
|
+
end setWorkspaceStatus`;
|
|
803
1034
|
/** Conductor's command palette matches workspaces by branch — its unique key. A
|
|
804
1035
|
* looser query (directory name) can match a command like unarchive, so prefer
|
|
805
1036
|
* branch and only fall back when it's absent. */
|
|
@@ -884,7 +1115,7 @@ export class AppleScriptActuator {
|
|
|
884
1115
|
name = 'applescript';
|
|
885
1116
|
caveat = 'Focuses the target workspace (Cmd+K) and its chat tab before sending.';
|
|
886
1117
|
precise = true;
|
|
887
|
-
async send(target, text) {
|
|
1118
|
+
async send(target, text, deadline = Date.now() + SEND_ATTEMPT_MS) {
|
|
888
1119
|
// Focus the target workspace, select its chat tab, fill the composer, send.
|
|
889
1120
|
// Filling is an Accessibility write (no keystrokes, no clipboard); the
|
|
890
1121
|
// clipboard paste is kept only as a fallback, and stashes/restores around it.
|
|
@@ -914,7 +1145,7 @@ end tell
|
|
|
914
1145
|
try {
|
|
915
1146
|
await uiTurn(() => exec('osascript', ['-e', script], {
|
|
916
1147
|
env: { ...process.env, RELAY_PROMPT_FILE: tmp, ...targetEnv(target) },
|
|
917
|
-
timeout:
|
|
1148
|
+
timeout: runCeiling(deadline)
|
|
918
1149
|
}));
|
|
919
1150
|
return { ok: true, strategy: this.name };
|
|
920
1151
|
}
|
|
@@ -968,6 +1199,55 @@ return "ok"`.trim();
|
|
|
968
1199
|
RELAY_SET_FAST: opts.toggleFast ? '1' : '',
|
|
969
1200
|
RELAY_SET_MODEL: opts.model ?? ''
|
|
970
1201
|
},
|
|
1202
|
+
timeout: SEND_ATTEMPT_MS
|
|
1203
|
+
}));
|
|
1204
|
+
return { ok: true, strategy: 'applescript' };
|
|
1205
|
+
}
|
|
1206
|
+
catch (err) {
|
|
1207
|
+
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* The workspace statuses Conductor's sidebar groups by, mapped from the value it
|
|
1212
|
+
* stores in `workspaces.manual_status` to the label on its own menu. `canceled`
|
|
1213
|
+
* is on the menu but has never been written in this DB, so it's the one spelling
|
|
1214
|
+
* here that is taken from the UI rather than confirmed against stored data — a
|
|
1215
|
+
* mismatch surfaces as a failed confirmation naming what Conductor actually wrote.
|
|
1216
|
+
*/
|
|
1217
|
+
export const WORKSPACE_STATUS_LABELS = {
|
|
1218
|
+
backlog: 'Backlog',
|
|
1219
|
+
'in-progress': 'In progress',
|
|
1220
|
+
'in-review': 'In review',
|
|
1221
|
+
done: 'Done',
|
|
1222
|
+
canceled: 'Canceled'
|
|
1223
|
+
};
|
|
1224
|
+
/**
|
|
1225
|
+
* Move a workspace between the sidebar's status groups — the thing a merged PR
|
|
1226
|
+
* that Conductor never linked can't do for itself.
|
|
1227
|
+
*
|
|
1228
|
+
* Unlike every other write here this one never changes what's on screen: it
|
|
1229
|
+
* right-clicks the workspace's *row* (AXShowMenu) and works the menu, so the
|
|
1230
|
+
* workspace you were reading stays open. It does need the row to be rendered,
|
|
1231
|
+
* which a collapsed sidebar section prevents — that case is reported in words
|
|
1232
|
+
* rather than guessed around, because there is no palette command to fall back to.
|
|
1233
|
+
*/
|
|
1234
|
+
export async function setWorkspaceStatus(workspace, status) {
|
|
1235
|
+
const label = WORKSPACE_STATUS_LABELS[status];
|
|
1236
|
+
if (!label)
|
|
1237
|
+
return { ok: false, strategy: 'applescript', error: `unknown status ${status}` };
|
|
1238
|
+
const script = `
|
|
1239
|
+
${SELECT_CHAT_TAB_HANDLERS}
|
|
1240
|
+
|
|
1241
|
+
my activateConductor()
|
|
1242
|
+
my setWorkspaceStatus()
|
|
1243
|
+
return "ok"`.trim();
|
|
1244
|
+
try {
|
|
1245
|
+
await uiTurn(() => exec('osascript', ['-e', script], {
|
|
1246
|
+
env: {
|
|
1247
|
+
...process.env,
|
|
1248
|
+
...targetEnv({ workspace, sessionId: null }),
|
|
1249
|
+
RELAY_SET_STATUS: label
|
|
1250
|
+
},
|
|
971
1251
|
timeout: 25000
|
|
972
1252
|
}));
|
|
973
1253
|
return { ok: true, strategy: 'applescript' };
|
|
@@ -988,7 +1268,7 @@ return my listModels()`.trim();
|
|
|
988
1268
|
try {
|
|
989
1269
|
const { stdout } = await uiTurn(() => exec('osascript', ['-e', script], {
|
|
990
1270
|
env: { ...process.env, ...targetEnv(target) },
|
|
991
|
-
timeout:
|
|
1271
|
+
timeout: SEND_ATTEMPT_MS
|
|
992
1272
|
}));
|
|
993
1273
|
const models = stdout
|
|
994
1274
|
.split('\n')
|
|
@@ -1060,9 +1340,11 @@ tell application "System Events"
|
|
|
1060
1340
|
keystroke "t" using {command down}
|
|
1061
1341
|
end tell`.trim();
|
|
1062
1342
|
try {
|
|
1343
|
+
// Shares the focus path with a send, so it needs the same ceiling: 15s was under
|
|
1344
|
+
// the cost of activating a cold Conductor and finding the row on its own.
|
|
1063
1345
|
await uiTurn(() => exec('osascript', ['-e', script], {
|
|
1064
1346
|
env: { ...process.env, ...targetEnv({ workspace, sessionId: null }) },
|
|
1065
|
-
timeout:
|
|
1347
|
+
timeout: SEND_ATTEMPT_MS
|
|
1066
1348
|
}));
|
|
1067
1349
|
return { ok: true, strategy: 'applescript' };
|
|
1068
1350
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.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.",
|