conductor-remote 1.58.2 → 1.59.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-wa-CeYLY.js +44 -0
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/dist-node/src/mcp-tools.js +13 -2
- package/dist-node/src/reads.js +19 -1
- package/dist-node/src/routes.js +6 -0
- package/dist-node/src/server.js +18 -0
- package/dist-node/src/shared.js +12 -0
- package/dist-node/src/transcript.js +144 -12
- package/package.json +1 -1
- package/dist/assets/index-yoen_tHL.js +0 -44
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-wa-CeYLY.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-DT9enoqA.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 l={};const c=e=>i(e,o),t={module:{uri:o},exports:l,require:c};s[o]=Promise.all(n.map(e=>t[e]||c(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 c=e=>i(e,o),t={module:{uri:o},exports:l,require:c};s[o]=Promise.all(n.map(e=>t[e]||c(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:"13feb5d03a4ccc59c1d0229bf7647aa1"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-wa-CeYLY.js",revision:null},{url:"assets/index-DT9enoqA.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\//]}))});
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { chatCursor, parseChatCursor } from "./chat-cursor.js";
|
|
29
29
|
import { routes } from "./routes.js";
|
|
30
|
-
import { HIT_CLOSE, HIT_OPEN, workspaceTitle } from "./shared.js";
|
|
30
|
+
import { HIT_CLOSE, HIT_OPEN, isToolResult, workspaceTitle } from "./shared.js";
|
|
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' };
|
|
@@ -197,7 +197,18 @@ export function createTools(call) {
|
|
|
197
197
|
if (anchor !== null && !data.entries.some(entry => entry.rowid === anchor)) {
|
|
198
198
|
throw new Error('near cursor is not in that session');
|
|
199
199
|
}
|
|
200
|
-
const wanted = data.entries.filter(e =>
|
|
200
|
+
const wanted = data.entries.filter(e => {
|
|
201
|
+
if (e.role === 'thinking')
|
|
202
|
+
return includeThinking;
|
|
203
|
+
// A successful result carries the call's output, which is the churn `include_tools`
|
|
204
|
+
// exists to keep out of a context window — the call above it already says what ran.
|
|
205
|
+
// A failed one stays: one line, and it is why the turn changed course.
|
|
206
|
+
if (isToolResult(e) && !e.error)
|
|
207
|
+
return false;
|
|
208
|
+
if (e.role === 'tool')
|
|
209
|
+
return includeTools;
|
|
210
|
+
return true;
|
|
211
|
+
});
|
|
201
212
|
if (!wanted.length)
|
|
202
213
|
return `no messages in session ${sessionId}`;
|
|
203
214
|
let selected;
|
package/dist-node/src/reads.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { describeRepoIcon, resolveRepoIcon } from "./icons.js";
|
|
5
5
|
import { workspaceTitle } from "./shared.js";
|
|
6
|
-
import { parseMessage } from "./transcript.js";
|
|
6
|
+
import { parseMessage, toolImageAt } from "./transcript.js";
|
|
7
7
|
/** Keep the stable wire field in sync with whichever provider owns the chat. */
|
|
8
8
|
function toSessionRow(row) {
|
|
9
9
|
const { codex_thinking_level, ...session } = row;
|
|
@@ -309,6 +309,24 @@ export class Reads {
|
|
|
309
309
|
}
|
|
310
310
|
return null;
|
|
311
311
|
}
|
|
312
|
+
/**
|
|
313
|
+
* One image a tool returned, by the reference its transcript entry carries.
|
|
314
|
+
*
|
|
315
|
+
* The bytes sit in `session_messages.content` as base64, so this is the same read-only
|
|
316
|
+
* handle as everything else — nothing is written and nothing is cached to disk. The
|
|
317
|
+
* reference names a row and the image's position in it, and `toolImageAt` does the
|
|
318
|
+
* walk, because the numbering has to be the one `parseMessage` used.
|
|
319
|
+
*/
|
|
320
|
+
toolImage(reference) {
|
|
321
|
+
const dot = reference.lastIndexOf('.');
|
|
322
|
+
const rowid = Number(reference.slice(0, dot));
|
|
323
|
+
const index = Number(reference.slice(dot + 1));
|
|
324
|
+
if (dot < 0 || !Number.isInteger(rowid) || !Number.isInteger(index) || index < 0)
|
|
325
|
+
return null;
|
|
326
|
+
const rows = this.db.query('SELECT content FROM session_messages WHERE rowid = ? LIMIT 1', [rowid]);
|
|
327
|
+
const content = rows[0]?.content;
|
|
328
|
+
return content ? toolImageAt(content, index) : null;
|
|
329
|
+
}
|
|
312
330
|
/**
|
|
313
331
|
* Which workspace a chat belongs to.
|
|
314
332
|
*
|
package/dist-node/src/routes.js
CHANGED
|
@@ -51,6 +51,12 @@ export const routes = {
|
|
|
51
51
|
localImage: param('GET', '/api/local-images/:path'),
|
|
52
52
|
/** A source-file preview linked from agent Markdown. */
|
|
53
53
|
filePreview: param('GET', '/api/files/:reference'),
|
|
54
|
+
/**
|
|
55
|
+
* One image a tool returned, addressed `<message rowid>.<image number in that row>`.
|
|
56
|
+
* It is a route rather than a field because the bytes are ~100 kB of base64 each and a
|
|
57
|
+
* transcript's first fetch carries a whole chat: the phone asks only for the ones it opens.
|
|
58
|
+
*/
|
|
59
|
+
toolImage: param('GET', '/api/tool-images/:reference'),
|
|
54
60
|
/** Temporarily hold a file while the phone creates the workspace it will belong to. */
|
|
55
61
|
stageAttachment: flat('POST', '/api/attachments'),
|
|
56
62
|
/** Drop a staged file the user removed before creating its workspace. */
|
package/dist-node/src/server.js
CHANGED
|
@@ -1053,6 +1053,24 @@ const server = http.createServer(async (req, res) => {
|
|
|
1053
1053
|
const localImage = routeParam(routes.localImage, req.method, pathname);
|
|
1054
1054
|
if (localImage)
|
|
1055
1055
|
return serveLocalImage(req, res, localImage);
|
|
1056
|
+
// GET /api/tool-images/:reference — a screenshot or other image a tool returned. Held
|
|
1057
|
+
// back from the transcript itself (~100 kB of base64 each) and fetched only for a step
|
|
1058
|
+
// the reader opened, with the phone's auth header, like every other image route here.
|
|
1059
|
+
const toolImageRef = routeParam(routes.toolImage, req.method, pathname);
|
|
1060
|
+
if (toolImageRef) {
|
|
1061
|
+
const image = reads.toolImage(toolImageRef);
|
|
1062
|
+
if (!image)
|
|
1063
|
+
return json(req, res, 404, { error: 'image not found' });
|
|
1064
|
+
const bytes = Buffer.from(image.data, 'base64');
|
|
1065
|
+
// Immutable: a transcript row is written once, so the reference names one picture
|
|
1066
|
+
// forever and re-opening the step costs nothing.
|
|
1067
|
+
res.writeHead(200, {
|
|
1068
|
+
'content-type': image.mediaType,
|
|
1069
|
+
'content-length': String(bytes.length),
|
|
1070
|
+
'cache-control': 'private, max-age=86400, immutable'
|
|
1071
|
+
});
|
|
1072
|
+
return void res.end(bytes);
|
|
1073
|
+
}
|
|
1056
1074
|
// GET /api/files/:reference — source linked from an agent reply. The Markdown component
|
|
1057
1075
|
// intercepts the browser navigation and fetches this endpoint with its auth header.
|
|
1058
1076
|
const fileReference = routeParam(routes.filePreview, req.method, pathname);
|
package/dist-node/src/shared.js
CHANGED
|
@@ -202,3 +202,15 @@ export function isPreviewableSource(filePath) {
|
|
|
202
202
|
const dot = name.lastIndexOf('.');
|
|
203
203
|
return dot !== -1 && SOURCE_EXTENSIONS.has(name.slice(dot).toLowerCase());
|
|
204
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* A transcript row that carries a tool's *output* rather than the call that produced it.
|
|
207
|
+
*
|
|
208
|
+
* The two are separate `session_messages` rows and reach the phone as separate entries
|
|
209
|
+
* (src/transcript.ts), so three places have to agree on which is which: the phone folds
|
|
210
|
+
* a result onto its call, a rendered transcript prints the call and leaves the output
|
|
211
|
+
* behind, and `read_chat` does the same for an agent. Structural, because each of them
|
|
212
|
+
* holds a slightly different view of the same row.
|
|
213
|
+
*/
|
|
214
|
+
export function isToolResult(e) {
|
|
215
|
+
return e.role === 'tool' && !e.tool && e.output !== undefined;
|
|
216
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* tool_result blocks, never the user's own words. Real prompts are the
|
|
9
9
|
* plain-text rows. Never render an SDK user frame as a user bubble.
|
|
10
10
|
*/
|
|
11
|
+
import { isToolResult } from "./shared.js";
|
|
11
12
|
const clip = (s, n) => (s.length > n ? `${s.slice(0, n)}…` : s);
|
|
12
13
|
const str = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
|
13
14
|
/** Make tool details repo-relative: absolute worktree paths waste the whole line on a phone. */
|
|
@@ -35,16 +36,113 @@ function summarizeToolUse(name, input, worktree) {
|
|
|
35
36
|
return { text };
|
|
36
37
|
return { text, detail: stripWorktree(detail, worktree) };
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* How much of a tool's output travels to the phone.
|
|
41
|
+
*
|
|
42
|
+
* Output is the largest thing in the history — 799 MB of the 3,106 MB in
|
|
43
|
+
* `session_messages.content` (see src/search.ts) — and a chat's first transcript fetch
|
|
44
|
+
* carries the whole backlog at once, so this cap is what keeps opening a chat from
|
|
45
|
+
* paying for every file the agent ever read. Measured over the two biggest chats on
|
|
46
|
+
* this Mac: 335 results / 271 kB raw and 664 results / 1,732 kB raw, which this cap
|
|
47
|
+
* brings down to 193 kB and 569 kB. What is past it is worth reading in Conductor.
|
|
48
|
+
*/
|
|
49
|
+
const MAX_OUTPUT_CHARS = 2000;
|
|
50
|
+
/**
|
|
51
|
+
* Read a tool result, whatever shape it came in.
|
|
52
|
+
*
|
|
53
|
+
* Measured over the 40,000 most recent result blocks on this Mac: 35,709 are a plain
|
|
54
|
+
* string, 3,120 are Conductor's own edit result (`{status, diffString}`), 753 are text
|
|
55
|
+
* blocks, 205 are `tool_reference` lists and 80 carry an image. Only the first and third
|
|
56
|
+
* used to be read, so **3,917 results rendered as nothing at all** — an edit's diff, the
|
|
57
|
+
* tools a search found, and every screenshot, each of them a step that looked like it
|
|
58
|
+
* did nothing. An unknown shape falls back to its own JSON rather than to silence, which
|
|
59
|
+
* is what keeps Conductor drift visible instead of blank.
|
|
60
|
+
*/
|
|
61
|
+
function resultOutput(content, worktree) {
|
|
62
|
+
const plain = (text, diff = false) => ({
|
|
63
|
+
text: clip(text.replace(/<\/?tool_use_error>/g, '').trim(), MAX_OUTPUT_CHARS),
|
|
64
|
+
diff,
|
|
65
|
+
images: 0
|
|
66
|
+
});
|
|
40
67
|
if (typeof content === 'string')
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
68
|
+
return plain(content);
|
|
69
|
+
if (Array.isArray(content)) {
|
|
70
|
+
const said = [];
|
|
71
|
+
const tools = [];
|
|
72
|
+
let images = 0;
|
|
73
|
+
for (const raw of content) {
|
|
74
|
+
if (!raw || typeof raw !== 'object')
|
|
75
|
+
continue;
|
|
76
|
+
if (raw.type === 'image')
|
|
77
|
+
images++;
|
|
78
|
+
else if (str(raw.tool_name))
|
|
79
|
+
tools.push(str(raw.tool_name));
|
|
80
|
+
else if (typeof raw.text === 'string')
|
|
81
|
+
said.push(raw.text);
|
|
82
|
+
}
|
|
83
|
+
// A tool_reference list is the whole answer of a tool search; naming them beats the
|
|
84
|
+
// count, because which tools came back is the thing worth reading later.
|
|
85
|
+
if (tools.length)
|
|
86
|
+
said.push(`${plural(tools.length, 'tool')}: ${tools.join(', ')}`);
|
|
87
|
+
return { ...plain(said.join('')), images };
|
|
88
|
+
}
|
|
89
|
+
if (content && typeof content === 'object') {
|
|
90
|
+
const o = content;
|
|
91
|
+
// Conductor's edit result. The status names the file it wrote, so it is worth a line
|
|
92
|
+
// of its own above the hunks — worktree-relative, like every other path here.
|
|
93
|
+
const patch = str(o.diffString);
|
|
94
|
+
if (patch) {
|
|
95
|
+
const status = str(o.status);
|
|
96
|
+
const head = status ? `${stripWorktree(status, worktree)}\n` : '';
|
|
97
|
+
return plain(head + patch, true);
|
|
98
|
+
}
|
|
99
|
+
return plain(JSON.stringify(content));
|
|
100
|
+
}
|
|
101
|
+
return plain('');
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The images one row's tool results carry, in the order the transcript numbered them.
|
|
105
|
+
*
|
|
106
|
+
* The reference on an entry is `<rowid>.<n>`, and `n` counts image blocks across every
|
|
107
|
+
* `tool_result` in that row — so this walk and the one in `parseMessage` must stay the
|
|
108
|
+
* same walk, which is why they live in one file.
|
|
109
|
+
*/
|
|
110
|
+
export function toolImageAt(content, index) {
|
|
111
|
+
let parsed;
|
|
112
|
+
try {
|
|
113
|
+
parsed = JSON.parse(content);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
let seen = 0;
|
|
119
|
+
for (const b of parsed.message?.content ?? []) {
|
|
120
|
+
if (b.type !== 'tool_result' || !Array.isArray(b.content))
|
|
121
|
+
continue;
|
|
122
|
+
for (const raw of b.content) {
|
|
123
|
+
if (raw?.type !== 'image')
|
|
124
|
+
continue;
|
|
125
|
+
if (seen++ !== index)
|
|
126
|
+
continue;
|
|
127
|
+
const data = str(raw.source?.data);
|
|
128
|
+
if (!data)
|
|
129
|
+
return null;
|
|
130
|
+
return { mediaType: str(raw.source?.media_type) ?? sniffImageType(data), data };
|
|
131
|
+
}
|
|
46
132
|
}
|
|
47
|
-
return
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
/** Base64 magic bytes, for the image blocks that carry no `media_type`. */
|
|
136
|
+
function sniffImageType(base64) {
|
|
137
|
+
if (base64.startsWith('iVBOR'))
|
|
138
|
+
return 'image/png';
|
|
139
|
+
if (base64.startsWith('/9j/'))
|
|
140
|
+
return 'image/jpeg';
|
|
141
|
+
if (base64.startsWith('R0lGOD'))
|
|
142
|
+
return 'image/gif';
|
|
143
|
+
if (base64.startsWith('UklGR'))
|
|
144
|
+
return 'image/webp';
|
|
145
|
+
return 'application/octet-stream';
|
|
48
146
|
}
|
|
49
147
|
export function parseMessage(row, worktree = null) {
|
|
50
148
|
const queued = row.queue_order !== null && row.sent_at === null;
|
|
@@ -86,6 +184,9 @@ export function parseMessage(row, worktree = null) {
|
|
|
86
184
|
}
|
|
87
185
|
const entries = [];
|
|
88
186
|
const push = (e) => entries.push({ ...base, ...e, id: `${row.id}:${entries.length}` });
|
|
187
|
+
// Images are numbered per row, because that is all a reference needs to find one again
|
|
188
|
+
// (`toolImageAt`) and a row may hold several results.
|
|
189
|
+
let imageIndex = 0;
|
|
89
190
|
let pending = [];
|
|
90
191
|
const flush = () => {
|
|
91
192
|
const text = pending.join('\n').trim();
|
|
@@ -107,12 +208,36 @@ export function parseMessage(row, worktree = null) {
|
|
|
107
208
|
}
|
|
108
209
|
else if (b.type === 'tool_use' && typeof b.name === 'string') {
|
|
109
210
|
flush();
|
|
110
|
-
push({ role: 'tool', tool: b.name, ...summarizeToolUse(b.name, b.input, worktree) });
|
|
211
|
+
push({ role: 'tool', tool: b.name, toolUseId: str(b.id), ...summarizeToolUse(b.name, b.input, worktree) });
|
|
111
212
|
}
|
|
112
|
-
else if (b.type === 'tool_result'
|
|
113
|
-
//
|
|
213
|
+
else if (b.type === 'tool_result') {
|
|
214
|
+
// A result is written to a later row than the call it answers — anything slower than
|
|
215
|
+
// the 1s poll lands a tick or more behind it — so it travels as its own entry naming
|
|
216
|
+
// that call, and the phone folds the two together (web/src/lib/transcript-merge.ts).
|
|
217
|
+
// Only a failure repeats the output as `text`, which is what an unpaired one renders
|
|
218
|
+
// as. Repeating it for a success would send the biggest thing here twice: on the
|
|
219
|
+
// largest chat on this Mac that second copy was 569 kB of a 2.0 MB transcript.
|
|
114
220
|
flush();
|
|
115
|
-
|
|
221
|
+
const read = resultOutput(b.content, worktree);
|
|
222
|
+
// Bytes stay behind: a screenshot is ~100 kB of base64 in this row, and the phone
|
|
223
|
+
// fetches one only when the step it belongs to is opened (routes ▸ toolImage).
|
|
224
|
+
const images = Array.from({ length: read.images }, () => `${row.rowid}.${imageIndex++}`);
|
|
225
|
+
const output = read.text || (b.is_error ? '(tool error)' : '');
|
|
226
|
+
if (!output && !images.length)
|
|
227
|
+
continue;
|
|
228
|
+
const result = {
|
|
229
|
+
role: 'tool',
|
|
230
|
+
text: b.is_error ? output : '',
|
|
231
|
+
output,
|
|
232
|
+
toolUseId: str(b.tool_use_id)
|
|
233
|
+
};
|
|
234
|
+
if (read.diff)
|
|
235
|
+
result.diff = true;
|
|
236
|
+
if (images.length)
|
|
237
|
+
result.images = images;
|
|
238
|
+
if (b.is_error)
|
|
239
|
+
result.error = true;
|
|
240
|
+
push(result);
|
|
116
241
|
}
|
|
117
242
|
}
|
|
118
243
|
flush();
|
|
@@ -169,6 +294,13 @@ export function renderTranscript(entries, format) {
|
|
|
169
294
|
out.push(`[${parts.join(', ')} elided]`);
|
|
170
295
|
};
|
|
171
296
|
for (const e of entries) {
|
|
297
|
+
// A successful result is the output of the call printed just above it, and output is
|
|
298
|
+
// the biggest half of a chat (src/search.ts) as well as the least re-readable. So a
|
|
299
|
+
// render carries the call and leaves the file dumps behind — this is not an elision
|
|
300
|
+
// of a tool *call*, so it is not counted as one. A failure still prints: one line,
|
|
301
|
+
// and it is often why the answer changed course.
|
|
302
|
+
if (isToolResult(e) && !e.error)
|
|
303
|
+
continue;
|
|
172
304
|
if (e.role === 'thinking' && !format.thinking) {
|
|
173
305
|
pending.thinking++;
|
|
174
306
|
elided.thinking++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.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.",
|