conductor-remote 1.131.0 → 1.131.2

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
@@ -25,7 +25,7 @@
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-CL5zdosq.js"></script>
28
+ <script type="module" crossorigin src="/assets/index-fUZj9cRu.js"></script>
29
29
  <link rel="stylesheet" crossorigin href="/assets/index-Dbu5DOed.css">
30
30
  <link rel="manifest" href="/manifest.webmanifest"></head>
31
31
  <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 l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const t=e=>i(e,l),a={module:{uri:l},exports:o,require:t};s[l]=Promise.all(n.map(e=>a[e]||t(e))).then(e=>(r(...e),o))}}define(["./workbox-dcde9eb3"],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:"4e0b714c0ea53e85c167e6d8f5b767aa"},{url:"index.html",revision:"294f915bf658c37239fb5a9fff8084a7"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/pierre-shiki-wasm-C8ETeZem.js",revision:null},{url:"assets/pierre-light-480U9XYS.js",revision:null},{url:"assets/pierre-dark-CyvmCCZW.js",revision:null},{url:"assets/index-Dbu5DOed.css",revision:null},{url:"assets/index-CL5zdosq.js",revision:null},{url:"assets/PierrePatch-CbrLXTO-.js",revision:null},{url:"assets/AutoModelSettings-DEMM_Z8d.js",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\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/assets/diff-syntax/"),new e.CacheFirst({cacheName:"diff-syntax",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:31536e3})]}),"GET")});
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 l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const t=e=>i(e,l),a={module:{uri:l},exports:o,require:t};s[l]=Promise.all(n.map(e=>a[e]||t(e))).then(e=>(r(...e),o))}}define(["./workbox-dcde9eb3"],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:"4e0b714c0ea53e85c167e6d8f5b767aa"},{url:"index.html",revision:"5373066373ca133cbf24521227792444"},{url:"assets/workbox-window.prod.es5-BBnX5xw4.js",revision:null},{url:"assets/pierre-shiki-wasm-C8ETeZem.js",revision:null},{url:"assets/pierre-light-480U9XYS.js",revision:null},{url:"assets/pierre-dark-CyvmCCZW.js",revision:null},{url:"assets/index-fUZj9cRu.js",revision:null},{url:"assets/index-Dbu5DOed.css",revision:null},{url:"assets/PierrePatch-Dzam9HOM.js",revision:null},{url:"assets/AutoModelSettings-BeVICf6p.js",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\//]})),e.registerRoute(({url:e})=>e.pathname.startsWith("/assets/diff-syntax/"),new e.CacheFirst({cacheName:"diff-syntax",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:31536e3})]}),"GET")});
@@ -98,8 +98,20 @@ export function createDelegationsServices(services) {
98
98
  }
99
99
  return { ok: true };
100
100
  }
101
+ /** Correlate an ordinary delegation without treating changed text as successful delivery. */
102
+ function trackedReceipt(sessionId, text, delivery, attachment) {
103
+ if (delivery.messageId)
104
+ return reads.deliveryReceiptForId(sessionId, delivery.messageId);
105
+ const cursor = { rowid: delivery.rowid, outboxIds: new Set(delivery.outboxIds) };
106
+ // Each handoff/report has its own persisted attachment directory. Its ASCII
107
+ // URL survives UTF-8 chunk corruption and also identifies sends from older relays.
108
+ // This is correlation only: verify the complete raw text after saving the id.
109
+ const marker = attachment ? `(${encodeURIComponent(attachment.path)})` : '';
110
+ return (reads.deliveryReceiptSince(sessionId, text, cursor) ??
111
+ (marker && text.includes(marker) ? reads.deliveryReceiptContainingSince(sessionId, marker, cursor) : null));
112
+ }
101
113
  /** Prepare durably before sending, then follow the accepted id without spending retries. */
