conductor-remote 1.32.1 → 1.34.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-BfvWGOfK.css +1 -0
- package/dist/assets/index-jfBrvANR.js +42 -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 +38 -1
- package/dist-node/src/writes.js +226 -15
- package/package.json +1 -1
- package/dist/assets/index-D883fUSq.js +0 -41
- package/dist/assets/index-DwrzkcPG.css +0 -1
package/dist/index.html
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
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-
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-jfBrvANR.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BfvWGOfK.css">
|
|
18
18
|
<link rel="manifest" href="/manifest.webmanifest"></head>
|
|
19
19
|
<body>
|
|
20
20
|
<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 l={};const
|
|
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 t=e=>i(e,o),c={module:{uri:o},exports:l,require:t};s[o]=Promise.all(n.map(e=>c[e]||t(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:"890f6a70e97d144f4d025254e90892a1"},{url:"index.html",revision:"3d3a6c1f7a0b0f8cb32564e5a1970d02"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-jfBrvANR.js",revision:null},{url:"assets/index-BfvWGOfK.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/reads.js
CHANGED
|
@@ -141,12 +141,27 @@ export class Reads {
|
|
|
141
141
|
}
|
|
142
142
|
listSessions(workspaceId) {
|
|
143
143
|
// created_at ASC keeps tab order stable (matches the desktop app) instead of jumping on activity.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
144
|
+
//
|
|
145
|
+
// `turn_started_at` is when the current answer began, which is NOT
|
|
146
|
+
// `last_user_message_at`: a message typed while the agent is already working is
|
|
147
|
+
// *steering* — it joins the running turn rather than starting one, and the phone's
|
|
148
|
+
// elapsed timer must not restart on it. Conductor separates the two itself.
|
|
149
|
+
// `session_messages.queue_order` is set exactly on the messages that head a turn
|
|
150
|
+
// (verified over the whole DB from May 2026, when the column appeared — older rows
|
|
151
|
+
// are all NULL, so a long-dormant chat reports null here and simply shows no timer),
|
|
152
|
+
// and `sent_at` is when that head was dispatched, so a prompt that sat in the queue
|
|
153
|
+
// times from when it actually ran, not from when it was typed. Still-queued heads
|
|
154
|
+
// have `sent_at` NULL and are skipped, which is why a queued message can't blip the
|
|
155
|
+
// timer while the previous answer is still going. Served straight off
|
|
156
|
+
// idx_session_messages_sent_at(session_id, sent_at) — measured free at this poll rate.
|
|
157
|
+
return this.db.query(`SELECT s.id, s.status, s.title, s.model, s.permission_mode, s.claude_effort_level, s.fast_mode, s.agent_type,
|
|
158
|
+
s.context_used_percent, s.unread_count,
|
|
159
|
+
s.created_at, s.updated_at, s.last_user_message_at,
|
|
160
|
+
(SELECT MAX(m.sent_at) FROM session_messages m
|
|
161
|
+
WHERE m.session_id = s.id AND m.queue_order IS NOT NULL AND m.sent_at IS NOT NULL) AS turn_started_at
|
|
162
|
+
FROM sessions s
|
|
163
|
+
WHERE s.workspace_id = ? AND COALESCE(s.is_hidden, 0) = 0
|
|
164
|
+
ORDER BY s.created_at ASC`, [workspaceId]);
|
|
150
165
|
}
|
|
151
166
|
/**
|
|
152
167
|
* Every live chat's status, across all workspaces — what the notifier watches for
|
package/dist-node/src/server.js
CHANGED
|
@@ -15,7 +15,7 @@ import { notifyDevice, pushConfig, startNotifier, subscribeDevice, unsubscribeDe
|
|
|
15
15
|
import { attachPrStatus } from "./pr.js";
|
|
16
16
|
import { Reads } from "./reads.js";
|
|
17
17
|
import { driftWarningLines, tailscaleBin } from "./tailscale.js";
|
|
18
|
-
import { createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, newChat, pickActuator, retryWontHelp, setAgentOptions, setRestartGuard } from "./writes.js";
|
|
18
|
+
import { createWorkspace, describeActuator, EFFORT_LABELS, listAgentModels, newChat, pickActuator, retryWontHelp, setAgentOptions, setRestartGuard, setWorkspaceStatus, WORKSPACE_STATUS_LABELS } from "./writes.js";
|
|
19
19
|
// Before anything that logs: from here on every console line is also kept in memory for
|
|
20
20
|
// `GET /api/logs`, so the phone can read why a send failed without ssh-ing into the Mac.
|
|
21
21
|
installLogCapture();
|
|
@@ -518,6 +518,43 @@ const server = http.createServer(async (req, res) => {
|
|
|
518
518
|
const result = await mergePr(ws);
|
|
519
519
|
return json(req, res, result.ok ? 200 : 409, result);
|
|
520
520
|
}
|
|
521
|
+
// POST /api/workspaces/:id/status { status } — move it between the sidebar's status groups.
|
|
522
|
+
// Conductor derives that status from a PR it sometimes never links (a PR merged inside its
|
|
523
|
+
// poll window is invisible to it afterwards), which strands finished work in "In progress"
|
|
524
|
+
// with no way to correct it from a phone. This is that way.
|
|
525
|
+
m = pathname.match(/^\/api\/workspaces\/([^/]+)\/status$/);
|
|
526
|
+
if (req.method === 'POST' && m) {
|
|
527
|
+
const workspaceId = decodeURIComponent(m[1]);
|
|
528
|
+
const body = JSON.parse((await readBody(req)) || '{}');
|
|
529
|
+
const status = body.status ?? '';
|
|
530
|
+
if (!WORKSPACE_STATUS_LABELS[status]) {
|
|
531
|
+
const allowed = Object.keys(WORKSPACE_STATUS_LABELS).join(', ');
|
|
532
|
+
return json(req, res, 400, { error: `status must be one of ${allowed}` });
|
|
533
|
+
}
|
|
534
|
+
const ws = reads.getWorkspace(workspaceId);
|
|
535
|
+
if (!ws)
|
|
536
|
+
return json(req, res, 404, { error: 'workspace not found' });
|
|
537
|
+
const result = await setWorkspaceStatus(ws, status);
|
|
538
|
+
if (!result.ok)
|
|
539
|
+
return json(req, res, 502, result);
|
|
540
|
+
// The menu press lands in the DB a beat later. Confirm rather than assume —
|
|
541
|
+
// and if Conductor wrote something else, say what, instead of "didn't work".
|
|
542
|
+
let observed = ws.manual_status ?? '';
|
|
543
|
+
for (let i = 0; i < 10 && observed !== status; i++) {
|
|
544
|
+
await new Promise(r => setTimeout(r, 300));
|
|
545
|
+
observed = reads.getWorkspace(workspaceId)?.manual_status ?? '';
|
|
546
|
+
}
|
|
547
|
+
if (observed !== status) {
|
|
548
|
+
return json(req, res, 502, {
|
|
549
|
+
ok: false,
|
|
550
|
+
strategy: result.strategy,
|
|
551
|
+
error: observed
|
|
552
|
+
? `Conductor recorded the status as “${observed}”, not “${status}”.`
|
|
553
|
+
: 'Conductor didn’t record the change — it may have been asleep. Try again.'
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return json(req, res, 200, { ok: true, workspace: reads.getWorkspace(workspaceId) });
|
|
557
|
+
}
|
|
521
558
|
// GET /api/sessions/:id/messages?after=<rowid>
|
|
522
559
|
m = pathname.match(/^\/api\/sessions\/([^/]+)\/messages$/);
|
|
523
560
|
if (req.method === 'GET' && m) {
|
package/dist-node/src/writes.js
CHANGED
|
@@ -383,34 +383,39 @@ on sidebarLinks()
|
|
|
383
383
|
end tell
|
|
384
384
|
end sidebarLinks
|
|
385
385
|
|
|
386
|
-
on
|
|
387
|
-
--
|
|
388
|
-
--
|
|
389
|
-
--
|
|
390
|
-
--
|
|
391
|
-
-- unique hit — anything ambiguous falls back to the palette. Being wrong is
|
|
392
|
-
-- 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.
|
|
393
391
|
set titles to my splitLines(system attribute "RELAY_WS_TITLES")
|
|
394
|
-
if (count of titles) is 0 then return
|
|
392
|
+
if (count of titles) is 0 then return missing value
|
|
395
393
|
set repoName to system attribute "RELAY_WS_REPO"
|
|
396
394
|
set rows to my sidebarLinks()
|
|
397
|
-
set hit to missing value
|
|
398
395
|
repeat with candidate in titles
|
|
399
396
|
if (candidate as text) is not "" then
|
|
400
397
|
set matches to {}
|
|
401
398
|
repeat with entry in rows
|
|
402
399
|
set row to contents of entry
|
|
403
|
-
|
|
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)
|
|
404
404
|
if rowName contains (candidate as text) then
|
|
405
405
|
if repoName is "" or rowName contains repoName then set end of matches to row
|
|
406
406
|
end if
|
|
407
407
|
end repeat
|
|
408
|
-
if (count of matches) is 1 then
|
|
409
|
-
set hit to item 1 of matches
|
|
410
|
-
exit repeat
|
|
411
|
-
end if
|
|
408
|
+
if (count of matches) is 1 then return item 1 of matches
|
|
412
409
|
end if
|
|
413
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()
|
|
414
419
|
if hit is missing value then return false
|
|
415
420
|
tell application "System Events" to tell process "Conductor"
|
|
416
421
|
perform action "AXPress" of hit
|
|
@@ -868,7 +873,164 @@ on applyAgentOptions()
|
|
|
868
873
|
if wantEffort is not "" then my setEffort(wantEffort)
|
|
869
874
|
if wantPlan is not "" then my setPlan(wantPlan)
|
|
870
875
|
if wantFast is "1" then my pressFast()
|
|
871
|
-
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`;
|
|
872
1034
|
/** Conductor's command palette matches workspaces by branch — its unique key. A
|
|
873
1035
|
* looser query (directory name) can match a command like unarchive, so prefer
|
|
874
1036
|
* branch and only fall back when it's absent. */
|
|
@@ -1045,6 +1207,55 @@ return "ok"`.trim();
|
|
|
1045
1207
|
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
1046
1208
|
}
|
|
1047
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
|
+
},
|
|
1251
|
+
timeout: 25000
|
|
1252
|
+
}));
|
|
1253
|
+
return { ok: true, strategy: 'applescript' };
|
|
1254
|
+
}
|
|
1255
|
+
catch (err) {
|
|
1256
|
+
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1048
1259
|
/** The model labels Conductor is currently offering, read off the live picker. */
|
|
1049
1260
|
export async function listAgentModels(target) {
|
|
1050
1261
|
const script = `
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.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.",
|