@chatpanel/bridge 0.10.40 → 0.10.41

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/bridge",
3
- "version": "0.10.40",
3
+ "version": "0.10.41",
4
4
  "type": "module",
5
5
  "description": "Local bridge that exposes the AI coding agents installed on your machine \u2014 Claude Code (CLI), Codex (CLI), and Antigravity CLI (formerly Gemini CLI, which remains available for business/enterprise) \u2014 to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
6
6
  "keywords": [
@@ -0,0 +1,64 @@
1
+ // Turning a CLI agent's dying breath into something a person can act on.
2
+ //
3
+ // When an agent exits non-zero we used to surface its raw stderr. For a crash that is fine;
4
+ // for the common real failure — one of the AGENT'S OWN MCP servers refusing to authenticate —
5
+ // it is not: those servers answer with an HTML error page, so the user got kilobytes of
6
+ // markup, inline CSS and an SVG logo in the chat, with the one useful sentence buried inside.
7
+ //
8
+ // The failure is also usually not ChatPanel's to fix. Saying whose it is, and naming the
9
+ // server, is the difference between "something broke" and "re-auth that server".
10
+
11
+ const NOISE = [
12
+ /^\s*$/,
13
+ /^\s*at\s+/, // stack frames
14
+ /^\s*[.#]?[\w-]+\s*\{/, // CSS rules
15
+ /^\s*[\w-]+:\s*[^;]+;$/,// CSS declarations
16
+ /^\s*<\//, // closing tags
17
+ ];
18
+
19
+ // Strip HTML/CSS/SVG so an error page collapses to whatever prose it contained.
20
+ export function stripMarkup(text) {
21
+ return String(text || '')
22
+ .replace(/<script[\s\S]*?<\/script>/gi, ' ')
23
+ .replace(/<style[\s\S]*?<\/style>/gi, ' ')
24
+ .replace(/<svg[\s\S]*?<\/svg>/gi, ' ')
25
+ .replace(/<[^>]+>/g, ' ')
26
+ .replace(/&nbsp;/g, ' ')
27
+ .replace(/[ \t]+/g, ' ');
28
+ }
29
+
30
+ // The named causes worth translating. Each returns a sentence that says what to DO.
31
+ function knownCause(text) {
32
+ const server = /refresh OAuth tokens for server ([\w.-]+)/i.exec(text)?.[1]
33
+ || /server ['"]?([\w.-]+)['"]? (?:requires|failed) auth/i.exec(text)?.[1];
34
+ if (/refresh token (?:does not exist|was rejected)|invalid_grant/i.test(text)) {
35
+ return `${server ? `its MCP server "${server}"` : 'one of its MCP servers'} needs re-authentication — its saved OAuth token has expired or been revoked. Re-login to that server in the agent's own config; ChatPanel can't refresh it.`;
36
+ }
37
+ if (/invalid_token|AuthRequired|www-authenticate|\b401\b/i.test(text)) {
38
+ return `${server ? `its MCP server "${server}"` : 'one of its MCP servers'} rejected the agent's token (401/invalid_token). Re-authenticate that server in the agent, then retry.`;
39
+ }
40
+ if (/HTTP 403|\b403\b/.test(text)) {
41
+ return `${server ? `its MCP server "${server}"` : 'one of its MCP servers'} returned 403 and an error page — that server is refusing requests or is temporarily down. This is outside ChatPanel; retry when it recovers.`;
42
+ }
43
+ if (/ENOENT|command not found/i.test(text)) return 'the command could not be found on PATH.';
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * A short, actionable summary of why a CLI agent exited. Never returns markup, and never more
49
+ * than a couple of lines — the full output stays in the bridge log for anyone debugging.
50
+ */
51
+ export function summarizeCliError(label, code, stderr, stdout = '') {
52
+ const raw = `${stderr || ''}\n${stdout || ''}`;
53
+ const cause = knownCause(raw);
54
+ if (cause) return `${label} exited ${code}: ${cause}`;
55
+
56
+ const lines = stripMarkup(raw)
57
+ .split('\n')
58
+ .map((l) => l.trim())
59
+ .filter((l) => l && !NOISE.some((re) => re.test(l)));
60
+ // Prefer the last line that reads like an error, else the last line at all.
61
+ const errish = lines.filter((l) => /error|fail|fatal|panic|refused|denied|timeout/i.test(l));
62
+ const pick = (errish.length ? errish : lines).pop() || 'failed';
63
+ return `${label} exited ${code}: ${pick.length > 300 ? `${pick.slice(0, 300)}…` : pick}`;
64
+ }
@@ -16,6 +16,7 @@ import os from 'node:os';
16
16
  import path from 'node:path';
17
17
  import { findAgentBin } from '../env.js';
18
18
  import { buildCliPrompt } from './prompt.js';
19
+ import { summarizeCliError } from '../cli-errors.js';
19
20
  import { killOnAbort, spawnGroupOpts } from '../proc.js';
20
21
  import { pushExtraArgs, FORBIDDEN } from './args.js';
21
22
  import { resolveWorkdir } from '../workdir.js';
@@ -153,7 +154,7 @@ export async function chat({ messages, system, options, images }, emit, { signal
153
154
  emit({ type: 'done', text: '' });
154
155
  resolve();
155
156
  } else {
156
- reject(new Error(`Antigravity exited ${code}: ${err.trim() || out.trim() || 'failed'}`));
157
+ reject(new Error(summarizeCliError('Antigravity', code, err, out)));
157
158
  }
158
159
  });
159
160
  });
@@ -19,6 +19,7 @@ import os from 'node:os';
19
19
  import path from 'node:path';
20
20
  import { resolveClaude, buildSpawnSpec, isCompiledBinary, selfMcpStdio } from '../env.js';
21
21
  import { buildCliPrompt } from './prompt.js';
22
+ import { summarizeCliError } from '../cli-errors.js';
22
23
  import { killOnAbort } from '../proc.js';
23
24
  import { pushExtraArgs, FORBIDDEN } from './args.js';
24
25
  import { displayPath, resolveWorkdir } from '../workdir.js';
@@ -160,7 +161,7 @@ function runClaude({ prompt, args, cwd, emit, signal }) {
160
161
  detach();
161
162
  if (signal?.aborted) { resolve({ streamedAny, resultText }); return; } // Stop pressed — end quietly
162
163
  if (code === 0) resolve({ streamedAny, resultText });
163
- else reject(new Error(`Claude Code exited ${code}: ${stderr.trim().split('\n').pop() || 'failed'}`));
164
+ else reject(new Error(summarizeCliError('Claude Code', code, stderr)));
164
165
  });
