esque-bridge 0.2.0 → 0.2.1

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.
Files changed (2) hide show
  1. package/index.js +43 -5
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -138,7 +138,20 @@ const ADAPTERS = {
138
138
  // `--output-format json` returns a single JSON object with
139
139
  // {result, session_id, is_error, ...} — easy to parse, exact.
140
140
  buildArgs(_prompt, prevSessionId) {
141
- const args = ['--print', '--output-format', 'json'];
141
+ // `--dangerously-skip-permissions` lets Claude actually use its
142
+ // Write / Edit / Bash tools without an interactive approval prompt.
143
+ // In headless `--print` mode there's no way to say "yes" to a
144
+ // permission request, so without this every file write is silently
145
+ // blocked — the agent looks busy but can't touch the disk. This is
146
+ // the autonomous-bridge use case the flag exists for; access is
147
+ // already gated by the per-session pairing secret. (Mirrors Aider's
148
+ // `--yes-always` below.)
149
+ const args = [
150
+ '--print',
151
+ '--output-format',
152
+ 'json',
153
+ '--dangerously-skip-permissions',
154
+ ];
142
155
  if (prevSessionId) args.push('--resume', prevSessionId);
143
156
  return args;
144
157
  },
@@ -371,17 +384,42 @@ async function executeHandler(req, res) {
371
384
  `[bridge] POST ${preview}… esque=${esqueSessionId ?? '-'} ${AGENT_TYPE}=${prev ?? 'new'}`,
372
385
  );
373
386
 
387
+ // Stream a keep-alive heartbeat while the agent thinks. `claude --print`
388
+ // emits nothing until it finishes (often 30–120s on a cold start with a big
389
+ // prompt), and localtunnel / intervening proxies close *idle* connections —
390
+ // which surfaced on the phone as "fetch request has been canceled". A space
391
+ // every few seconds keeps the connection active. The phone trims the body
392
+ // before JSON.parse, so leading whitespace is harmless.
393
+ res.status(200);
394
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
395
+ res.setHeader('Cache-Control', 'no-cache, no-transform');
396
+ const heartbeat = setInterval(() => {
397
+ try {
398
+ res.write(' ');
399
+ } catch {
400
+ /* socket already closed */
401
+ }
402
+ }, 5000);
403
+ const finish = (payload) => {
404
+ clearInterval(heartbeat);
405
+ try {
406
+ res.end(JSON.stringify(payload));
407
+ } catch {
408
+ /* socket already closed */
409
+ }
410
+ };
411
+
374
412
  try {
375
413
  const result = await runAgent(prompt, esqueSessionId);
376
- res.json({
414
+ finish({
377
415
  text: result.text,
378
416
  status: result.isError ? 'blocked' : 'finished',
379
417
  });
380
418
  } catch (err) {
381
419
  console.error('[bridge] error:', err.message);
382
- res
383
- .status(500)
384
- .json({ text: `${adapter.label} failed: ${err.message}`, status: 'blocked' });
420
+ // Headers are already sent (heartbeat), so the error rides in the body
421
+ // with status 'blocked' rather than an HTTP 500.
422
+ finish({ text: `${adapter.label} failed: ${err.message}`, status: 'blocked' });
385
423
  }
386
424
  }
387
425
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "esque-bridge",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Desktop-side receiver for the Esque Agent mobile app. Pairs your phone with a local coding-agent CLI (Claude Code, 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"