@chatpanel/bridge 0.3.0 → 0.3.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.
- package/package.json +1 -1
- package/src/engines/claude.js +18 -8
- package/src/engines/codex.js +17 -8
- package/src/engines/custom.js +17 -8
- package/src/engines/gemini.js +17 -8
- package/src/server.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local bridge that exposes the AI coding agents installed on your machine — Claude Code (CLI), Codex (CLI), and Gemini CLI — to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
|
|
6
6
|
"keywords": [
|
package/src/engines/claude.js
CHANGED
|
@@ -18,7 +18,11 @@ import os from 'node:os';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { resolveClaude, buildSpawnSpec, isCompiledBinary } from '../env.js';
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
// Idle timeout: kill the run only after this long with NO output. The timer
|
|
22
|
+
// re-arms on every stdout/stderr chunk, so a task that keeps streaming can run
|
|
23
|
+
// indefinitely — only a truly stuck/silent process is killed. Override with
|
|
24
|
+
// CHATPANEL_CLAUDE_TIMEOUT_MS (ms).
|
|
25
|
+
const IDLE_MS = Number(process.env.CHATPANEL_CLAUDE_TIMEOUT_MS) || 180_000;
|
|
22
26
|
// Read-only tools allowed without approval in headless mode; writes/shell are
|
|
23
27
|
// gated behind the agent's permission mode.
|
|
24
28
|
const READONLY_TOOLS = ['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'TodoWrite', 'Task'];
|
|
@@ -86,12 +90,18 @@ function runClaude({ prompt, args, cwd, emit }) {
|
|
|
86
90
|
let streamedAny = false;
|
|
87
91
|
let resultText = '';
|
|
88
92
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
let idleTimer;
|
|
94
|
+
const armIdle = () => {
|
|
95
|
+
clearTimeout(idleTimer);
|
|
96
|
+
idleTimer = setTimeout(() => {
|
|
97
|
+
child.kill('SIGKILL');
|
|
98
|
+
reject(new Error(`Claude Code timed out — no output for ${Math.round(IDLE_MS / 1000)}s.`));
|
|
99
|
+
}, IDLE_MS);
|
|
100
|
+
};
|
|
101
|
+
armIdle();
|
|
93
102
|
|
|
94
103
|
child.stdout.on('data', (d) => {
|
|
104
|
+
armIdle();
|
|
95
105
|
stdout += d.toString();
|
|
96
106
|
let nl;
|
|
97
107
|
while ((nl = stdout.indexOf('\n')) >= 0) {
|
|
@@ -109,13 +119,13 @@ function runClaude({ prompt, args, cwd, emit }) {
|
|
|
109
119
|
if (r.result != null) resultText = r.result;
|
|
110
120
|
}
|
|
111
121
|
});
|
|
112
|
-
child.stderr.on('data', (d) => (stderr += d.toString())
|
|
122
|
+
child.stderr.on('data', (d) => { armIdle(); stderr += d.toString(); });
|
|
113
123
|
child.on('error', (e) => {
|
|
114
|
-
clearTimeout(
|
|
124
|
+
clearTimeout(idleTimer);
|
|
115
125
|
reject(new Error(`Failed to start claude (${bin}): ${e.message}`));
|
|
116
126
|
});
|
|
117
127
|
child.on('close', (code) => {
|
|
118
|
-
clearTimeout(
|
|
128
|
+
clearTimeout(idleTimer);
|
|
119
129
|
if (code === 0) resolve({ streamedAny, resultText });
|
|
120
130
|
else reject(new Error(`Claude Code exited ${code}: ${stderr.trim().split('\n').pop() || 'failed'}`));
|
|
121
131
|
});
|
package/src/engines/codex.js
CHANGED
|
@@ -21,7 +21,10 @@ import os from 'node:os';
|
|
|
21
21
|
import path from 'node:path';
|
|
22
22
|
import { findAgentBin } from '../env.js';
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Idle timeout: re-armed on every stdout/stderr chunk, so a long run that keeps
|
|
25
|
+
// streaming never trips it — only true silence does. Override with
|
|
26
|
+
// CHATPANEL_CODEX_TIMEOUT_MS (ms).
|
|
27
|
+
const IDLE_MS = Number(process.env.CHATPANEL_CODEX_TIMEOUT_MS) || 180_000;
|
|
25
28
|
const REASONING = process.env.CHATPANEL_CODEX_EFFORT ?? 'low'; // '' → respect config
|
|
26
29
|
|
|
27
30
|
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-codex-scratch');
|
|
@@ -131,12 +134,18 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
131
134
|
|
|
132
135
|
let stdout = '';
|
|
133
136
|
let stderr = '';
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
let idleTimer;
|
|
138
|
+
const armIdle = () => {
|
|
139
|
+
clearTimeout(idleTimer);
|
|
140
|
+
idleTimer = setTimeout(() => {
|
|
141
|
+
child.kill('SIGKILL');
|
|
142
|
+
reject(new Error(`Codex timed out — no output for ${Math.round(IDLE_MS / 1000)}s.`));
|
|
143
|
+
}, IDLE_MS);
|
|
144
|
+
};
|
|
145
|
+
armIdle();
|
|
138
146
|
|
|
139
147
|
child.stdout.on('data', (d) => {
|
|
148
|
+
armIdle();
|
|
140
149
|
stdout += d.toString();
|
|
141
150
|
let nl;
|
|
142
151
|
while ((nl = stdout.indexOf('\n')) >= 0) {
|
|
@@ -150,13 +159,13 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
150
159
|
}
|
|
151
160
|
}
|
|
152
161
|
});
|
|
153
|
-
child.stderr.on('data', (d) => (stderr += d.toString())
|
|
162
|
+
child.stderr.on('data', (d) => { armIdle(); stderr += d.toString(); });
|
|
154
163
|
child.on('error', (e) => {
|
|
155
|
-
clearTimeout(
|
|
164
|
+
clearTimeout(idleTimer);
|
|
156
165
|
reject(e);
|
|
157
166
|
});
|
|
158
167
|
child.on('close', async (code) => {
|
|
159
|
-
clearTimeout(
|
|
168
|
+
clearTimeout(idleTimer);
|
|
160
169
|
let text = '';
|
|
161
170
|
try {
|
|
162
171
|
text = (await readFile(outFile, 'utf8')).trim();
|
package/src/engines/custom.js
CHANGED
|
@@ -21,7 +21,10 @@ import { resolveCommand, buildSpawnSpec } from '../env.js';
|
|
|
21
21
|
import { isProEntitled } from '../entitlement.js';
|
|
22
22
|
import { handleMessage } from './claude.js';
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Idle timeout: re-armed on every stdout/stderr chunk, so a long run that keeps
|
|
25
|
+
// streaming never trips it — only true silence does. Override with
|
|
26
|
+
// CHATPANEL_CUSTOM_TIMEOUT_MS (ms).
|
|
27
|
+
const IDLE_MS = Number(process.env.CHATPANEL_CUSTOM_TIMEOUT_MS) || 180_000;
|
|
25
28
|
|
|
26
29
|
export async function available() {
|
|
27
30
|
// The engine ships in every bridge; individual custom agents are user-defined
|
|
@@ -98,12 +101,18 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
98
101
|
let resultText = '';
|
|
99
102
|
let jsonBuf = '';
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
104
|
+
let idleTimer;
|
|
105
|
+
const armIdle = () => {
|
|
106
|
+
clearTimeout(idleTimer);
|
|
107
|
+
idleTimer = setTimeout(() => {
|
|
108
|
+
child.kill('SIGKILL');
|
|
109
|
+
reject(new Error(`${label} timed out — no output for ${Math.round(IDLE_MS / 1000)}s.`));
|
|
110
|
+
}, IDLE_MS);
|
|
111
|
+
};
|
|
112
|
+
armIdle();
|
|
105
113
|
|
|
106
114
|
child.stdout.on('data', (d) => {
|
|
115
|
+
armIdle();
|
|
107
116
|
const s = d.toString();
|
|
108
117
|
if (fmt === 'claude-stream-json') {
|
|
109
118
|
jsonBuf += s;
|
|
@@ -127,13 +136,13 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
127
136
|
emit({ type: 'delta', text: s });
|
|
128
137
|
}
|
|
129
138
|
});
|
|
130
|
-
child.stderr.on('data', (d) => (stderr += d.toString())
|
|
139
|
+
child.stderr.on('data', (d) => { armIdle(); stderr += d.toString(); });
|
|
131
140
|
child.on('error', (e) => {
|
|
132
|
-
clearTimeout(
|
|
141
|
+
clearTimeout(idleTimer);
|
|
133
142
|
reject(new Error(`Failed to start ${label}: ${e.message}`));
|
|
134
143
|
});
|
|
135
144
|
child.on('close', (code) => {
|
|
136
|
-
clearTimeout(
|
|
145
|
+
clearTimeout(idleTimer);
|
|
137
146
|
if (code === 0) {
|
|
138
147
|
emit({ type: 'done', text: streamedAny ? '' : resultText });
|
|
139
148
|
resolve();
|
package/src/engines/gemini.js
CHANGED
|
@@ -14,7 +14,10 @@ import os from 'node:os';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { findAgentBin } from '../env.js';
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
// Idle timeout: re-armed on every stdout/stderr chunk, so a long run that keeps
|
|
18
|
+
// streaming never trips it — only true silence does. Override with
|
|
19
|
+
// CHATPANEL_GEMINI_TIMEOUT_MS (ms).
|
|
20
|
+
const IDLE_MS = Number(process.env.CHATPANEL_GEMINI_TIMEOUT_MS) || 180_000;
|
|
18
21
|
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-gemini-scratch');
|
|
19
22
|
|
|
20
23
|
let installed = false;
|
|
@@ -76,24 +79,30 @@ export async function chat({ messages, system, options }, emit) {
|
|
|
76
79
|
let out = '';
|
|
77
80
|
let err = '';
|
|
78
81
|
let streamed = false;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
let idleTimer;
|
|
83
|
+
const armIdle = () => {
|
|
84
|
+
clearTimeout(idleTimer);
|
|
85
|
+
idleTimer = setTimeout(() => {
|
|
86
|
+
child.kill('SIGKILL');
|
|
87
|
+
reject(new Error(`Gemini timed out — no output for ${Math.round(IDLE_MS / 1000)}s.`));
|
|
88
|
+
}, IDLE_MS);
|
|
89
|
+
};
|
|
90
|
+
armIdle();
|
|
83
91
|
|
|
84
92
|
child.stdout.on('data', (d) => {
|
|
93
|
+
armIdle();
|
|
85
94
|
const s = d.toString();
|
|
86
95
|
out += s;
|
|
87
96
|
streamed = true;
|
|
88
97
|
emit({ type: 'delta', text: s });
|
|
89
98
|
});
|
|
90
|
-
child.stderr.on('data', (d) => (err += d.toString())
|
|
99
|
+
child.stderr.on('data', (d) => { armIdle(); err += d.toString(); });
|
|
91
100
|
child.on('error', (e) => {
|
|
92
|
-
clearTimeout(
|
|
101
|
+
clearTimeout(idleTimer);
|
|
93
102
|
reject(new Error(`Failed to start gemini: ${e.message}`));
|
|
94
103
|
});
|
|
95
104
|
child.on('close', (code) => {
|
|
96
|
-
clearTimeout(
|
|
105
|
+
clearTimeout(idleTimer);
|
|
97
106
|
if (code === 0) {
|
|
98
107
|
if (!streamed) emit({ type: 'delta', text: out.trim() || '(no output)' });
|
|
99
108
|
emit({ type: 'done', text: '' });
|
package/src/server.js
CHANGED
|
@@ -27,7 +27,7 @@ import { checkForUpdate, selfUpdate } from './update.js';
|
|
|
27
27
|
// Hardcoded (not read from package.json) so it survives Bun's single-file
|
|
28
28
|
// --compile, where package.json isn't on a readable FS. CI fails the publish if
|
|
29
29
|
// this drifts from package.json, so the two can't silently diverge.
|
|
30
|
-
const VERSION = '0.3.
|
|
30
|
+
const VERSION = '0.3.1';
|
|
31
31
|
const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
|
|
32
32
|
const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
|
|
33
33
|
|