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-B-iVHSJK.js"></script>
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:"a31c9bc462c8a6e8367a63165f95b1b0"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/index-Dc8zl3H4.css",revision:null},{url:"assets/index-B-iVHSJK.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\//]}))});
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 replies, not tool output. Results carry workspace_id and session_id for read_chat.',
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: {
@@ -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 1.2% of the transcript.** Measured over this Mac's 1.7M
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, thinking 77 MB and
14
- * what a person would ever search for, the assistant's own words plus the prompts
15
- * they typed, is **38 MB**. So this indexes prose only. A grep of the raw column
16
- * would be a 3 GB scan to search 38 MB, and it would rank a file dump the agent
17
- * happened to `cat` above the sentence that explained the decision.
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). Measured
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 = 1;
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 98.8% of the bytes.
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 (entry.role !== 'user' && entry.role !== 'assistant')
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
- role: r.role === 'user' ? 'user' : 'assistant',
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.44.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.",