esque-bridge 0.6.13 → 0.6.14
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/index.js +63 -2
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -191,6 +191,47 @@ function clearCliSessionId(agent, esqueSessionId) {
|
|
|
191
191
|
saveSessions();
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
// --- Handoff log ----------------------------------------------------------
|
|
195
|
+
// A running, on-disk record of each turn (prompt + what the agent did), kept in
|
|
196
|
+
// the project folder. Its whole purpose is to survive a lost CLI session: if
|
|
197
|
+
// the agent's conversation memory is ever GC'd, the fresh session is told to
|
|
198
|
+
// read this file and recover the project's context — so a memory reset doesn't
|
|
199
|
+
// mean starting from zero. Best-effort; bounded so it stays readable.
|
|
200
|
+
const HANDOFF_MAX = 48 * 1024;
|
|
201
|
+
function handoffRel(esqueSessionId) {
|
|
202
|
+
const short = String(esqueSessionId || 'default').replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 16) || 'default';
|
|
203
|
+
return `.esque/handoff-${short}.md`;
|
|
204
|
+
}
|
|
205
|
+
function handoffPath(esqueSessionId) {
|
|
206
|
+
return path.join(WORKDIR, handoffRel(esqueSessionId));
|
|
207
|
+
}
|
|
208
|
+
function appendHandoff(esqueSessionId, prompt, replyText) {
|
|
209
|
+
if (!esqueSessionId) return;
|
|
210
|
+
try {
|
|
211
|
+
fs.mkdirSync(path.join(WORKDIR, '.esque'), { recursive: true });
|
|
212
|
+
const file = handoffPath(esqueSessionId);
|
|
213
|
+
const header = fs.existsSync(file)
|
|
214
|
+
? ''
|
|
215
|
+
: "# Esque handoff log\n\n> A running record of this project's work so your AI agent can recover context if its session is ever reset. Safe to delete (or add `.esque/` to .gitignore).\n\n";
|
|
216
|
+
const reply = String(replyText || '').trim().replace(/\n{3,}/g, '\n\n').slice(0, 1200);
|
|
217
|
+
const stamp = new Date().toISOString();
|
|
218
|
+
fs.appendFileSync(
|
|
219
|
+
file,
|
|
220
|
+
`${header}## ${stamp}\n\n**Prompt:** ${String(prompt || '').slice(0, 600)}\n\n**Agent:** ${reply || '(no output)'}\n\n---\n\n`,
|
|
221
|
+
);
|
|
222
|
+
// Keep it bounded: on overflow, retain the most recent entries (trim to a
|
|
223
|
+
// clean entry boundary so the recovered context reads cleanly).
|
|
224
|
+
const buf = fs.readFileSync(file, 'utf8');
|
|
225
|
+
if (buf.length > HANDOFF_MAX) {
|
|
226
|
+
const tail = buf.slice(buf.length - HANDOFF_MAX);
|
|
227
|
+
const cut = tail.indexOf('\n## ');
|
|
228
|
+
fs.writeFileSync(file, '# Esque handoff log (older entries trimmed)\n\n' + (cut >= 0 ? tail.slice(cut + 1) : tail));
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
/* best-effort — never let handoff bookkeeping break a turn */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
194
235
|
// --- Adapters -------------------------------------------------------------
|
|
195
236
|
// Each adapter describes how to invoke a CLI for a single prompt. The
|
|
196
237
|
// runner is identical across adapters; only argv-building and stdout
|
|
@@ -542,7 +583,23 @@ async function runAgentResilient(prompt, esqueSessionId) {
|
|
|
542
583
|
if (prevId && RESUME_FAIL_RE.test(String(err && err.message))) {
|
|
543
584
|
console.warn('[bridge] stored CLI session is gone — retrying as a fresh session');
|
|
544
585
|
clearCliSessionId(AGENT_TYPE, esqueSessionId);
|
|
545
|
-
|
|
586
|
+
// The agent lost its memory. If we've been keeping a handoff log, point
|
|
587
|
+
// the fresh session at it FIRST so it recovers the project's context
|
|
588
|
+
// instead of starting from zero.
|
|
589
|
+
let recovered = prompt;
|
|
590
|
+
try {
|
|
591
|
+
if (fs.existsSync(handoffPath(esqueSessionId))) {
|
|
592
|
+
recovered =
|
|
593
|
+
`[Esque session recovery] Your previous conversation in this project was reset and its in-memory context was lost. ` +
|
|
594
|
+
`BEFORE anything else, read the file \`${handoffRel(esqueSessionId)}\` in this folder — a running log of everything done in this project so far — and use it to reconstruct context. Then carry out this request:\n\n${prompt}`;
|
|
595
|
+
}
|
|
596
|
+
} catch {
|
|
597
|
+
/* fall back to the bare prompt */
|
|
598
|
+
}
|
|
599
|
+
const fresh = await runAgent(recovered, esqueSessionId);
|
|
600
|
+
// Tell the phone the agent's memory was reset (it ignores the field if
|
|
601
|
+
// unknown). Older bridges never set it.
|
|
602
|
+
return { ...fresh, sessionReset: true };
|
|
546
603
|
}
|
|
547
604
|
throw err;
|
|
548
605
|
}
|
|
@@ -1284,11 +1341,13 @@ async function executeHandler(req, res) {
|
|
|
1284
1341
|
jobs.set(jobId, {
|
|
1285
1342
|
status: result.isError ? 'blocked' : 'finished',
|
|
1286
1343
|
text: result.text,
|
|
1344
|
+
sessionReset: !!result.sessionReset,
|
|
1287
1345
|
createdAt: Date.now(),
|
|
1288
1346
|
});
|
|
1289
1347
|
console.log(
|
|
1290
1348
|
`[bridge] done job=${jobId} ${result.isError ? 'blocked' : 'finished'}`,
|
|
1291
1349
|
);
|
|
1350
|
+
if (!result.isError) appendHandoff(esqueSessionId, prompt, result.text);
|
|
1292
1351
|
const ok = !result.isError;
|
|
1293
1352
|
sendExpoPush(
|
|
1294
1353
|
pushToken,
|
|
@@ -1335,9 +1394,11 @@ async function executeHandler(req, res) {
|
|
|
1335
1394
|
|
|
1336
1395
|
try {
|
|
1337
1396
|
const result = await enqueueRun(prompt, esqueSessionId);
|
|
1397
|
+
if (!result.isError) appendHandoff(esqueSessionId, prompt, result.text);
|
|
1338
1398
|
finish({
|
|
1339
1399
|
text: result.text,
|
|
1340
1400
|
status: result.isError ? 'blocked' : 'finished',
|
|
1401
|
+
sessionReset: !!result.sessionReset,
|
|
1341
1402
|
});
|
|
1342
1403
|
} catch (err) {
|
|
1343
1404
|
console.error('[bridge] error:', err.message);
|
|
@@ -1355,7 +1416,7 @@ function resultHandler(req, res) {
|
|
|
1355
1416
|
text: 'That task is no longer available — the bridge may have restarted.',
|
|
1356
1417
|
});
|
|
1357
1418
|
}
|
|
1358
|
-
res.json({ status: job.status, text: job.text });
|
|
1419
|
+
res.json({ status: job.status, text: job.text, sessionReset: !!job.sessionReset });
|
|
1359
1420
|
}
|
|
1360
1421
|
|
|
1361
1422
|
// Single-flight wrapper for reviving the saved preview: GET /preview and the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esque-bridge",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.14",
|
|
4
4
|
"description": "Desktop-side receiver for the Esque Agent mobile app. Pairs your phone with a local coding-agent CLI (Claude Code, Codex, Aider, or any custom command) via a tunnel + QR code, so prompts run through your subscription instead of per-token API billing.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"esque-bridge": "index.js"
|