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/assets/{AutoModelSettings-DEMM_Z8d.js → AutoModelSettings-BeVICf6p.js} +1 -1
- package/dist/assets/{PierrePatch-CbrLXTO-.js → PierrePatch-Dzam9HOM.js} +1 -1
- package/dist/assets/{index-CL5zdosq.js → index-fUZj9cRu.js} +32 -32
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/dist-node/src/http/services/delegations.js +42 -9
- package/dist-node/src/http/services/delivery.js +8 -7
- package/dist-node/src/orchestration/delegation/codec.js +1 -0
- package/dist-node/src/orchestration/delegation/queue.js +22 -2
- package/dist-node/src/reads/messages.js +13 -0
- package/dist-node/src/reads/repository.js +3 -0
- package/package.json +1 -1
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-
|
|
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:"
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
?
|
|
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 =
|
|
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 =
|
|
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
|
-
?
|
|
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
|
-
? `
|
|
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 };
|
|
@@ -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
|
-
|
|
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.
|
|
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.",
|