165
166
 
166
167
  child.stdin.write(prompt);
@@ -24,6 +24,7 @@ import { findAgentBin, selfMcpStdio } from '../env.js';
24
24
  import { buildCliPrompt } from './prompt.js';
25
25
  import { pushExtraArgs, FORBIDDEN } from './args.js';
26
26
  import { resolveWorkdir } from '../workdir.js';
27
+ import { summarizeCliError } from '../cli-errors.js';
27
28
 
28
29
  // Idle timeout: re-armed on every stdout/stderr chunk, so a long run that keeps
29
30
  // streaming never trips it — only true silence does. Override with
@@ -238,7 +239,7 @@ export async function chat({ messages, system, options, images }, emit, { signal
238
239
  emit({ type: 'done', text: '' });
239
240
  resolve();
240
241
  } else {
241
- reject(new Error(`Codex exited ${code}: ${stderr.trim() || 'failed'}`));
242
+ reject(new Error(summarizeCliError('Codex', code, stderr)));
242
243
  }
243
244
  });
244
245
 
@@ -621,7 +621,7 @@ export async function runSpec(spec, { messages, system, options = {}, images },
621
621
  emit({ type: 'done', text: parser.streamed ? '' : parser.finish() });
622
622
  resolve();
623
623
  } else {
624
- reject(new Error(`${label} exited ${code}: ${stderr.trim().split('\n').pop() || 'failed'}`));
624
+ reject(new Error(summarizeCliError(label, code, stderr)));
625
625
  }
626
626
  });
627
627
 
package/src/server.js CHANGED
@@ -67,7 +67,7 @@ import {
67
67
  // Hardcoded (not read from package.json) so it survives Bun's single-file
68
68
  // --compile, where package.json isn't on a readable FS. CI fails the publish if
69
69
  // this drifts from package.json, so the two can't silently diverge.
70
- const VERSION = '0.10.40';
70
+ const VERSION = '0.10.41';
71
71
  const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
72
72
  const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
73
73