conductor-remote 1.48.0 → 1.49.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/assets/{index-ajfm3IVV.css → index-DWYStsWl.css} +1 -1
- package/dist/assets/index-qLOtnZJy.js +43 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/dist-node/src/file-preview.js +59 -0
- package/dist-node/src/parked.js +3 -1
- package/dist-node/src/routes.js +2 -0
- package/dist-node/src/server.js +82 -6
- package/dist-node/src/sidecar.js +2 -2
- package/dist-node/src/writes.js +15 -5
- package/package.json +3 -2
- package/dist/assets/index-CfrbqYvg.js +0 -42
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-qLOtnZJy.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DWYStsWl.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 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:"e1e682e2e5e88fa03b7808ae9db8e098"},{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 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:"e1e682e2e5e88fa03b7808ae9db8e098"},{url:"index.html",revision:"08562b5f5dbf07b0c55881ab9c52d8b9"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-qLOtnZJy.js",revision:null},{url:"assets/index-DWYStsWl.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\//]}))});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
2
|
+
'.bash',
|
|
3
|
+
'.c',
|
|
4
|
+
'.cc',
|
|
5
|
+
'.cpp',
|
|
6
|
+
'.css',
|
|
7
|
+
'.go',
|
|
8
|
+
'.h',
|
|
9
|
+
'.hpp',
|
|
10
|
+
'.html',
|
|
11
|
+
'.java',
|
|
12
|
+
'.js',
|
|
13
|
+
'.json',
|
|
14
|
+
'.jsx',
|
|
15
|
+
'.md',
|
|
16
|
+
'.mjs',
|
|
17
|
+
'.mts',
|
|
18
|
+
'.php',
|
|
19
|
+
'.py',
|
|
20
|
+
'.rb',
|
|
21
|
+
'.rs',
|
|
22
|
+
'.scss',
|
|
23
|
+
'.sh',
|
|
24
|
+
'.sql',
|
|
25
|
+
'.svg',
|
|
26
|
+
'.swift',
|
|
27
|
+
'.toml',
|
|
28
|
+
'.ts',
|
|
29
|
+
'.tsx',
|
|
30
|
+
'.txt',
|
|
31
|
+
'.yaml',
|
|
32
|
+
'.yml'
|
|
33
|
+
]);
|
|
34
|
+
function sourceExtension(filePath) {
|
|
35
|
+
const name = filePath.slice(filePath.lastIndexOf('/') + 1);
|
|
36
|
+
const dot = name.lastIndexOf('.');
|
|
37
|
+
return dot === -1 ? '' : name.slice(dot).toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
/** True when a file is a text source format this viewer is allowed to expose. */
|
|
40
|
+
export function isPreviewableSource(filePath) {
|
|
41
|
+
return SOURCE_EXTENSIONS.has(sourceExtension(filePath));
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse a source link without treating ordinary PWA routes as file references.
|
|
45
|
+
* The extension allowlist keeps links such as `/w/a-workspace` in the browser,
|
|
46
|
+
* while also preventing this endpoint from becoming a generic file reader.
|
|
47
|
+
*/
|
|
48
|
+
export function parseFileReference(reference) {
|
|
49
|
+
if (!reference.startsWith('/'))
|
|
50
|
+
return null;
|
|
51
|
+
const location = reference.match(/:([1-9]\d*)(?::\d+)?$/);
|
|
52
|
+
const line = location ? Number(location[1]) : null;
|
|
53
|
+
if (line !== null && !Number.isSafeInteger(line))
|
|
54
|
+
return null;
|
|
55
|
+
const filePath = location ? reference.slice(0, -location[0].length) : reference;
|
|
56
|
+
if (!isPreviewableSource(filePath))
|
|
57
|
+
return null;
|
|
58
|
+
return { path: filePath, line };
|
|
59
|
+
}
|
package/dist-node/src/parked.js
CHANGED
|
@@ -54,7 +54,7 @@ export class ParkedPromptQueue {
|
|
|
54
54
|
* Park a prompt (idempotent per chat + text, so a retap while locked revives
|
|
55
55
|
* the entry instead of doubling it) and start watching for the unlock.
|
|
56
56
|
*/
|
|
57
|
-
park(workspaceId, sessionId, text, agent) {
|
|
57
|
+
park(workspaceId, sessionId, text, agent, queue = false) {
|
|
58
58
|
const existing = this.entries.find(e => e.sessionId === sessionId && e.text.trim() === text.trim());
|
|
59
59
|
if (existing) {
|
|
60
60
|
// A re-park is the user asking again: back to waiting with a clean slate.
|
|
@@ -62,6 +62,7 @@ export class ParkedPromptQueue {
|
|
|
62
62
|
existing.attempts = 0;
|
|
63
63
|
existing.error = undefined;
|
|
64
64
|
existing.agent = agent ?? existing.agent;
|
|
65
|
+
existing.queue = queue;
|
|
65
66
|
this.save();
|
|
66
67
|
void this.pump();
|
|
67
68
|
return existing;
|
|
@@ -71,6 +72,7 @@ export class ParkedPromptQueue {
|
|
|
71
72
|
sessionId,
|
|
72
73
|
text,
|
|
73
74
|
agent,
|
|
75
|
+
queue,
|
|
74
76
|
status: 'waiting',
|
|
75
77
|
attempts: 0,
|
|
76
78
|
createdAt: Date.now(),
|
package/dist-node/src/routes.js
CHANGED
|
@@ -45,6 +45,8 @@ export const routes = {
|
|
|
45
45
|
search: flat('GET', '/api/search'),
|
|
46
46
|
repos: flat('GET', '/api/repos'),
|
|
47
47
|
repoIcon: param('GET', '/api/repos/:repo/icon'),
|
|
48
|
+
/** A source-file preview linked from agent Markdown. */
|
|
49
|
+
filePreview: param('GET', '/api/files/:reference'),
|
|
48
50
|
logs: flat('GET', '/api/logs'),
|
|
49
51
|
settings: flat('GET', '/api/settings'),
|
|
50
52
|
updateSettings: flat('PATCH', '/api/settings'),
|
package/dist-node/src/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { writeAttachment } from "./attachments.js";
|
|
|
7
7
|
import { startAutoUpdate, updateStatus } from "./autoupdate.js";
|
|
8
8
|
import { loadConfig, stateDir } from "./config.js";
|
|
9
9
|
import { ConductorDb } from "./db.js";
|
|
10
|
+
import { parseFileReference } from "./file-preview.js";
|
|
10
11
|
import { FirstPromptQueue } from "./firstprompt.js";
|
|
11
12
|
import { startFunnelWatchdog } from "./funnel-watchdog.js";
|
|
12
13
|
import { workspaceDiff } from "./git.js";
|
|
@@ -226,7 +227,7 @@ async function confirmAgentOptions(ws, sessionId, opts) {
|
|
|
226
227
|
* minutes schedule sits *outside* this and is unaffected — it retries a delivery that
|
|
227
228
|
* never got off the ground (worktree still setting up), not one Conductor fumbled.
|
|
228
229
|
*/
|
|
229
|
-
async function deliverPrompt(ws, sessionId, text, budgetMs = SEND_BUDGET_MS) {
|
|
230
|
+
async function deliverPrompt(ws, sessionId, text, budgetMs = SEND_BUDGET_MS, queue = false) {
|
|
230
231
|
const located = locateChat(ws, sessionId);
|
|
231
232
|
if ('error' in located)
|
|
232
233
|
return { ok: false, strategy: actuator.name, attempts: 0, error: located.error };
|
|
@@ -243,7 +244,10 @@ async function deliverPrompt(ws, sessionId, text, budgetMs = SEND_BUDGET_MS) {
|
|
|
243
244
|
// write, and only the run knows what was left of the budget when it started. Minus
|
|
244
245
|
// the confirm, so a caller on a tight budget spends it on the run rather than on
|
|
245
246
|
// watching — a 25s-era phone gets one full-length attempt, not two too short to finish.
|
|
246
|
-
last = await actuator.send({ workspace: ws, sessionId, tab: located.tab }, text,
|
|
247
|
+
last = await actuator.send({ workspace: ws, sessionId, tab: located.tab }, text, {
|
|
248
|
+
deadline: deadline - MIN_CONFIRM_MS,
|
|
249
|
+
queue
|
|
250
|
+
});
|
|
247
251
|
if (await confirmDelivery(sessionId, text, beforeRowid, deadline)) {
|
|
248
252
|
if (attempts > 1)
|
|
249
253
|
console.info(`[relay] send to ${label} landed on attempt ${attempts}`);
|
|
@@ -359,7 +363,7 @@ const parkedPrompts = new ParkedPromptQueue(path.join(stateDir(), 'parked-prompt
|
|
|
359
363
|
if (!applied.ok)
|
|
360
364
|
return { ok: false, error: applied.error, blocked: lockBlocked(applied.error) };
|
|
361
365
|
}
|
|
362
|
-
const result = await deliverPrompt(ws, entry.sessionId, entry.text);
|
|
366
|
+
const result = await deliverPrompt(ws, entry.sessionId, entry.text, SEND_BUDGET_MS, entry.queue);
|
|
363
367
|
return { ok: result.ok, error: result.error, blocked: lockBlocked(result.error) };
|
|
364
368
|
}),
|
|
365
369
|
notify: (entry, error) => {
|
|
@@ -386,6 +390,72 @@ const MIME = {
|
|
|
386
390
|
'.svg': 'image/svg+xml',
|
|
387
391
|
'.png': 'image/png'
|
|
388
392
|
};
|
|
393
|
+
/** A preview stays small enough to render smoothly in the phone's source sheet. */
|
|
394
|
+
const FILE_PREVIEW_MAX_BYTES = 512 * 1024;
|
|
395
|
+
const FILE_PREVIEW_CONTEXT_LINES = 100;
|
|
396
|
+
const FILE_PREVIEW_FIRST_LINES = 500;
|
|
397
|
+
/** True only for a descendant. A prefix test would let `/workspaces-old` escape `/workspaces`. */
|
|
398
|
+
function insideDirectory(filePath, root) {
|
|
399
|
+
const relative = path.relative(root, filePath);
|
|
400
|
+
return relative !== '' && relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Serve source that an agent linked in its Markdown. The link format comes from
|
|
404
|
+
* coding-agent file references, but its path still arrives from an internet-facing
|
|
405
|
+
* client. Resolve both sides and accept only regular source files below the
|
|
406
|
+
* configured Conductor workspace root.
|
|
407
|
+
*/
|
|
408
|
+
async function serveFilePreview(req, res, reference) {
|
|
409
|
+
const target = parseFileReference(reference);
|
|
410
|
+
if (!target)
|
|
411
|
+
return json(req, res, 404, { error: 'source file not found' });
|
|
412
|
+
let filePath;
|
|
413
|
+
let workspaceRoot;
|
|
414
|
+
let size;
|
|
415
|
+
try {
|
|
416
|
+
;
|
|
417
|
+
[filePath, workspaceRoot] = await Promise.all([
|
|
418
|
+
fs.promises.realpath(target.path),
|
|
419
|
+
fs.promises.realpath(cfg.workspacesRoot)
|
|
420
|
+
]);
|
|
421
|
+
if (!insideDirectory(filePath, workspaceRoot))
|
|
422
|
+
return json(req, res, 404, { error: 'source file not found' });
|
|
423
|
+
const info = await fs.promises.stat(filePath);
|
|
424
|
+
if (!info.isFile())
|
|
425
|
+
return json(req, res, 404, { error: 'source file not found' });
|
|
426
|
+
size = info.size;
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
return json(req, res, 404, { error: 'source file not found' });
|
|
430
|
+
}
|
|
431
|
+
if (size > FILE_PREVIEW_MAX_BYTES)
|
|
432
|
+
return json(req, res, 413, { error: 'source file is too large to preview' });
|
|
433
|
+
let content;
|
|
434
|
+
try {
|
|
435
|
+
const raw = await fs.promises.readFile(filePath);
|
|
436
|
+
if (raw.includes(0))
|
|
437
|
+
return json(req, res, 415, { error: 'source file is not text' });
|
|
438
|
+
content = new TextDecoder('utf-8', { fatal: true }).decode(raw);
|
|
439
|
+
}
|
|
440
|
+
catch {
|
|
441
|
+
return json(req, res, 415, { error: 'source file is not text' });
|
|
442
|
+
}
|
|
443
|
+
const lines = content.split('\n');
|
|
444
|
+
const focus = target.line === null ? null : Math.min(target.line, lines.length);
|
|
445
|
+
const start = focus === null ? 0 : Math.max(0, focus - FILE_PREVIEW_CONTEXT_LINES - 1);
|
|
446
|
+
const end = focus === null
|
|
447
|
+
? Math.min(lines.length, FILE_PREVIEW_FIRST_LINES)
|
|
448
|
+
: Math.min(lines.length, focus + FILE_PREVIEW_CONTEXT_LINES);
|
|
449
|
+
return json(req, res, 200, {
|
|
450
|
+
path: target.path,
|
|
451
|
+
line: focus,
|
|
452
|
+
lineStart: start + 1,
|
|
453
|
+
lineEnd: end,
|
|
454
|
+
totalLines: lines.length,
|
|
455
|
+
content: lines.slice(start, end).join('\n'),
|
|
456
|
+
truncated: start > 0 || end < lines.length
|
|
457
|
+
});
|
|
458
|
+
}
|
|
389
459
|
/**
|
|
390
460
|
* Successful GETs are conditional + compressed to keep the phone's polling cheap.
|
|
391
461
|
* `no-cache` (not `no-store`) means the browser must revalidate on every tick —
|
|
@@ -814,6 +884,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
814
884
|
res.end(data);
|
|
815
885
|
});
|
|
816
886
|
}
|
|
887
|
+
// GET /api/files/:reference — source linked from an agent reply. The Markdown component
|
|
888
|
+
// intercepts the browser navigation and fetches this endpoint with its auth header.
|
|
889
|
+
const fileReference = routeParam(routes.filePreview, req.method, pathname);
|
|
890
|
+
if (fileReference)
|
|
891
|
+
return serveFilePreview(req, res, fileReference);
|
|
817
892
|
// GET /api/workspaces/:id — one workspace by id, archived included. `/api/state` lists
|
|
818
893
|
// only the live ones, so this is what lets the phone open a chat search found in work
|
|
819
894
|
// that has been put away: the worktree is gone, the transcript is not.
|
|
@@ -1001,6 +1076,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1001
1076
|
// rather than extending it past what the phone said it would wait.
|
|
1002
1077
|
const deadline = Date.now() + sendBudget(req);
|
|
1003
1078
|
const agent = body.agent && Object.keys(body.agent).length ? body.agent : undefined;
|
|
1079
|
+
const queue = body.queue === true;
|
|
1004
1080
|
if (agent?.effort && !EFFORT_LABELS[agent.effort]) {
|
|
1005
1081
|
return json(req, res, 400, { error: `effort must be one of ${Object.keys(EFFORT_LABELS).join(', ')}` });
|
|
1006
1082
|
}
|
|
@@ -1016,7 +1092,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1016
1092
|
const applied = await applyAgentPatch(ws, sessionId, agent);
|
|
1017
1093
|
if (!applied.ok) {
|
|
1018
1094
|
if (lockBlocked(applied.error)) {
|
|
1019
|
-
const queued = parkedPrompts.park(ws.id, sessionId, text, agent);
|
|
1095
|
+
const queued = parkedPrompts.park(ws.id, sessionId, text, agent, queue);
|
|
1020
1096
|
return {
|
|
1021
1097
|
status: 202,
|
|
1022
1098
|
body: { ok: false, parked: true, queued, strategy: actuator.name, error: PARKED_ERROR }
|
|
@@ -1027,7 +1103,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1027
1103
|
}
|
|
1028
1104
|
// Retries live inside deliverPrompt, confirmed against the transcript each time,
|
|
1029
1105
|
// and inside the deadline this phone told us it would wait.
|
|
1030
|
-
const result = await deliverPrompt(ws, sessionId, text, deadline - Date.now());
|
|
1106
|
+
const result = await deliverPrompt(ws, sessionId, text, deadline - Date.now(), queue);
|
|
1031
1107
|
if (result.ok) {
|
|
1032
1108
|
// Whatever a queue was still holding has now been said by hand — the first
|
|
1033
1109
|
// prompt (including a failed entry retried from the chat), and any parked
|
|
@@ -1038,7 +1114,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
1038
1114
|
}
|
|
1039
1115
|
if (lockBlocked(result.error)) {
|
|
1040
1116
|
// Settings (if any) already stuck, so the entry parks without them.
|
|
1041
|
-
const queued = parkedPrompts.park(ws.id, sessionId, text);
|
|
1117
|
+
const queued = parkedPrompts.park(ws.id, sessionId, text, undefined, queue);
|
|
1042
1118
|
return {
|
|
1043
1119
|
status: 202,
|
|
1044
1120
|
body: { ok: false, parked: true, queued, strategy: result.strategy, error: PARKED_ERROR }
|
package/dist-node/src/sidecar.js
CHANGED
|
@@ -166,7 +166,7 @@ export async function sidecarAvailable() {
|
|
|
166
166
|
* Deliver a prompt to a specific session — the real send path, precisely
|
|
167
167
|
* targeted. Resolves once the sidecar has accepted (queued/sent) the message.
|
|
168
168
|
*/
|
|
169
|
-
export async function sidecarSendUserMessage(sessionId, text) {
|
|
169
|
+
export async function sidecarSendUserMessage(sessionId, text, deliveryMode = 'default') {
|
|
170
170
|
await rpc('query', {
|
|
171
171
|
type: 'sendUserMessageRequest',
|
|
172
172
|
...LOCAL_AUTH,
|
|
@@ -174,7 +174,7 @@ export async function sidecarSendUserMessage(sessionId, text) {
|
|
|
174
174
|
id: randomUUID(),
|
|
175
175
|
message: text,
|
|
176
176
|
agentMessage: text,
|
|
177
|
-
deliveryMode
|
|
177
|
+
deliveryMode
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
/**
|
package/dist-node/src/writes.js
CHANGED
|
@@ -195,12 +195,12 @@ export class SidecarActuator {
|
|
|
195
195
|
return sidecarAvailable();
|
|
196
196
|
}
|
|
197
197
|
/** `deadline` is ignored — the sidecar is one socket write, with no UI to wait on. */
|
|
198
|
-
async send(target, text,
|
|
198
|
+
async send(target, text, options = {}) {
|
|
199
199
|
const sessionId = target.sessionId ?? target.workspace.active_session_id;
|
|
200
200
|
if (!sessionId)
|
|
201
201
|
return { ok: false, strategy: this.name, error: 'no session id to target' };
|
|
202
202
|
try {
|
|
203
|
-
await sidecarSendUserMessage(sessionId, text);
|
|
203
|
+
await sidecarSendUserMessage(sessionId, text, options.queue ? 'queue' : 'default');
|
|
204
204
|
return { ok: true, strategy: this.name };
|
|
205
205
|
}
|
|
206
206
|
catch (err) {
|
|
@@ -347,7 +347,8 @@ export class AppleScriptActuator {
|
|
|
347
347
|
name = 'applescript';
|
|
348
348
|
caveat = "Opens the target workspace's own Conductor link, then confirms the chat tab before sending.";
|
|
349
349
|
precise = true;
|
|
350
|
-
async send(target, text,
|
|
350
|
+
async send(target, text, options = {}) {
|
|
351
|
+
const deadline = options.deadline ?? Date.now() + SEND_ATTEMPT_MS;
|
|
351
352
|
// Open the target workspace's own link, confirm its chat tab, fill the composer, send.
|
|
352
353
|
// Filling is an Accessibility write (no keystrokes, no clipboard); the
|
|
353
354
|
// clipboard paste is kept only as a fallback, and stashes/restores around it.
|
|
@@ -365,7 +366,11 @@ if not (my fillComposer(promptText)) then
|
|
|
365
366
|
set the clipboard to savedClipboard
|
|
366
367
|
end if
|
|
367
368
|
tell application "System Events"
|
|
368
|
-
|
|
369
|
+
if (system attribute "RELAY_QUEUE_PROMPT") is "1" then
|
|
370
|
+
key code 36 using {command down}
|
|
371
|
+
else
|
|
372
|
+
key code 36
|
|
373
|
+
end if
|
|
369
374
|
end tell
|
|
370
375
|
`.trim();
|
|
371
376
|
// Pass the prompt via a temp file + env to avoid AppleScript string escaping.
|
|
@@ -376,7 +381,12 @@ end tell
|
|
|
376
381
|
await fs.writeFile(tmp, text, 'utf8');
|
|
377
382
|
try {
|
|
378
383
|
await uiTurn(() => exec('osascript', ['-e', script], {
|
|
379
|
-
env: {
|
|
384
|
+
env: {
|
|
385
|
+
...process.env,
|
|
386
|
+
RELAY_PROMPT_FILE: tmp,
|
|
387
|
+
RELAY_QUEUE_PROMPT: options.queue ? '1' : '',
|
|
388
|
+
...targetEnv(target)
|
|
389
|
+
},
|
|
380
390
|
timeout: runCeiling(deadline)
|
|
381
391
|
}));
|
|
382
392
|
return { ok: true, strategy: this.name };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.49.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.",
|
|
@@ -50,13 +50,14 @@
|
|
|
50
50
|
"typecheck": "tsc -p tsconfig.json",
|
|
51
51
|
"lint": "biome check .",
|
|
52
52
|
"fix": "biome check . --fix",
|
|
53
|
-
"verify": "yarn typecheck && yarn lint && yarn check:imports && yarn check:routes && yarn check:attachments && yarn check:applescript && yarn check:config && yarn check:model-labels && yarn check:nosleep && yarn check:uilock && yarn check:firstprompt && yarn check:sendonce && yarn check:notify",
|
|
53
|
+
"verify": "yarn typecheck && yarn lint && yarn check:imports && yarn check:routes && yarn check:attachments && yarn check:applescript && yarn check:config && yarn check:file-links && yarn check:model-labels && yarn check:nosleep && yarn check:uilock && yarn check:firstprompt && yarn check:sendonce && yarn check:notify",
|
|
54
54
|
"release": "semantic-release",
|
|
55
55
|
"prepack": "yarn build && yarn build:node",
|
|
56
56
|
"postinstall": "husky || true",
|
|
57
57
|
"check:attachments": "node scripts/check-attachments.ts",
|
|
58
58
|
"check:applescript": "node scripts/check-applescript.ts",
|
|
59
59
|
"check:config": "node scripts/check-config.ts",
|
|
60
|
+
"check:file-links": "node scripts/check-file-links.ts",
|
|
60
61
|
"check:firstprompt": "node scripts/check-firstprompt.ts",
|
|
61
62
|
"check:model-labels": "node scripts/check-model-labels.ts",
|
|
62
63
|
"check:sendonce": "node scripts/check-sendonce.ts",
|