@chatpanel/bridge 0.10.39 → 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 +1 -1
- package/src/cli-errors.js +64 -0
- package/src/engines/antigravity.js +5 -2
- package/src/engines/claude.js +5 -2
- package/src/engines/cli-agents.js +7 -3
- package/src/engines/codex.js +6 -5
- package/src/engines/custom.js +1 -1
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.10.
|
|
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(/ /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';
|
|
@@ -50,7 +51,9 @@ let lastProbe = 0;
|
|
|
50
51
|
export async function available() {
|
|
51
52
|
// Cache a positive result; keep re-probing (throttled) while not found so it
|
|
52
53
|
// self-heals once agy appears on PATH — never cache a negative forever.
|
|
53
|
-
|
|
54
|
+
// Re-probe in both directions (see codex.js): an uninstalled CLI must stop reporting itself
|
|
55
|
+
// available without waiting for a bridge restart.
|
|
56
|
+
if (Date.now() - lastProbe > (installed ? 30_000 : 4000)) {
|
|
54
57
|
lastProbe = Date.now();
|
|
55
58
|
try {
|
|
56
59
|
installed = !!findAgentBin('agy');
|
|
@@ -151,7 +154,7 @@ export async function chat({ messages, system, options, images }, emit, { signal
|
|
|
151
154
|
emit({ type: 'done', text: '' });
|
|
152
155
|
resolve();
|
|
153
156
|
} else {
|
|
154
|
-
reject(new Error(
|
|
157
|
+
reject(new Error(summarizeCliError('Antigravity', code, err, out)));
|
|
155
158
|
}
|
|
156
159
|
});
|
|
157
160
|
});
|
package/src/engines/claude.js
CHANGED
|
@@ -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';
|
|
@@ -54,7 +55,9 @@ let cachedOk = false;
|
|
|
54
55
|
export async function available() {
|
|
55
56
|
// Availability = "can we launch claude somehow" (native / cli.js / WSL / SDK),
|
|
56
57
|
// NOT "does `claude --version` exit 0" (which fails when it just needs login).
|
|
57
|
-
|
|
58
|
+
// Re-probe in both directions (see codex.js): caching success forever meant an uninstalled
|
|
59
|
+
// CLI still reported itself available.
|
|
60
|
+
if (Date.now() - lastProbe > (cachedOk ? 30_000 : 4000)) {
|
|
58
61
|
lastProbe = Date.now();
|
|
59
62
|
try {
|
|
60
63
|
const spec = resolveClaude();
|
|
@@ -158,7 +161,7 @@ function runClaude({ prompt, args, cwd, emit, signal }) {
|
|
|
158
161
|
detach();
|
|
159
162
|
if (signal?.aborted) { resolve({ streamedAny, resultText }); return; } // Stop pressed — end quietly
|
|
160
163
|
if (code === 0) resolve({ streamedAny, resultText });
|
|
161
|
-
else reject(new Error(
|
|
164
|
+
else reject(new Error(summarizeCliError('Claude Code', code, stderr)));
|
|
162
165
|
});
|
|
163
166
|
|
|
164
167
|
child.stdin.write(prompt);
|
|
@@ -21,9 +21,13 @@ function makeCliAgent(command, spec, notFoundHint, overrides = {}) {
|
|
|
21
21
|
return {
|
|
22
22
|
spec: resolvedSpec,
|
|
23
23
|
async available() {
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// Re-probe in BOTH directions. Caching a positive result forever meant an UNINSTALLED
|
|
25
|
+
// CLI kept reporting itself available until the bridge restarted — the picker showed a
|
|
26
|
+
// green dot for an agent that could no longer run, and the turn failed with "couldn't
|
|
27
|
+
// find it on your PATH". A found CLI is re-checked on a slow interval (it rarely
|
|
28
|
+
// disappears, and `which` is cheap but not free); a missing one keeps the fast interval
|
|
29
|
+
// so it still self-heals the moment it is installed.
|
|
30
|
+
if (Date.now() - lastProbe > (installed ? 30_000 : 4000)) {
|
|
27
31
|
lastProbe = Date.now();
|
|
28
32
|
try {
|
|
29
33
|
installed = !!findAgentBin(command);
|
package/src/engines/codex.js
CHANGED
|
@@ -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
|
|
@@ -77,10 +78,10 @@ function ensureIsolatedHome() {
|
|
|
77
78
|
let installed = false;
|
|
78
79
|
let lastProbe = 0;
|
|
79
80
|
export async function available() {
|
|
80
|
-
// Availability = "is codex findable on PATH", not "does `codex --version` exit
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
if (
|
|
81
|
+
// Availability = "is codex findable on PATH", not "does `codex --version` exit 0" (which
|
|
82
|
+
// fails when the CLI just needs login). Re-probed in BOTH directions: caching a positive
|
|
83
|
+
// forever kept an uninstalled CLI reporting itself available until the bridge restarted.
|
|
84
|
+
if (Date.now() - lastProbe > (installed ? 30_000 : 4000)) {
|
|
84
85
|
lastProbe = Date.now();
|
|
85
86
|
try {
|
|
86
87
|
installed = !!findAgentBin('codex');
|
|
@@ -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(
|
|
242
|
+
reject(new Error(summarizeCliError('Codex', code, stderr)));
|
|
242
243
|
}
|
|
243
244
|
});
|
|
244
245
|
|
package/src/engines/custom.js
CHANGED
|
@@ -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(
|
|
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.
|
|
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
|
|