@chatpanel/bridge 0.4.0 → 0.5.0
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/{gemini.js → antigravity.js} +49 -36
- package/src/engines/cli-agents.js +77 -0
- package/src/engines/custom.js +30 -10
- package/src/env.js +2 -2
- package/src/server.js +10 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chatpanel/bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
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": [
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Antigravity engine — drives the Antigravity CLI (`agy -p`) using your local
|
|
2
|
+
// login. This replaces Gemini CLI as the default Google-model agent (the Gemini
|
|
3
|
+
// CLI is being deprecated for individual users).
|
|
2
4
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// stdout, and exits. We run in an empty scratch dir for general chat so Gemini
|
|
6
|
-
// never crawls the bridge's own files; set a working dir on the agent to point
|
|
7
|
-
// it at a real project.
|
|
5
|
+
// agy -p "<prompt>" → run one prompt non-interactively, print the answer, exit
|
|
6
|
+
// agy --model <id> → pick the model agy models → list models
|
|
8
7
|
//
|
|
9
|
-
//
|
|
8
|
+
// Images: Antigravity has no image flag, but it READS image files referenced by
|
|
9
|
+
// path in the prompt (vision) — same approach as Claude Code. We write the image
|
|
10
|
+
// into the workspace (cwd), grant read access with --add-dir, and reference the
|
|
11
|
+
// path so the model opens it.
|
|
10
12
|
|
|
11
13
|
import { spawn, spawnSync } from 'node:child_process';
|
|
12
14
|
import { mkdirSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
@@ -14,38 +16,50 @@ import os from 'node:os';
|
|
|
14
16
|
import path from 'node:path';
|
|
15
17
|
import { findAgentBin } from '../env.js';
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// CHATPANEL_GEMINI_TIMEOUT_MS (ms).
|
|
20
|
-
const IDLE_MS = Number(process.env.CHATPANEL_GEMINI_TIMEOUT_MS) || 180_000;
|
|
21
|
-
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-gemini-scratch');
|
|
19
|
+
const IDLE_MS = Number(process.env.CHATPANEL_AGY_TIMEOUT_MS) || 180_000;
|
|
20
|
+
const SCRATCH = path.join(os.tmpdir(), 'chatpanel-agy-scratch');
|
|
22
21
|
|
|
23
|
-
//
|
|
24
|
-
// and
|
|
25
|
-
// text still accepted.
|
|
22
|
+
// `agy models` lists available models. Parse ids best-effort; free text is still
|
|
23
|
+
// accepted by the picker, and [] just means "type a model or use the default".
|
|
26
24
|
export async function listModels() {
|
|
27
|
-
|
|
25
|
+
try {
|
|
26
|
+
const bin = findAgentBin('agy') || 'agy';
|
|
27
|
+
const r = spawnSync(bin, ['models'], { encoding: 'utf8', timeout: 15000 });
|
|
28
|
+
const ids = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
for (const line of String(r.stdout || '').split('\n')) {
|
|
31
|
+
const tok = line.trim().split(/\s+/)[0] || '';
|
|
32
|
+
if (!/^[A-Za-z0-9][\w./:-]{1,79}$/.test(tok)) continue;
|
|
33
|
+
if (/^(name|model|models|id|provider|available)$/i.test(tok)) continue;
|
|
34
|
+
if (seen.has(tok)) continue;
|
|
35
|
+
seen.add(tok);
|
|
36
|
+
ids.push(tok);
|
|
37
|
+
if (ids.length >= 100) break;
|
|
38
|
+
}
|
|
39
|
+
return ids;
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
let installed = false;
|
|
31
46
|
let lastProbe = 0;
|
|
32
47
|
export async function available() {
|
|
33
|
-
// Cache a positive result
|
|
34
|
-
//
|
|
48
|
+
// Cache a positive result; keep re-probing (throttled) while not found so it
|
|
49
|
+
// self-heals once agy appears on PATH — never cache a negative forever.
|
|
35
50
|
if (!installed && Date.now() - lastProbe > 4000) {
|
|
36
51
|
lastProbe = Date.now();
|
|
37
52
|
try {
|
|
38
|
-
installed = !!findAgentBin('
|
|
53
|
+
installed = !!findAgentBin('agy');
|
|
39
54
|
} catch {
|
|
40
55
|
installed = false;
|
|
41
56
|
}
|
|
42
57
|
}
|
|
43
58
|
return installed
|
|
44
59
|
? { ok: true }
|
|
45
|
-
: { ok: false, reason: '
|
|
60
|
+
: { ok: false, reason: 'agy not found on PATH. Install Antigravity, then run `agy` once to sign in.' };
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
// Write base64 data-URL images into `dir` so Gemini's `@<file>` can read them.
|
|
49
63
|
function writeImages(images, dir) {
|
|
50
64
|
const files = [];
|
|
51
65
|
for (let i = 0; i < (images?.length || 0); i++) {
|
|
@@ -63,6 +77,7 @@ function writeImages(images, dir) {
|
|
|
63
77
|
return files;
|
|
64
78
|
}
|
|
65
79
|
|
|
80
|
+
// The bridge is stateless, so replay the conversation as a single prompt.
|
|
66
81
|
function buildPrompt(messages, system) {
|
|
67
82
|
let p = system ? `${system}\n\n` : '';
|
|
68
83
|
const history = messages.slice(0, -1);
|
|
@@ -84,9 +99,9 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
84
99
|
}
|
|
85
100
|
const cwd = options.workingDir ? path.resolve(options.workingDir) : SCRATCH;
|
|
86
101
|
|
|
87
|
-
// Images: write
|
|
88
|
-
// files
|
|
89
|
-
//
|
|
102
|
+
// Images: write into the cwd (workspace) and reference with `@<file>` — agy
|
|
103
|
+
// reads @-referenced files (incl. images) inline as multimodal input, so no
|
|
104
|
+
// read-tool approval is needed in headless `-p` mode. (Confirmed working.)
|
|
90
105
|
const imageFiles = writeImages(images, cwd);
|
|
91
106
|
const cleanup = () => imageFiles.forEach((f) => { try { unlinkSync(f); } catch { /* gone */ } });
|
|
92
107
|
let prompt = buildPrompt(messages, system);
|
|
@@ -94,22 +109,20 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
94
109
|
prompt += `\n\nThe user attached image(s): ${imageFiles.map((f) => '@' + path.basename(f)).join(' ')}`;
|
|
95
110
|
}
|
|
96
111
|
|
|
97
|
-
// `-p`
|
|
98
|
-
// auto-approves tool
|
|
99
|
-
//
|
|
112
|
+
// `-p` runs one prompt non-interactively. --model picks the model.
|
|
113
|
+
// --dangerously-skip-permissions auto-approves tool use (headless has no human
|
|
114
|
+
// approver) only when the user opted into bypassPermissions.
|
|
100
115
|
const args = ['-p', prompt];
|
|
101
|
-
if (options.model) args.push('
|
|
102
|
-
if (options.permissionMode === 'bypassPermissions') args.push('-
|
|
116
|
+
if (options.model) args.push('--model', options.model);
|
|
117
|
+
if (options.permissionMode === 'bypassPermissions') args.push('--dangerously-skip-permissions');
|
|
103
118
|
|
|
104
119
|
await new Promise((resolve, reject) => {
|
|
105
120
|
let child;
|
|
106
121
|
try {
|
|
107
|
-
|
|
108
|
-
// interactive "trust this folder?" dialog can block us.
|
|
109
|
-
child = spawn('gemini', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
|
|
122
|
+
child = spawn('agy', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'], env: { ...process.env } });
|
|
110
123
|
} catch (e) {
|
|
111
124
|
cleanup();
|
|
112
|
-
return reject(new Error(`Failed to start
|
|
125
|
+
return reject(new Error(`Failed to start agy: ${e.message}`));
|
|
113
126
|
}
|
|
114
127
|
|
|
115
128
|
let out = '';
|
|
@@ -121,7 +134,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
121
134
|
idleTimer = setTimeout(() => {
|
|
122
135
|
child.kill('SIGKILL');
|
|
123
136
|
cleanup();
|
|
124
|
-
reject(new Error(`
|
|
137
|
+
reject(new Error(`Antigravity timed out — no output for ${Math.round(IDLE_MS / 1000)}s.`));
|
|
125
138
|
}, IDLE_MS);
|
|
126
139
|
};
|
|
127
140
|
armIdle();
|
|
@@ -137,7 +150,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
137
150
|
child.on('error', (e) => {
|
|
138
151
|
clearTimeout(idleTimer);
|
|
139
152
|
cleanup();
|
|
140
|
-
reject(new Error(`Failed to start
|
|
153
|
+
reject(new Error(`Failed to start agy: ${e.message}`));
|
|
141
154
|
});
|
|
142
155
|
child.on('close', (code) => {
|
|
143
156
|
clearTimeout(idleTimer);
|
|
@@ -147,7 +160,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
147
160
|
emit({ type: 'done', text: '' });
|
|
148
161
|
resolve();
|
|
149
162
|
} else {
|
|
150
|
-
reject(new Error(`
|
|
163
|
+
reject(new Error(`Antigravity exited ${code}: ${err.trim() || out.trim() || 'failed'}`));
|
|
151
164
|
}
|
|
152
165
|
});
|
|
153
166
|
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Built-in CLI agents — pi, opencode, kiro — that reuse the shared custom-engine
|
|
2
|
+
// runner (runSpec) with a FIXED spec each. Unlike the Pro "custom" engine, these
|
|
3
|
+
// are NOT entitlement-gated: they ship as built-ins, and the extension bounds
|
|
4
|
+
// free users to a single usable agent (FREE_LIMITS.bridgeAgents). Bring-your-own
|
|
5
|
+
// arbitrary CLIs stay Pro via custom.js.
|
|
6
|
+
//
|
|
7
|
+
// Specs come from each CLI's actual flags:
|
|
8
|
+
// pi — pi -p "<prompt>" · --model · @{path} images · --list-models
|
|
9
|
+
// opencode — opencode run "<prompt>" · -m provider/model · -f {path} images · models
|
|
10
|
+
// kiro — kiro-cli chat --no-interactive "<prompt>" · --model · --list-models
|
|
11
|
+
|
|
12
|
+
import { runSpec, listSpecModels } from './custom.js';
|
|
13
|
+
import { findAgentBin } from '../env.js';
|
|
14
|
+
|
|
15
|
+
function makeCliAgent(command, spec, notFoundHint) {
|
|
16
|
+
let installed = false;
|
|
17
|
+
let lastProbe = 0;
|
|
18
|
+
return {
|
|
19
|
+
async available() {
|
|
20
|
+
// Cache a positive result; re-probe (throttled) while not found so it
|
|
21
|
+
// self-heals once the CLI appears on PATH.
|
|
22
|
+
if (!installed && Date.now() - lastProbe > 4000) {
|
|
23
|
+
lastProbe = Date.now();
|
|
24
|
+
try {
|
|
25
|
+
installed = !!findAgentBin(command);
|
|
26
|
+
} catch {
|
|
27
|
+
installed = false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return installed ? { ok: true } : { ok: false, reason: notFoundHint };
|
|
31
|
+
},
|
|
32
|
+
listModels(options = {}) {
|
|
33
|
+
return listSpecModels(command, spec.listModelsArgs, options.workingDir);
|
|
34
|
+
},
|
|
35
|
+
chat(input, emit) {
|
|
36
|
+
return runSpec({ ...spec, command }, input, emit);
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const pi = makeCliAgent(
|
|
42
|
+
'pi',
|
|
43
|
+
{
|
|
44
|
+
args: '-p',
|
|
45
|
+
promptVia: 'arg',
|
|
46
|
+
modelArg: '--model {model}',
|
|
47
|
+
imageArg: '@{path}',
|
|
48
|
+
listModelsArgs: '--list-models',
|
|
49
|
+
label: 'Pi',
|
|
50
|
+
},
|
|
51
|
+
'pi not found on PATH. Install Pi, then run `pi` once to sign in.',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
export const opencode = makeCliAgent(
|
|
55
|
+
'opencode',
|
|
56
|
+
{
|
|
57
|
+
args: 'run',
|
|
58
|
+
promptVia: 'arg',
|
|
59
|
+
modelArg: '-m {model}',
|
|
60
|
+
imageArg: '-f {path}',
|
|
61
|
+
listModelsArgs: 'models',
|
|
62
|
+
label: 'OpenCode',
|
|
63
|
+
},
|
|
64
|
+
'opencode not found on PATH. Install opencode, then sign in.',
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
export const kiro = makeCliAgent(
|
|
68
|
+
'kiro-cli',
|
|
69
|
+
{
|
|
70
|
+
args: 'chat --no-interactive',
|
|
71
|
+
promptVia: 'arg',
|
|
72
|
+
modelArg: '--model {model}',
|
|
73
|
+
listModelsArgs: '--list-models',
|
|
74
|
+
label: 'Kiro',
|
|
75
|
+
},
|
|
76
|
+
'kiro-cli not found on PATH. Install Kiro CLI, then sign in.',
|
|
77
|
+
);
|
package/src/engines/custom.js
CHANGED
|
@@ -59,6 +59,12 @@ function imageTokensFor(imageArg, files) {
|
|
|
59
59
|
// CHATPANEL_CUSTOM_TIMEOUT_MS (ms).
|
|
60
60
|
const IDLE_MS = Number(process.env.CHATPANEL_CUSTOM_TIMEOUT_MS) || 180_000;
|
|
61
61
|
|
|
62
|
+
// Many CLIs emit ANSI colour/escape codes even when piped (kiro-cli does), which
|
|
63
|
+
// leak into the answer as `\x1b[38;5;141m…`. Strip them from text output. (We
|
|
64
|
+
// also set NO_COLOR on the child env, but this is the robust backstop.)
|
|
65
|
+
const ANSI_RE = /\u001b\[[0-9;?]*[ -/]*[@-~]/g;
|
|
66
|
+
const stripAnsi = (s) => s.replace(ANSI_RE, '');
|
|
67
|
+
|
|
62
68
|
export async function available() {
|
|
63
69
|
// The engine ships in every bridge; individual custom agents are user-defined
|
|
64
70
|
// (Pro) and validated per request and via /agent-check.
|
|
@@ -92,19 +98,26 @@ export async function listModels(options = {}) {
|
|
|
92
98
|
throw new Error('Custom agents require ChatPanel Pro.');
|
|
93
99
|
}
|
|
94
100
|
const spec = options.custom || {};
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
return listSpecModels(spec.command, spec.listModelsArgs, options.workingDir);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Shared model listing (no Pro gate): run a CLI's list-models invocation and
|
|
105
|
+
// parse it. Used by the Pro custom engine (gated above) AND built-in CLI agents.
|
|
106
|
+
export async function listSpecModels(command, listModelsArgs, workingDir) {
|
|
107
|
+
const listArgs = String(listModelsArgs || '').trim();
|
|
108
|
+
if (!command || !listArgs) return [];
|
|
109
|
+
const resolved = resolveCommand(command);
|
|
110
|
+
if (!resolved) throw new Error(`Couldn't find "${command}".`);
|
|
111
|
+
const cwd = workingDir ? path.resolve(workingDir) : null;
|
|
100
112
|
const [bin, argv, opts] = buildSpawnSpec(resolved, listArgs.split(/\s+/).filter(Boolean), cwd);
|
|
113
|
+
opts.env = { ...(opts.env || process.env), NO_COLOR: '1', CLICOLOR: '0' };
|
|
101
114
|
const stdout = await new Promise((resolve, reject) => {
|
|
102
115
|
let child;
|
|
103
|
-
try { child = spawn(bin, argv, opts); } catch (e) { return reject(new Error(`Failed to start ${
|
|
116
|
+
try { child = spawn(bin, argv, opts); } catch (e) { return reject(new Error(`Failed to start ${command}: ${e.message}`)); }
|
|
104
117
|
let out = '';
|
|
105
118
|
const timer = setTimeout(() => { child.kill('SIGKILL'); reject(new Error('Listing models timed out.')); }, 20000);
|
|
106
119
|
child.stdout.on('data', (d) => (out += d.toString()));
|
|
107
|
-
child.on('error', (e) => { clearTimeout(timer); reject(new Error(`Failed to start ${
|
|
120
|
+
child.on('error', (e) => { clearTimeout(timer); reject(new Error(`Failed to start ${command}: ${e.message}`)); });
|
|
108
121
|
child.on('close', () => { clearTimeout(timer); resolve(out); });
|
|
109
122
|
try { child.stdin.end(); } catch { /* some CLIs don't read stdin */ }
|
|
110
123
|
});
|
|
@@ -130,9 +143,14 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
130
143
|
if (!(await isProEntitled(options.entitlement))) {
|
|
131
144
|
throw new Error('Custom agents require ChatPanel Pro. Upgrade in Settings to bring your own CLI agent.');
|
|
132
145
|
}
|
|
146
|
+
return runSpec(options.custom || {}, { messages, system, options, images }, emit);
|
|
147
|
+
}
|
|
133
148
|
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
// Run a CLI agent from a spec — SHARED by the Pro custom engine (gated in chat()
|
|
150
|
+
// above) and the built-in CLI engines (pi/opencode/kiro). This never gates; the
|
|
151
|
+
// built-in agents are bounded instead by the extension's free 1-agent limit.
|
|
152
|
+
export async function runSpec(spec, { messages, system, options = {}, images }, emit) {
|
|
153
|
+
if (!spec.command) throw new Error('This agent has no command configured.');
|
|
136
154
|
|
|
137
155
|
const resolved = resolveCommand(spec.command);
|
|
138
156
|
if (!resolved) {
|
|
@@ -200,6 +218,8 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
200
218
|
}
|
|
201
219
|
|
|
202
220
|
const [bin, argv, opts] = buildSpawnSpec(resolved, args, cwd);
|
|
221
|
+
// Discourage CLIs from colourizing output (kiro-cli etc.) when piped.
|
|
222
|
+
opts.env = { ...(opts.env || process.env), NO_COLOR: '1', FORCE_COLOR: '0', CLICOLOR: '0', TERM: 'dumb' };
|
|
203
223
|
|
|
204
224
|
await new Promise((resolve, reject) => {
|
|
205
225
|
let child;
|
|
@@ -248,7 +268,7 @@ export async function chat({ messages, system, options, images }, emit) {
|
|
|
248
268
|
}
|
|
249
269
|
} else {
|
|
250
270
|
streamedAny = true;
|
|
251
|
-
emit({ type: 'delta', text: s });
|
|
271
|
+
emit({ type: 'delta', text: stripAnsi(s) });
|
|
252
272
|
}
|
|
253
273
|
});
|
|
254
274
|
child.stderr.on('data', (d) => { armIdle(); stderr += d.toString(); });
|
package/src/env.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Resolve a usable PATH for spawning the agent CLIs (codex,
|
|
1
|
+
// Resolve a usable PATH for spawning the agent CLIs (codex, agy).
|
|
2
2
|
//
|
|
3
3
|
// When the bridge runs as a login service (LaunchAgent / Scheduled Task) — or as
|
|
4
4
|
// a double-clicked app — it inherits a MINIMAL PATH, not your interactive shell's.
|
|
@@ -14,7 +14,7 @@ let enriched = false;
|
|
|
14
14
|
|
|
15
15
|
// The agent CLIs the bridge shells out to. Claude has its own richer resolution
|
|
16
16
|
// (resolveClaude: native / cli.js / WSL / SDK) below.
|
|
17
|
-
const AGENT_CLIS = ['codex', '
|
|
17
|
+
const AGENT_CLIS = ['codex', 'claude', 'agy', 'pi', 'opencode', 'kiro-cli'];
|
|
18
18
|
|
|
19
19
|
// Is `name` executable somewhere on the current PATH?
|
|
20
20
|
function onPath(name) {
|
package/src/server.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// ChatPanel Bridge — a tiny localhost server that exposes the coding agents
|
|
3
|
-
// running on this machine (Claude Code, Codex and
|
|
3
|
+
// running on this machine (Claude Code, Codex and Antigravity, each via its CLI) to
|
|
4
4
|
// the ChatPanel Chrome extension. Zero runtime dependencies.
|
|
5
5
|
//
|
|
6
6
|
// GET /health → { ok, version, agents: [...], update: {current,latest,…} }
|
|
@@ -18,7 +18,8 @@ import { createServer } from 'node:http';
|
|
|
18
18
|
import os from 'node:os';
|
|
19
19
|
import * as claude from './engines/claude.js';
|
|
20
20
|
import * as codex from './engines/codex.js';
|
|
21
|
-
import * as
|
|
21
|
+
import * as antigravity from './engines/antigravity.js';
|
|
22
|
+
import { pi, opencode, kiro } from './engines/cli-agents.js';
|
|
22
23
|
import * as custom from './engines/custom.js';
|
|
23
24
|
import { installService, uninstallService, serviceStatus, restartService } from './service.js';
|
|
24
25
|
import { enrichPath, findAgentBin, resolveCommand } from './env.js';
|
|
@@ -27,14 +28,17 @@ import { checkForUpdate, selfUpdate } from './update.js';
|
|
|
27
28
|
// Hardcoded (not read from package.json) so it survives Bun's single-file
|
|
28
29
|
// --compile, where package.json isn't on a readable FS. CI fails the publish if
|
|
29
30
|
// this drifts from package.json, so the two can't silently diverge.
|
|
30
|
-
const VERSION = '0.
|
|
31
|
+
const VERSION = '0.5.0';
|
|
31
32
|
const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
|
|
32
33
|
const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
|
|
33
34
|
|
|
34
35
|
const ENGINES = {
|
|
35
36
|
claude: { engine: claude, label: 'Claude Code' },
|
|
36
37
|
codex: { engine: codex, label: 'Codex' },
|
|
37
|
-
|
|
38
|
+
antigravity: { engine: antigravity, label: 'Antigravity' },
|
|
39
|
+
pi: { engine: pi, label: 'Pi' },
|
|
40
|
+
opencode: { engine: opencode, label: 'OpenCode' },
|
|
41
|
+
kiro: { engine: kiro, label: 'Kiro' },
|
|
38
42
|
// "Bring your own" — one engine drives any user-onboarded CLI (Pro). Hidden
|
|
39
43
|
// from /health (it's not a single installable agent; the extension manages the
|
|
40
44
|
// list and validates commands via /agent-check).
|
|
@@ -253,7 +257,7 @@ const server = createServer(async (req, res) => {
|
|
|
253
257
|
version: VERSION,
|
|
254
258
|
home: os.homedir(),
|
|
255
259
|
codex: findAgentBin('codex') || null,
|
|
256
|
-
|
|
260
|
+
agy: findAgentBin('agy') || null,
|
|
257
261
|
path: process.env.PATH,
|
|
258
262
|
});
|
|
259
263
|
}
|
|
@@ -282,7 +286,7 @@ function startServer() {
|
|
|
282
286
|
const a = await engine.available().catch(() => ({ ok: false }));
|
|
283
287
|
log('info', ` ${a.ok ? '✓' : '✕'} ${label}${a.ok ? '' : ' — ' + (a.reason || 'unavailable')}`);
|
|
284
288
|
}
|
|
285
|
-
log('info', 'Open the ChatPanel side panel; installed agents (Claude Code, Codex,
|
|
289
|
+
log('info', 'Open the ChatPanel side panel; installed agents (Claude Code, Codex, Antigravity) appear automatically.');
|
|
286
290
|
});
|
|
287
291
|
}
|
|
288
292
|
|