102
- async function trackedPrompt(ws, sessionId, text, delivery, code, queue = false) {
114
+ async function trackedPrompt(ws, sessionId, text, delivery, code, attachment, queue = false) {
103
115
  if (!delivery) {
104
116
  const cursor = reads.deliveryCursor(sessionId);
105
117
  return {
@@ -109,9 +121,8 @@ export function createDelegationsServices(services) {
109
121
  };
110
122
  }
111
123
  const cursor = { rowid: delivery.rowid, outboxIds: new Set(delivery.outboxIds) };
112
- let receipt = delivery.messageId
113
- ? reads.deliveryReceiptForId(sessionId, delivery.messageId)
114
- : reads.deliveryReceiptSince(sessionId, text, cursor);
124
+ const probe = () => trackedReceipt(sessionId, text, delivery, attachment);
125
+ let receipt = probe();
115
126
  if (!receipt && delivery.messageId) {
116
127
  return delegationError(code, 'the accepted delegated message was removed from Conductor before dispatch', false);
117
128
  }
@@ -126,8 +137,8 @@ export function createDelegationsServices(services) {
126
137
  queue: true
127
138
  }));
128
139
  receipt = result.ok
129
- ? reads.deliveryReceiptSince(sessionId, text, cursor)
130
- : await confirmDelivery(sessionId, text, cursor, Date.now() + CONFIRM_WINDOW_MS);
140
+ ? probe()
141
+ : await confirmDelivery(sessionId, text, cursor, Date.now() + CONFIRM_WINDOW_MS, probe);
131
142
  if (!receipt) {
132
143
  return result.ok
133
144
  ? { ok: true, pending: true, delivery: { ...delivery, accepted: true } }
@@ -135,11 +146,17 @@ export function createDelegationsServices(services) {
135
146
  }
136
147
  }
137
148
  if (!receipt) {
138
- const result = await withUiPriority('background', () => deliverPrompt(ws, sessionId, text, SEND_BUDGET_MS, false, cursor));
149
+ const result = await withUiPriority('background', () => deliverPrompt(ws, sessionId, text, SEND_BUDGET_MS, false, cursor, probe));
139
150
  if (!result.ok)
140
151
  return delegationError(code, result.error ?? 'the delegated message did not land');
141
152
  receipt = result.receipt;
142
153
  }
154
+ if (delivery.messageId !== receipt.id) {
155
+ return { ok: true, pending: true, delivery: { ...delivery, messageId: receipt.id } };
156
+ }
157
+ if (!reads.deliveryTextMatches(sessionId, receipt.id, text)) {
158
+ return delegationError('delivery_altered', `Conductor accepted the delegated ${code === 'send_failed' ? 'assignment' : 'result'} as message ${receipt.id}, but its text changed in transit. It will not be resent automatically. Inspect chat ${sessionId} before continuing.`, false);
159
+ }
143
160
  return receipt.kind === 'message'
144
161
  ? { ok: true, rowid: receipt.rowid }
145
162
  : { ok: true, pending: true, delivery: { ...delivery, messageId: receipt.id } };
@@ -157,7 +174,7 @@ export function createDelegationsServices(services) {
157
174
  catch (err) {
158
175
  return delegationError('state_invalid', err instanceof Error ? err.message : String(err), false);
159
176
  }
160
- const result = await trackedPrompt(ws, job.childSessionId, text, job.sendDelivery, 'send_failed');
177
+ const result = await trackedPrompt(ws, job.childSessionId, text, job.sendDelivery, 'send_failed', job.handoff);
161
178
  if (!result.ok)
162
179
  return result;
163
180
  return 'pending' in result
@@ -237,7 +254,7 @@ export function createDelegationsServices(services) {
237
254
  catch (err) {
238
255
  return delegationError('return_failed', err instanceof Error ? err.message : String(err), false);
239
256
  }
240
- const result = await trackedPrompt(ws, job.parentSessionId, text, job.returnDelivery, 'return_failed', job.returnMode === 'queue');
257
+ const result = await trackedPrompt(ws, job.parentSessionId, text, job.returnDelivery, 'return_failed', attachment, job.returnMode === 'queue');
241
258
  if (!result.ok)
242
259
  return result;
243
260
  return 'pending' in result
@@ -252,6 +269,22 @@ export function createDelegationsServices(services) {
252
269
  : { ok: true, returnRowid: result.rowid };
253
270
  }
254
271
  const delegationQueue = new DelegationQueue({
272
+ reconcile: job => {
273
+ const returning = job.failure?.code === 'return_failed';
274
+ if (!returning && job.failure?.code !== 'send_failed')
275
+ return null;
276
+ const delivery = returning ? job.returnDelivery : job.sendDelivery;
277
+ const sessionId = returning ? job.parentSessionId : job.childSessionId;
278
+ if (!delivery || !sessionId)
279
+ return null;
280
+ const text = returning ? job.returnText : delegatedPrompt(job);
281
+ if (!text)
282
+ return null;
283
+ const receipt = trackedReceipt(sessionId, text, delivery, returning ? job.returnAttachment : job.handoff);
284
+ return receipt
285
+ ? { stage: returning ? 'returning' : 'sending', delivery: { ...delivery, messageId: receipt.id } }
286
+ : null;
287
+ },
255
288
  open: openDelegation,
256
289
  configure: configureDelegation,
257
290
  send: sendDelegation,
@@ -41,10 +41,10 @@ export function createDeliveryServices(services) {
41
41
  * a confirm *followed by another attempt* always gets its full window. Only the
42
42
  * last confirm of all can be cut short, and nothing follows it to duplicate a row.
43
43
  */
44
- async function confirmDelivery(sessionId, text, since, budgetDeadline) {
44
+ async function confirmDelivery(sessionId, text, since, budgetDeadline, receiptProbe = () => deliveredSince(sessionId, text, since)) {
45
45
  const stopAt = Math.min(Date.now() + CONFIRM_WINDOW_MS, budgetDeadline);
46
46
  for (;;) {
47
- const receipt = deliveredSince(sessionId, text, since);
47
+ const receipt = receiptProbe();
48
48
  if (receipt)
49
49
  return receipt;
50
50
  if (Date.now() >= stopAt)
@@ -166,7 +166,7 @@ export function createDeliveryServices(services) {
166
166
  * minutes schedule sits *outside* this and is unaffected — it retries a delivery that
167
167
  * never got off the ground (worktree still setting up), not one Conductor fumbled.
168
168
  */
169
- async function deliverPrompt(ws, sessionId, text, budgetMs = SEND_BUDGET_MS, queue = false, cursor) {
169
+ async function deliverPrompt(ws, sessionId, text, budgetMs = SEND_BUDGET_MS, queue = false, cursor, receiptProbe) {
170
170
  const located = locateChat(ws, sessionId);
171
171
  if ('error' in located)
172
172
  return { ok: false, strategy: actuator.name, attempts: 0, error: located.error };
@@ -174,6 +174,7 @@ export function createDeliveryServices(services) {
174
174
  // *this* prompt arrive since we started", so a retry can't be fooled by an older
175
175
  // identical prompt moving from the outbox into a new transcript row.
176
176
  const before = cursor ?? reads.deliveryCursor(sessionId);
177
+ const probe = receiptProbe ?? (() => deliveredSince(sessionId, text, before));
177
178
  const label = ws.branch ?? ws.id;
178
179
  const deadline = Date.now() + budgetMs;
179
180
  let attempts = 0;
@@ -181,7 +182,7 @@ export function createDeliveryServices(services) {
181
182
  for (;;) {
182
183
  // A persisted delegation cursor can already have a receipt after a restart
183
184
  // or a late acceptance. Recover it before touching the composer again.
184
- const existing = deliveredSince(sessionId, text, before);
185
+ const existing = probe();
185
186
  if (existing)
186
187
  return { ok: true, strategy: last.strategy, attempts, receipt: existing };
187
188
  attempts++;
@@ -198,8 +199,8 @@ export function createDeliveryServices(services) {
198
199
  // an *earlier* attempt's row can be arriving, and typing again over that is the
199
200
  // duplicate this whole path exists to avoid.
200
201
  const landed = sendNeverStarted(last.error)
201
- ? deliveredSince(sessionId, text, before)
202
- : await confirmDelivery(sessionId, text, before, deadline);
202
+ ? probe()
203
+ : await confirmDelivery(sessionId, text, before, deadline, probe);
203
204
  if (landed) {
204
205
  if (attempts > 1)
205
206
  console.info(`[relay] send to ${label} landed on attempt ${attempts}`);
@@ -220,7 +221,7 @@ export function createDeliveryServices(services) {
220
221
  }
221
222
  const tried = attempts > 1 ? ` (tried ${attempts}×)` : '';
222
223
  const error = last.ok
223
- ? `Send didn’t land in the chat Conductor may have been asleep or unfocused${tried}. Try again.`
224
+ ? `Could not confirm the sent message in Conductor${tried}. Check the chat before trying again.`
224
225
  : `${last.error}${tried}`;
225
226
  console.warn(`[relay] send to ${label} failed after ${attempts} attempt(s): ${error}`);
226
227
  return { ok: false, strategy: last.strategy, attempts, error };
@@ -29,6 +29,7 @@ const ERROR_CODES = new Set([
29
29
  'opening_failed',
30
30
  'configuration_failed',
31
31
  'send_failed',
32
+ 'delivery_altered',
32
33
  'completion_failed',
33
34
  'return_failed'
34
35
  ]);
@@ -129,8 +129,28 @@ export class DelegationQueue {
129
129
  .flatMap(store => store.list().jobs.map(job => ({ store, job })))
130
130
  .sort((a, b) => a.job.createdAt - b.job.createdAt || a.job.id.localeCompare(b.job.id));
131
131
  for (const { store, job } of entries) {
132
- if (job.status === 'failed')
133
- continue;
132
+ if (job.status === 'failed') {
133
+ try {
134
+ const recovered = this.deps.reconcile?.(job);
135
+ if (!recovered)
136
+ continue;
137
+ // Only a positive read-only receipt reopens a terminal send failure.
138
+ // The normal delivery stage verifies integrity before observing completion.
139
+ store.put(decodeDelegation({
140
+ ...job,
141
+ status: recovered.stage,
142
+ [recovered.stage === 'sending' ? 'sendDelivery' : 'returnDelivery']: recovered.delivery,
143
+ failure: undefined,
144
+ lastAttemptAt: undefined,
145
+ updatedAt: this.now()
146
+ }));
147
+ return true;
148
+ }
149
+ catch (err) {
150
+ this.onError(`delegation reconciliation failed: ${err instanceof Error ? err.message : err}`);
151
+ continue;
152
+ }
153
+ }
134
154
  if (job.status === 'returned') {
135
155
  store.remove(job.id);
136
156
  return true;
@@ -254,6 +254,19 @@ export class MessageReads {
254
254
  const accepted = this.rawOutboxRows(sessionId).find(row => this.outboxText(row.delivery_payload)?.includes(marker) && !before.outboxIds.has(row.message_id));
255
255
  return accepted ? { kind: 'outbox', id: accepted.message_id } : durableReceipt();
256
256
  }
257
+ /** Compare an identified delivery against raw text without exposing private envelopes. */
258
+ // Recheck the durable row if promotion straddles the two reads.
259
+ deliveryTextMatches(sessionId, messageId, text) {
260
+ const target = text.trim();
261
+ const durable = () => this.deliveryMessageRows('id = ?', [sessionId, messageId])[0];
262
+ const row = durable();
263
+ if (row)
264
+ return row.content?.trim() === target;
265
+ const queued = this.rawOutboxRows(sessionId, messageId)[0];
266
+ if (queued)
267
+ return this.outboxText(queued.delivery_payload)?.trim() === target;
268
+ return durable()?.content?.trim() === target;
269
+ }
257
270
  /** Follow one accepted id as Conductor promotes it from outbox to transcript. */
258
271
  deliveryReceiptForId(sessionId, messageId) {
259
272
  if (!this.hasMessageOutbox()) {
@@ -75,6 +75,9 @@ export class Reads {
75
75
  deliveryReceiptForId(sessionId, messageId) {
76
76
  return this.messages.deliveryReceiptForId(sessionId, messageId);
77
77
  }
78
+ deliveryTextMatches(sessionId, messageId, text) {
79
+ return this.messages.deliveryTextMatches(sessionId, messageId, text);
80
+ }
78
81
  getMessages(sessionId, afterRowid = 0) {
79
82
  return this.messages.getMessages(sessionId, afterRowid);
80
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-remote",
3
- "version": "1.131.0",
3
+ "version": "1.131.2",
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.",