conductor-remote 1.88.0 → 1.89.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/README.md +9 -7
- package/dist/assets/{index-CDKjnPTR.css → index-Bo6T0C__.css} +1 -1
- package/dist/assets/index-E7UanAoE.js +48 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/src/conductor.applescript +63 -10
- package/dist-node/src/mcp-tools.js +36 -1
- package/dist-node/src/routes.js +2 -0
- package/dist-node/src/server.js +78 -1
- package/dist-node/src/writes.js +39 -0
- package/package.json +1 -1
- package/dist/assets/index-DniQHCcH.js +0 -48
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-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-E7UanAoE.js"></script>
|
|
29
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Bo6T0C__.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:"
|
|
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:"a0eac09ea3fd8b6248c5f20a6be9d3cb"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-E7UanAoE.js",revision:null},{url:"assets/index-Bo6T0C__.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\//]}))});
|
|
@@ -1272,6 +1272,44 @@ on cancelAgent()
|
|
|
1272
1272
|
delay 0.3
|
|
1273
1273
|
end cancelAgent
|
|
1274
1274
|
|
|
1275
|
+
on closeChatTab()
|
|
1276
|
+
-- Hide the selected chat through Conductor's own Close tab shortcut, Command-W.
|
|
1277
|
+
-- The same chord closes a terminal tab when the terminal has focus, so Command-L
|
|
1278
|
+
-- first is load-bearing: it moves focus into the chat whose workspace and tab were
|
|
1279
|
+
-- asserted immediately above this handler.
|
|
1280
|
+
set strips to my tabGroups()
|
|
1281
|
+
if (count of strips) is 0 then error "couldn't find the chat pane to close from"
|
|
1282
|
+
my assertWorkspace(item 1 of strips)
|
|
1283
|
+
tell application "System Events"
|
|
1284
|
+
keystroke "l" using {command down}
|
|
1285
|
+
delay 0.2
|
|
1286
|
+
keystroke "w" using {command down}
|
|
1287
|
+
end tell
|
|
1288
|
+
|
|
1289
|
+
-- Conductor asks "Close anyway" when the chat's agent is still running. The
|
|
1290
|
+
-- server normally catches that from sessions.status before touching the UI, but
|
|
1291
|
+
-- a turn can start in the gap between that read and this keystroke. Detect the
|
|
1292
|
+
-- live dialog too, and never infer permission to hide a running conversation.
|
|
1293
|
+
set confirmEl to missing value
|
|
1294
|
+
set checks to 1
|
|
1295
|
+
if (system attribute "RELAY_CLOSE_RUNNING") is "1" then set checks to 4
|
|
1296
|
+
repeat with attempt from 1 to checks
|
|
1297
|
+
delay 0.25
|
|
1298
|
+
set confirmEl to my closeConfirmButton()
|
|
1299
|
+
if confirmEl is not missing value then exit repeat
|
|
1300
|
+
end repeat
|
|
1301
|
+
if confirmEl is missing value then return "closed"
|
|
1302
|
+
if (system attribute "RELAY_CLOSE_RUNNING") is not "1" then
|
|
1303
|
+
tell application "System Events" to key code 53
|
|
1304
|
+
error "the agent started working before the tab closed - closing it now needs confirmation"
|
|
1305
|
+
end if
|
|
1306
|
+
tell application "System Events" to tell process "Conductor"
|
|
1307
|
+
perform action "AXPress" of confirmEl
|
|
1308
|
+
end tell
|
|
1309
|
+
delay 0.5
|
|
1310
|
+
return "closed"
|
|
1311
|
+
end closeChatTab
|
|
1312
|
+
|
|
1275
1313
|
on buttonsUnder(root, maxDepth)
|
|
1276
1314
|
-- Every AXButton at or below root, level-order and bounded. Depth is the whole
|
|
1277
1315
|
-- cost here: the transcript hangs off the same web area as anything Conductor
|
|
@@ -1298,20 +1336,35 @@ on buttonsUnder(root, maxDepth)
|
|
|
1298
1336
|
return acc
|
|
1299
1337
|
end buttonsUnder
|
|
1300
1338
|
|
|
1301
|
-
on
|
|
1302
|
-
-- Both places Conductor could be drawing
|
|
1303
|
-
--
|
|
1304
|
-
--
|
|
1305
|
-
--
|
|
1306
|
-
--
|
|
1307
|
-
-- walk the transcript to get there.
|
|
1339
|
+
on dialogButtons()
|
|
1340
|
+
-- Both places Conductor could be drawing a confirmation, each at the depth that
|
|
1341
|
+
-- shape actually sits at. A real sheet hangs off the window a level or two down;
|
|
1342
|
+
-- an in-webview dialog is a shallow portal near the top of the web area. Its
|
|
1343
|
+
-- buttons are inside depth 3; going to 4 reaches the transcript and made an idle
|
|
1344
|
+
-- close spend its whole 28-second budget proving that no dialog existed.
|
|
1308
1345
|
my requireWindow()
|
|
1309
1346
|
set fromWindow to {}
|
|
1310
1347
|
tell application "System Events" to tell process "Conductor"
|
|
1311
1348
|
set fromWindow to my buttonsUnder(window 1, 4)
|
|
1312
1349
|
end tell
|
|
1313
|
-
return fromWindow & (my buttonsUnder(my webArea(),
|
|
1314
|
-
end
|
|
1350
|
+
return fromWindow & (my buttonsUnder(my webArea(), 3))
|
|
1351
|
+
end dialogButtons
|
|
1352
|
+
|
|
1353
|
+
on closeConfirmButton()
|
|
1354
|
+
-- The running-chat dialog has exactly Cancel + "Close anyway". Require the
|
|
1355
|
+
-- pair so an unrelated destructive dialog can never donate a similarly named
|
|
1356
|
+
-- button to this run.
|
|
1357
|
+
set found to missing value
|
|
1358
|
+
set sawCancel to false
|
|
1359
|
+
repeat with entry in my dialogButtons()
|
|
1360
|
+
set node to contents of entry
|
|
1361
|
+
set label to my axName(node)
|
|
1362
|
+
if label is "Cancel" then set sawCancel to true
|
|
1363
|
+
if label starts with "Close anyway" then set found to node
|
|
1364
|
+
end repeat
|
|
1365
|
+
if sawCancel then return found
|
|
1366
|
+
return missing value
|
|
1367
|
+
end closeConfirmButton
|
|
1315
1368
|
|
|
1316
1369
|
on archiveConfirmButton()
|
|
1317
1370
|
-- Conductor asks before archiving a workspace whose agents are still running:
|
|
@@ -1323,7 +1376,7 @@ on archiveConfirmButton()
|
|
|
1323
1376
|
-- and would otherwise read that as a confirmation of this.
|
|
1324
1377
|
set found to missing value
|
|
1325
1378
|
set sawCancel to false
|
|
1326
|
-
repeat with entry in my
|
|
1379
|
+
repeat with entry in my dialogButtons()
|
|
1327
1380
|
set node to contents of entry
|
|
1328
1381
|
set label to my axName(node)
|
|
1329
1382
|
if label is "Cancel" then set sawCancel to true
|
|
@@ -31,7 +31,7 @@ import { HIT_CLOSE, HIT_OPEN, isToolResult, timestampMs, workspaceTitle } from "
|
|
|
31
31
|
/** Versions we know how to speak. The client's choice wins when we know it. */
|
|
32
32
|
export const PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
33
33
|
export const SERVER_INFO = { name: 'conductor-remote', version: '1' };
|
|
34
|
-
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. create_workspace opens its workspace link without direct UI control; requested agent settings and the first prompt are applied later through Conductor’s UI. plan_usage, dismiss_prompt, keep_awake and relay_logs touch no UI. send_prompt, split_chat, stop_turn, set_agent_options, set_default_model, a live list_models call, set_workspace_status and archive_workspace drive the real Mac UI and steal focus for a few seconds — confirm with the user before using them on a chat they did not name. archive_workspace also deletes the worktree and stops whatever is running there.';
|
|
34
|
+
export const INSTRUCTIONS = 'Drives local Conductor agents through the conductor-remote relay. search_chats and read_chat reach archived workspaces, which is where most finished work lives. create_workspace opens its workspace link without direct UI control; requested agent settings and the first prompt are applied later through Conductor’s UI. plan_usage, dismiss_prompt, keep_awake and relay_logs touch no UI. send_prompt, split_chat, stop_turn, close_chat, set_agent_options, set_default_model, a live list_models call, set_workspace_status and archive_workspace drive the real Mac UI and steal focus for a few seconds — confirm with the user before using them on a chat they did not name. archive_workspace also deletes the worktree and stops whatever is running there.';
|
|
35
35
|
/** Reads are quick; a UI write is measured in tens of seconds (writes.ts ▸ SEND_ATTEMPT_MS). */
|
|
36
36
|
export const READ_TIMEOUT_MS = 10_000;
|
|
37
37
|
export const WRITE_TIMEOUT_MS = 75_000;
|
|
@@ -577,6 +577,41 @@ export function createTools(call) {
|
|
|
577
577
|
return 'stopped';
|
|
578
578
|
}
|
|
579
579
|
},
|
|
580
|
+
{
|
|
581
|
+
name: 'close_chat',
|
|
582
|
+
description: 'Close one visible Conductor chat tab. This hides the tab but keeps its transcript; it can be reopened in Conductor with ⌘⇧T. DRIVES THE REAL UI and steals focus for a few seconds. A running chat is refused unless close_running is true, matching Conductor’s own “Close anyway” confirmation — only set it after the user confirms.',
|
|
583
|
+
inputSchema: {
|
|
584
|
+
type: 'object',
|
|
585
|
+
properties: {
|
|
586
|
+
session_id: { type: 'string', description: 'The visible chat tab to close.' },
|
|
587
|
+
workspace_id: {
|
|
588
|
+
type: 'string',
|
|
589
|
+
description: 'Workspace holding the chat. The relay verifies it when provided.'
|
|
590
|
+
},
|
|
591
|
+
close_running: {
|
|
592
|
+
type: 'boolean',
|
|
593
|
+
description: 'Close even though its agent is still working. Requires explicit user confirmation.'
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
required: ['session_id']
|
|
597
|
+
},
|
|
598
|
+
run: async (args) => {
|
|
599
|
+
const sessionId = need(args, 'session_id');
|
|
600
|
+
const data = await call(routes.closeChat.path(sessionId), {
|
|
601
|
+
method: routes.closeChat.method,
|
|
602
|
+
body: {
|
|
603
|
+
workspaceId: str(args.workspace_id),
|
|
604
|
+
closeRunning: args.close_running === true
|
|
605
|
+
},
|
|
606
|
+
timeoutMs: WRITE_TIMEOUT_MS
|
|
607
|
+
});
|
|
608
|
+
if (!data.ok)
|
|
609
|
+
throw new Error(data.error ?? 'the chat tab did not close');
|
|
610
|
+
if (data.alreadyClosed)
|
|
611
|
+
return 'already closed';
|
|
612
|
+
return data.activeSessionId ? `closed; active session_id: ${data.activeSessionId}` : 'closed; no tabs remain';
|
|
613
|
+
}
|
|
614
|
+
},
|
|
580
615
|
{
|
|
581
616
|
name: 'list_models',
|
|
582
617
|
description: 'The model labels the relay knows. With no session_id this returns its stored picker labels, grouped by harness; use it before create_workspace. With a session_id it reads that chat’s live Conductor picker and refreshes the stored labels. A live read DRIVES THE REAL UI — it focuses the workspace and opens the menu — so it costs a few seconds of stolen focus.',
|
package/dist-node/src/routes.js
CHANGED
|
@@ -112,6 +112,8 @@ export const routes = {
|
|
|
112
112
|
defaultModel: param('POST', '/api/sessions/:sessionId/default-model'),
|
|
113
113
|
agent: param('POST', '/api/sessions/:sessionId/agent'),
|
|
114
114
|
stop: param('POST', '/api/sessions/:sessionId/stop'),
|
|
115
|
+
/** Hide one chat tab through Conductor's own Close tab action (Command-W). */
|
|
116
|
+
closeChat: param('DELETE', '/api/sessions/:sessionId'),
|
|
115
117
|
sendPrompt: param('POST', '/api/sessions/:sessionId/prompt'),
|
|
116
118
|
/** Write a phone-selected file into Conductor's attachment layout for this chat's workspace. */
|
|
117
119
|
uploadAttachment: param('POST', '/api/sessions/:sessionId/attachments'),
|
package/dist-node/src/server.js
CHANGED
|
@@ -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, 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, 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();
|
|
@@ -1560,6 +1560,83 @@ const server = http.createServer(async (req, res) => {
|
|
|
1560
1560
|
session: reads.listSessions(ws.id).find(s => s.id === sessionId)
|
|
1561
1561
|
});
|
|
1562
1562
|
}
|
|
1563
|
+
// DELETE /api/sessions/:id { workspaceId?, closeRunning? } — Conductor's
|
|
1564
|
+
// reversible Close tab action (Command-W). A running chat gets the same
|
|
1565
|
+
// explicit "Close anyway" gate as the desktop app.
|
|
1566
|
+
const closeChatId = routeParam(routes.closeChat, req.method, pathname);
|
|
1567
|
+
if (closeChatId) {
|
|
1568
|
+
const sessionId = closeChatId;
|
|
1569
|
+
const body = JSON.parse((await readBody(req)) || '{}');
|
|
1570
|
+
const ownerId = reads.sessionWorkspaceId(sessionId);
|
|
1571
|
+
if (!ownerId)
|
|
1572
|
+
return json(req, res, 404, { error: 'chat not found' });
|
|
1573
|
+
if (body.workspaceId && body.workspaceId !== ownerId) {
|
|
1574
|
+
return json(req, res, 409, { error: 'chat is not in that workspace' });
|
|
1575
|
+
}
|
|
1576
|
+
const ws = reads.getWorkspace(ownerId);
|
|
1577
|
+
if (!ws)
|
|
1578
|
+
return json(req, res, 404, { error: 'workspace for session not found' });
|
|
1579
|
+
const before = reads.listSessions(ws.id);
|
|
1580
|
+
const session = before.find(s => s.id === sessionId);
|
|
1581
|
+
const visibleActiveSession = () => {
|
|
1582
|
+
const visible = reads.listSessions(ws.id);
|
|
1583
|
+
const activeId = reads.getWorkspace(ws.id)?.active_session_id;
|
|
1584
|
+
return visible.some(s => s.id === activeId) ? (activeId ?? null) : (visible[0]?.id ?? null);
|
|
1585
|
+
};
|
|
1586
|
+
// Closing is a soft delete. A repeat whose first response was lost is already
|
|
1587
|
+
// in the requested state, so it is success rather than a stale-link error.
|
|
1588
|
+
if (!session) {
|
|
1589
|
+
return json(req, res, 200, {
|
|
1590
|
+
ok: true,
|
|
1591
|
+
alreadyClosed: true,
|
|
1592
|
+
activeSessionId: visibleActiveSession()
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
if ((session.status === 'working' || session.background_tasks.length > 0) && body.closeRunning !== true) {
|
|
1596
|
+
return json(req, res, 409, {
|
|
1597
|
+
ok: false,
|
|
1598
|
+
agentRunning: true,
|
|
1599
|
+
error: 'The agent is still working in this chat. Confirm closing it anyway.'
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
1602
|
+
const located = locateChat(ws, sessionId);
|
|
1603
|
+
if ('error' in located)
|
|
1604
|
+
return json(req, res, 409, { error: located.error });
|
|
1605
|
+
const result = await closeChat({ workspace: ws, sessionId, tab: located.tab }, body.closeRunning === true);
|
|
1606
|
+
if (!result.ok) {
|
|
1607
|
+
// A turn can start after the status read above. The script dismisses
|
|
1608
|
+
// Conductor's surprise dialog instead of accepting it, and this sends the
|
|
1609
|
+
// caller back through the same explicit confirmation path.
|
|
1610
|
+
if (body.closeRunning !== true && result.error?.includes('needs confirmation')) {
|
|
1611
|
+
return json(req, res, 409, {
|
|
1612
|
+
ok: false,
|
|
1613
|
+
agentRunning: true,
|
|
1614
|
+
error: 'The agent is still working in this chat. Confirm closing it anyway.'
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
return json(req, res, 502, result);
|
|
1618
|
+
}
|
|
1619
|
+
// Command-W is fire-and-forget. The durable receipt is the same flag all tab
|
|
1620
|
+
// reads filter on: this id disappearing from listSessions means Conductor set
|
|
1621
|
+
// sessions.is_hidden, not merely that a keystroke happened.
|
|
1622
|
+
let visible = true;
|
|
1623
|
+
for (let i = 0; i < 20 && visible; i++) {
|
|
1624
|
+
await sleep(300);
|
|
1625
|
+
visible = reads.listSessions(ws.id).some(s => s.id === sessionId);
|
|
1626
|
+
}
|
|
1627
|
+
if (visible) {
|
|
1628
|
+
return json(req, res, 502, {
|
|
1629
|
+
ok: false,
|
|
1630
|
+
strategy: result.strategy,
|
|
1631
|
+
error: 'Conductor took the close but the chat tab is still open. Try again, or close it on your Mac.'
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
return json(req, res, 200, {
|
|
1635
|
+
ok: true,
|
|
1636
|
+
strategy: result.strategy,
|
|
1637
|
+
activeSessionId: visibleActiveSession()
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1563
1640
|
// POST /api/sessions/:id/attachments?workspaceId= — raw bytes from the phone.
|
|
1564
1641
|
// Conductor derives an attachment from this on-disk layout plus the composer
|
|
1565
1642
|
// token, so its own database stays read-only from this relay's point of view.
|
package/dist-node/src/writes.js
CHANGED
|
@@ -492,6 +492,45 @@ return "ok"`.trim();
|
|
|
492
492
|
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
+
/**
|
|
496
|
+
* Hide one chat through Conductor's own Close tab action (Command-W).
|
|
497
|
+
*
|
|
498
|
+
* Like `stopTurn`, the branch is required because Command-W acts on whatever has
|
|
499
|
+
* keyboard focus. `selectChatTab` proves the target and `closeChatTab` moves focus
|
|
500
|
+
* back out of the terminal before pressing it. Closing is reversible in Conductor,
|
|
501
|
+
* but a running chat still gets its desktop confirmation: `closeRunning` is the
|
|
502
|
+
* caller's explicit answer to that prompt, never something this layer infers.
|
|
503
|
+
*
|
|
504
|
+
* The script only reports that it pressed the action. `sessions.is_hidden` is the
|
|
505
|
+
* receipt; server.ts waits until `Reads.listSessions` no longer returns this id.
|
|
506
|
+
*/
|
|
507
|
+
export async function closeChat(target, closeRunning) {
|
|
508
|
+
if (!target.workspace.branch) {
|
|
509
|
+
return { ok: false, strategy: 'applescript', error: 'workspace has no branch to focus' };
|
|
510
|
+
}
|
|
511
|
+
const script = `
|
|
512
|
+
${CONDUCTOR_HANDLERS}
|
|
513
|
+
|
|
514
|
+
my activateConductor()
|
|
515
|
+
my focusWorkspace()
|
|
516
|
+
my selectChatTab()
|
|
517
|
+
my closeChatTab()
|
|
518
|
+
return "ok"`.trim();
|
|
519
|
+
try {
|
|
520
|
+
await withTargetEnvironment(target, targetEnvironment => uiTurn(() => exec('osascript', ['-e', script], {
|
|
521
|
+
env: {
|
|
522
|
+
...process.env,
|
|
523
|
+
...targetEnvironment,
|
|
524
|
+
RELAY_CLOSE_RUNNING: closeRunning ? '1' : ''
|
|
525
|
+
},
|
|
526
|
+
timeout: SEND_ATTEMPT_MS
|
|
527
|
+
})));
|
|
528
|
+
return { ok: true, strategy: 'applescript' };
|
|
529
|
+
}
|
|
530
|
+
catch (err) {
|
|
531
|
+
return { ok: false, strategy: 'applescript', error: osaError(err) };
|
|
532
|
+
}
|
|
533
|
+
}
|
|
495
534
|
/**
|
|
496
535
|
* Press the Wi-Fi menu's row for a personal hotspot — Instant Hotspot, the same
|
|
497
536
|
* button a human clicks. `networksetup` can only join a network that is
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.89.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.",
|