conductor-remote 1.44.1 → 1.45.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/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-C2YFo1dc.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-Dc8zl3H4.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:"49e02e6a417c0e62df0c7bf8afc87237"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-Dc8zl3H4.css",revision:null},{url:"assets/index-C2YFo1dc.js",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\//]}))});
|
|
@@ -75,7 +75,7 @@ export function createTools(call) {
|
|
|
75
75
|
return [
|
|
76
76
|
{
|
|
77
77
|
name: 'search_chats',
|
|
78
|
-
description: 'Full-text search every Conductor chat on this Mac, archived workspaces included, and get back the workspaces that discussed it with the matching excerpts. Use this to answer "which workspace did I do X in" or "what did we decide about X". Searches the prompts the user typed and the agent
|
|
78
|
+
description: 'Full-text search every Conductor chat on this Mac, archived workspaces included, and get back the workspaces that discussed it with the matching excerpts. Use this to answer "which workspace did I do X in" or "what did we decide about X". Searches the prompts the user typed, the agent replies and the agent\'s reasoning, not tool output. Each excerpt is tagged [user], [assistant] or [thinking] — a [thinking] hit is reasoning the agent never said out loud, so do not quote it back as its answer. Results carry workspace_id and session_id for read_chat.',
|
|
79
79
|
inputSchema: {
|
|
80
80
|
type: 'object',
|
|
81
81
|
properties: {
|
package/dist-node/src/search.js
CHANGED
|
@@ -8,25 +8,42 @@ import { parseMessage } from "./transcript.js";
|
|
|
8
8
|
*
|
|
9
9
|
* Two facts decide the whole shape of this file.
|
|
10
10
|
*
|
|
11
|
-
* **Prose is
|
|
11
|
+
* **Prose is a few percent of the transcript.** Measured over this Mac's 1.7M
|
|
12
12
|
* `session_messages` rows (3,106 MB of `content`): tool_result output is 799 MB,
|
|
13
|
-
* `type:"system"` frames 522 MB, tool_use arguments 162 MB
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* `type:"system"` frames 522 MB, tool_use arguments 162 MB — and what a person
|
|
14
|
+
* would ever search for is prose. So this indexes prose only. A grep of the raw
|
|
15
|
+
* column would be a 3 GB scan, and it would rank a file dump the agent happened
|
|
16
|
+
* to `cat` above the sentence that explained the decision.
|
|
17
|
+
*
|
|
18
|
+
* **Thinking counts as prose, and it is the bigger half.** Re-measured 2026-08-25:
|
|
19
|
+
* assistant text is 36.9 MB over 93,189 blocks, typed prompts 2.6 MB over 19,432
|
|
20
|
+
* rows, and thinking **82.7 MB over 102,776 blocks** — more than the other two
|
|
21
|
+
* together. Skipping it was the original cut and it was wrong: the chat view
|
|
22
|
+
* *renders* thinking, so a hit there opens to something you can read, and the
|
|
23
|
+
* reasoning is where a decision gets explained before the reply summarises it.
|
|
24
|
+
* Note the shape trap behind the source query below: **every thinking block sits
|
|
25
|
+
* in a row with no text block beside it** (0 of 102,773 rows carry both), so a
|
|
26
|
+
* prefilter written for `"type":"text"` excludes 100% of thinking rather than
|
|
27
|
+
* some of it — the bug that made this cut invisible.
|
|
28
|
+
*
|
|
29
|
+
* The cost is paid up front and it is bounded. Same machine, same history, five
|
|
30
|
+
* real queries at `CHUNK_LIMIT`: 112,571 chunks → **208,131**, 77 MB → 230 MB,
|
|
31
|
+
* 7.6s → **12.3s** to build, and 12–57ms → **25–137ms** per query. The worst case
|
|
32
|
+
* still fits inside the phone's 250ms search-as-you-type debounce, which is the
|
|
33
|
+
* only latency budget that binds here. (The 1–7ms this comment used to claim was
|
|
34
|
+
* measured on a smaller history and no longer held even before thinking: v1
|
|
35
|
+
* measures 12–57ms today.)
|
|
18
36
|
*
|
|
19
37
|
* **`node:sqlite` ships FTS5.** Porter stemming, `bm25()`, `snippet()`, `NEAR()`
|
|
20
38
|
* all work on the bundled SQLite (3.51.2), so the index costs no runtime
|
|
21
|
-
* dependency — which the tarball rule requires (see CLAUDE.md ▸ Traps).
|
|
22
|
-
* on the full history: 7.6s to build, 111,079 chunks, queries in 1–7ms.
|
|
39
|
+
* dependency — which the tarball rule requires (see CLAUDE.md ▸ Traps).
|
|
23
40
|
*
|
|
24
41
|
* The index is **never** written into `conductor.db`. That handle is read-only and
|
|
25
42
|
* stays that way; this opens its own file under the relay's state dir, and it is
|
|
26
43
|
* disposable — delete it and the next start rebuilds it.
|
|
27
44
|
*/
|
|
28
45
|
/** Bump to force a rebuild: a tokenizer or extraction change makes every stored chunk wrong. */
|
|
29
|
-
const SCHEMA_VERSION =
|
|
46
|
+
const SCHEMA_VERSION = 2;
|
|
30
47
|
/**
|
|
31
48
|
* Source rows advanced per tick. The cursor moves by *scanned* rowid rather than
|
|
32
49
|
* matched rowid, so a caught-up index re-scans nothing — get that wrong and every
|
|
@@ -49,6 +66,8 @@ const CHUNK_LIMIT = 300;
|
|
|
49
66
|
* must not render them literally.
|
|
50
67
|
*/
|
|
51
68
|
export { HIT_CLOSE, HIT_OPEN } from "./shared.js";
|
|
69
|
+
/** The `TranscriptEntry` roles this index keeps, and the set `search()` maps a stored role back through. */
|
|
70
|
+
const INDEXED_ROLES = new Set(['user', 'assistant', 'thinking']);
|
|
52
71
|
/**
|
|
53
72
|
* Turn a phone query into an FTS5 MATCH expression.
|
|
54
73
|
*
|
|
@@ -178,12 +197,14 @@ export class SearchIndex {
|
|
|
178
197
|
return false;
|
|
179
198
|
}
|
|
180
199
|
const end = window[window.length - 1].rowid;
|
|
181
|
-
// Only rows that can hold prose: a plain-text prompt, or a frame carrying a text
|
|
182
|
-
// block. Everything else is tool plumbing and
|
|
200
|
+
// Only rows that can hold prose: a plain-text prompt, or a frame carrying a text or
|
|
201
|
+
// thinking block. Everything else is tool plumbing and the bulk of the bytes.
|
|
202
|
+
// The thinking clause is not redundant with the text one — the two block types never
|
|
203
|
+
// share a row (see this file's header), so dropping it drops thinking entirely.
|
|
183
204
|
const rows = this.source.query(`SELECT rowid, id, session_id, role, content, full_message, created_at, sent_at, queue_order
|
|
184
205
|
FROM session_messages
|
|
185
206
|
WHERE rowid > ? AND rowid <= ? AND session_id IS NOT NULL
|
|
186
|
-
AND (role = 'user' OR content LIKE '%"type":"text"%')
|
|
207
|
+
AND (role = 'user' OR content LIKE '%"type":"text"%' OR content LIKE '%"type":"thinking"%')
|
|
187
208
|
ORDER BY rowid`, [this.cursor, end]);
|
|
188
209
|
const insert = db.prepare('INSERT INTO chunks(body, session_id, src_rowid, role, at) VALUES (?, ?, ?, ?, ?)');
|
|
189
210
|
db.exec('BEGIN');
|
|
@@ -194,7 +215,7 @@ export class SearchIndex {
|
|
|
194
215
|
// words, and indexing something the chat view would never show is how a search
|
|
195
216
|
// result becomes impossible to find once you open it.
|
|
196
217
|
for (const entry of parseMessage(row, null)) {
|
|
197
|
-
if (
|
|
218
|
+
if (!INDEXED_ROLES.has(entry.role))
|
|
198
219
|
continue;
|
|
199
220
|
const body = entry.text.trim();
|
|
200
221
|
if (!body)
|
|
@@ -254,7 +275,9 @@ export class SearchIndex {
|
|
|
254
275
|
return rows.map(r => ({
|
|
255
276
|
sessionId: r.session_id,
|
|
256
277
|
srcRowid: Number(r.src_rowid),
|
|
257
|
-
|
|
278
|
+
// An index written before SCHEMA_VERSION 2 is dropped on open, so an unknown role
|
|
279
|
+
// here is a bug rather than an old row — fall back to the neutral one.
|
|
280
|
+
role: INDEXED_ROLES.has(r.role) ? r.role : 'assistant',
|
|
258
281
|
at: r.at,
|
|
259
282
|
score: Number(r.score),
|
|
260
283
|
snippet: r.snippet
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-remote",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.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.",
|