genaicode 2.14.0 → 2.15.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/CHANGELOG.md +23 -0
- package/README.md +70 -0
- package/dist/agents/drivers/claude.d.ts +5 -0
- package/dist/agents/drivers/claude.js +12 -4
- package/dist/agents/drivers/claude.js.map +1 -1
- package/dist/agents/drivers/codex.d.ts +34 -2
- package/dist/agents/drivers/codex.js +83 -8
- package/dist/agents/drivers/codex.js.map +1 -1
- package/dist/agents/drivers/copilot.d.ts +13 -0
- package/dist/agents/drivers/copilot.js +26 -1
- package/dist/agents/drivers/copilot.js.map +1 -1
- package/dist/agents/drivers/mcp-isolation.d.ts +9 -0
- package/dist/agents/drivers/mcp-isolation.js +24 -0
- package/dist/agents/drivers/mcp-isolation.js.map +1 -0
- package/dist/agents/prepare.d.ts +2 -0
- package/dist/agents/prepare.js +2 -1
- package/dist/agents/prepare.js.map +1 -1
- package/dist/agents/session.d.ts +12 -2
- package/dist/agents/session.js +53 -9
- package/dist/agents/session.js.map +1 -1
- package/dist/app-agent/app-agent.d.ts +133 -0
- package/dist/app-agent/app-agent.js +678 -0
- package/dist/app-agent/app-agent.js.map +1 -0
- package/dist/app-agent/git.d.ts +25 -0
- package/dist/app-agent/git.js +286 -0
- package/dist/app-agent/git.js.map +1 -0
- package/dist/app-agent/http.d.ts +41 -0
- package/dist/app-agent/http.js +228 -0
- package/dist/app-agent/http.js.map +1 -0
- package/dist/app-agent/json.d.ts +7 -0
- package/dist/app-agent/json.js +62 -0
- package/dist/app-agent/json.js.map +1 -0
- package/dist/app-agent/mcp.d.ts +15 -0
- package/dist/app-agent/mcp.js +53 -0
- package/dist/app-agent/mcp.js.map +1 -0
- package/dist/app-agent/policy.d.ts +13 -0
- package/dist/app-agent/policy.js +97 -0
- package/dist/app-agent/policy.js.map +1 -0
- package/dist/app-agent/state.d.ts +75 -0
- package/dist/app-agent/state.js +10 -0
- package/dist/app-agent/state.js.map +1 -0
- package/dist/app-agent.d.ts +8 -0
- package/dist/app-agent.js +5 -0
- package/dist/app-agent.js.map +1 -0
- package/dist/react/agent-chat.css +137 -0
- package/dist/react/app-agent-chat.d.ts +55 -0
- package/dist/react/app-agent-chat.js +229 -0
- package/dist/react/app-agent-chat.js.map +1 -0
- package/dist/react.d.ts +4 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +1 -1
- package/dist/ui/index.js +59 -59
- package/dist/ui/web-client.js +148 -11
- package/package.json +5 -1
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { AppAgentError } from './app-agent.js';
|
|
3
|
+
import { safeJson } from './json.js';
|
|
4
|
+
import { handleMcpMessage } from './mcp.js';
|
|
5
|
+
const MAX_BODY = 256 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Serve an app agent over HTTP, for any Node server (node:http, Express, Connect, Fastify
|
|
8
|
+
* with `reply.hijack()`, a Vite middleware). Routes under `basePath`:
|
|
9
|
+
*
|
|
10
|
+
* - `GET /state`, `GET /events` (server-sent `AppAgentState` snapshots)
|
|
11
|
+
* - `POST /send {text}`, `/stop`, `/reset`, `/config {agent?, mode?}`, `/view {path}`
|
|
12
|
+
* - `POST /actions/:id {decision: 'approve' | 'reject'}`, `GET /actions/:id/changes`
|
|
13
|
+
* - `POST /approvals/:id {decision: 'approve' | 'deny'}`: the agent CLI's own permission prompts
|
|
14
|
+
* - `POST /mcp`: the agent's MCP endpoint (bearer `agent.mcpToken`; browser requests refused)
|
|
15
|
+
*
|
|
16
|
+
* Requests another site started are refused (`Sec-Fetch-Site`, `Origin`).
|
|
17
|
+
*/
|
|
18
|
+
export function createAppAgentHandler(agent, options = {}) {
|
|
19
|
+
const base = (options.basePath ?? '/api/agent').replace(/\/$/, '');
|
|
20
|
+
const serverInfo = options.serverInfo ?? { name: agent.serverName, version: '1.0.0' };
|
|
21
|
+
const streams = new Set();
|
|
22
|
+
const handle = async (req, res) => {
|
|
23
|
+
const url = new URL(req.url ?? '/', 'http://local');
|
|
24
|
+
if (url.pathname !== base && !url.pathname.startsWith(`${base}/`))
|
|
25
|
+
return false;
|
|
26
|
+
const route = url.pathname.slice(base.length) || '/';
|
|
27
|
+
const method = req.method ?? 'GET';
|
|
28
|
+
try {
|
|
29
|
+
if (route === '/mcp') {
|
|
30
|
+
// Only the agent CLI calls this; a browser always sends Origin on POST.
|
|
31
|
+
if (req.headers.origin !== undefined)
|
|
32
|
+
return json(res, 403, { error: 'forbidden' });
|
|
33
|
+
if (!bearerOk(req.headers.authorization, agent.mcpToken))
|
|
34
|
+
return json(res, 401, { error: 'bad token' });
|
|
35
|
+
if (method !== 'POST')
|
|
36
|
+
return json(res, 405, { error: 'POST only' });
|
|
37
|
+
const message = await readJson(req);
|
|
38
|
+
// Again after the body: the session may have been dropped while it arrived.
|
|
39
|
+
if (!bearerOk(req.headers.authorization, agent.mcpToken))
|
|
40
|
+
return json(res, 401, { error: 'bad token' });
|
|
41
|
+
const response = await handleMcpMessage(message, agent.mcpTools(), serverInfo);
|
|
42
|
+
if (response === null) {
|
|
43
|
+
res.writeHead(202).end();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return json(res, 200, response);
|
|
47
|
+
}
|
|
48
|
+
if (crossSite(req))
|
|
49
|
+
return json(res, 403, { error: 'cross-site request' });
|
|
50
|
+
if (!options.authorize && !loopbackHost(req.headers.host, options.allowedHosts)) {
|
|
51
|
+
return json(res, 403, { error: 'unknown host' });
|
|
52
|
+
}
|
|
53
|
+
if (options.authorize && !(await options.authorize(req)))
|
|
54
|
+
return json(res, 403, { error: 'forbidden' });
|
|
55
|
+
if (method === 'GET' && route === '/state')
|
|
56
|
+
return json(res, 200, agent.get());
|
|
57
|
+
if (method === 'GET' && route === '/events') {
|
|
58
|
+
events(req, res, agent, streams);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (method === 'GET') {
|
|
62
|
+
const changes = /^\/actions\/([^/]+)\/changes$/.exec(route);
|
|
63
|
+
if (changes)
|
|
64
|
+
return json(res, 200, await agent.changes(decodeURIComponent(changes[1])));
|
|
65
|
+
return json(res, 404, { error: 'not found' });
|
|
66
|
+
}
|
|
67
|
+
if (method !== 'POST')
|
|
68
|
+
return json(res, 405, { error: 'method not allowed' });
|
|
69
|
+
const body = await readJson(req);
|
|
70
|
+
const field = (name) => body && typeof body === 'object' ? body[name] : undefined;
|
|
71
|
+
switch (route) {
|
|
72
|
+
case '/send':
|
|
73
|
+
agent.send(String(field('text') ?? ''));
|
|
74
|
+
break;
|
|
75
|
+
case '/stop':
|
|
76
|
+
agent.stop();
|
|
77
|
+
break;
|
|
78
|
+
case '/reset':
|
|
79
|
+
agent.reset();
|
|
80
|
+
break;
|
|
81
|
+
case '/config': {
|
|
82
|
+
const name = field('agent');
|
|
83
|
+
const mode = field('mode');
|
|
84
|
+
agent.configure({
|
|
85
|
+
...(typeof name === 'string' ? { agent: name } : {}),
|
|
86
|
+
...(typeof mode === 'string' ? { mode: mode } : {}),
|
|
87
|
+
});
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case '/view': {
|
|
91
|
+
const path = field('path');
|
|
92
|
+
if (typeof path !== 'string' || path.length > 500)
|
|
93
|
+
return json(res, 400, { error: 'path required' });
|
|
94
|
+
agent.setView(path);
|
|
95
|
+
res.writeHead(204).end();
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
default: {
|
|
99
|
+
const approval = /^\/approvals\/([^/]+)$/.exec(route);
|
|
100
|
+
if (approval) {
|
|
101
|
+
const answer = field('decision');
|
|
102
|
+
if (answer !== 'approve' && answer !== 'deny') {
|
|
103
|
+
return json(res, 400, { error: 'decision must be approve or deny' });
|
|
104
|
+
}
|
|
105
|
+
agent.approve(decodeURIComponent(approval[1]), answer);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
const action = /^\/actions\/([^/]+)$/.exec(route);
|
|
109
|
+
const decision = field('decision');
|
|
110
|
+
if (!action)
|
|
111
|
+
return json(res, 404, { error: 'not found' });
|
|
112
|
+
if (decision !== 'approve' && decision !== 'reject') {
|
|
113
|
+
return json(res, 400, { error: 'decision must be approve or reject' });
|
|
114
|
+
}
|
|
115
|
+
agent.decide(decodeURIComponent(action[1]), decision);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return json(res, 200, agent.get());
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const status = error instanceof AppAgentError ? error.status : error instanceof BadRequest ? 400 : 500;
|
|
122
|
+
return json(res, status, { error: error.message });
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
return Object.assign(handle, {
|
|
126
|
+
close() {
|
|
127
|
+
for (const stream of streams)
|
|
128
|
+
stream.end();
|
|
129
|
+
streams.clear();
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
class BadRequest extends Error {
|
|
134
|
+
}
|
|
135
|
+
function events(req, res, agent, streams) {
|
|
136
|
+
res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
|
|
137
|
+
res.write(`data: ${safeJson(agent.get())}\n\n`);
|
|
138
|
+
let timer = null;
|
|
139
|
+
// Streamed text changes the state per token; send the latest every 100 ms at most.
|
|
140
|
+
const unsubscribe = agent.subscribe(() => {
|
|
141
|
+
if (timer)
|
|
142
|
+
return;
|
|
143
|
+
timer = setTimeout(() => {
|
|
144
|
+
timer = null;
|
|
145
|
+
if (!res.writableEnded)
|
|
146
|
+
res.write(`data: ${safeJson(agent.get())}\n\n`);
|
|
147
|
+
}, 100);
|
|
148
|
+
});
|
|
149
|
+
const ping = setInterval(() => res.write(': ping\n\n'), 15_000);
|
|
150
|
+
streams.add(res);
|
|
151
|
+
const end = () => {
|
|
152
|
+
streams.delete(res);
|
|
153
|
+
unsubscribe();
|
|
154
|
+
clearInterval(ping);
|
|
155
|
+
if (timer)
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
};
|
|
158
|
+
req.on('close', end);
|
|
159
|
+
res.on('close', end);
|
|
160
|
+
}
|
|
161
|
+
// A page on another site (or another local dev server) must not drive the agent.
|
|
162
|
+
function crossSite(req) {
|
|
163
|
+
const site = req.headers['sec-fetch-site'];
|
|
164
|
+
if (site !== undefined && site !== 'same-origin' && site !== 'none')
|
|
165
|
+
return true;
|
|
166
|
+
const origin = req.headers.origin;
|
|
167
|
+
if (origin === undefined || req.method === 'GET')
|
|
168
|
+
return false;
|
|
169
|
+
try {
|
|
170
|
+
return new URL(origin).host !== req.headers.host;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Without app auth, only a loopback name may reach the agent: a rebinding domain that resolves
|
|
177
|
+
// to 127.0.0.1 still sends its own name as Host.
|
|
178
|
+
function loopbackHost(host, allowed = []) {
|
|
179
|
+
if (!host)
|
|
180
|
+
return false;
|
|
181
|
+
let name;
|
|
182
|
+
try {
|
|
183
|
+
name = new URL(`http://${host}`).hostname;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
return (name === 'localhost' ||
|
|
189
|
+
name.endsWith('.localhost') ||
|
|
190
|
+
name === '127.0.0.1' ||
|
|
191
|
+
name === '[::1]' ||
|
|
192
|
+
allowed.includes(name));
|
|
193
|
+
}
|
|
194
|
+
function bearerOk(header, token) {
|
|
195
|
+
if (!header?.startsWith('Bearer '))
|
|
196
|
+
return false;
|
|
197
|
+
const a = Buffer.from(header.slice('Bearer '.length));
|
|
198
|
+
const b = Buffer.from(token);
|
|
199
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
200
|
+
}
|
|
201
|
+
function json(res, status, body) {
|
|
202
|
+
res.writeHead(status, { 'content-type': 'application/json', 'cache-control': 'no-store' });
|
|
203
|
+
res.end(safeJson(body));
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
// Frameworks that already parsed the body leave it on `req.body`.
|
|
207
|
+
async function readJson(req) {
|
|
208
|
+
if (req.body !== undefined)
|
|
209
|
+
return req.body;
|
|
210
|
+
let size = 0;
|
|
211
|
+
const chunks = [];
|
|
212
|
+
for await (const chunk of req) {
|
|
213
|
+
size += chunk.length;
|
|
214
|
+
if (size > MAX_BODY)
|
|
215
|
+
throw new BadRequest('request too large');
|
|
216
|
+
chunks.push(chunk);
|
|
217
|
+
}
|
|
218
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
219
|
+
if (!text)
|
|
220
|
+
return {};
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(text);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
throw new BadRequest('expected JSON');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/app-agent/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON that never throws: a custom agent's tool input can hold a BigInt (sent as a string) or a
|
|
3
|
+
* cycle (sent as `"[Circular]"`), or a `toJSON` or getter that throws (that object is copied field by
|
|
4
|
+
* field, and what cannot be read becomes `"[Unreadable]"`). An object reached twice without a cycle is
|
|
5
|
+
* written out each time.
|
|
6
|
+
*/
|
|
7
|
+
export declare function safeJson(value: unknown): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON that never throws: a custom agent's tool input can hold a BigInt (sent as a string) or a
|
|
3
|
+
* cycle (sent as `"[Circular]"`), or a `toJSON` or getter that throws (that object is copied field by
|
|
4
|
+
* field, and what cannot be read becomes `"[Unreadable]"`). An object reached twice without a cycle is
|
|
5
|
+
* written out each time.
|
|
6
|
+
*/
|
|
7
|
+
export function safeJson(value) {
|
|
8
|
+
try {
|
|
9
|
+
return stringify(value);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// A `toJSON` or getter that throws runs before any replacer sees the value.
|
|
13
|
+
return stringify(plain(value, []));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function stringify(value) {
|
|
17
|
+
const path = [];
|
|
18
|
+
return (JSON.stringify(value, function (_key, item) {
|
|
19
|
+
if (typeof item === 'bigint')
|
|
20
|
+
return String(item);
|
|
21
|
+
if (typeof item !== 'object' || item === null)
|
|
22
|
+
return item;
|
|
23
|
+
// `this` is the object holding `item`: drop the ancestors that are not on its path.
|
|
24
|
+
while (path.length && path.at(-1) !== this)
|
|
25
|
+
path.pop();
|
|
26
|
+
if (path.includes(item))
|
|
27
|
+
return '[Circular]';
|
|
28
|
+
path.push(item);
|
|
29
|
+
return item;
|
|
30
|
+
}) ?? '');
|
|
31
|
+
}
|
|
32
|
+
// A copy made of plain data only: what cannot be read becomes `"[Unreadable]"`.
|
|
33
|
+
function plain(value, path) {
|
|
34
|
+
if (typeof value === 'bigint')
|
|
35
|
+
return String(value);
|
|
36
|
+
// A function (a `toJSON` among them) is left out, as JSON leaves it out.
|
|
37
|
+
if (typeof value === 'function')
|
|
38
|
+
return undefined;
|
|
39
|
+
if (typeof value !== 'object' || value === null)
|
|
40
|
+
return value;
|
|
41
|
+
if (path.includes(value))
|
|
42
|
+
return '[Circular]';
|
|
43
|
+
const inner = [...path, value];
|
|
44
|
+
try {
|
|
45
|
+
if (Array.isArray(value))
|
|
46
|
+
return value.map((item) => plain(item, inner));
|
|
47
|
+
const copy = {};
|
|
48
|
+
for (const key of Object.keys(value)) {
|
|
49
|
+
try {
|
|
50
|
+
copy[key] = plain(value[key], inner);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
copy[key] = '[Unreadable]';
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return copy;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return '[Unreadable]';
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/app-agent/json.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,4EAA4E;QAC5E,OAAO,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;IACrC,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,OAAO,CACL,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,UAAyB,IAAI,EAAE,IAAa;QAChE,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC3D,oFAAoF;QACpF,OAAO,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,IAAI,CAAC,GAAG,EAAE,CAAC;QACvD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,YAAY,CAAC;QAC7C,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,IAAI,EAAE,CACT,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,SAAS,KAAK,CAAC,KAAc,EAAE,IAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACpD,yEAAyE;IACzE,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IAClD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,YAAY,CAAC;IAC9C,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;QACzE,MAAM,IAAI,GAA4B,EAAE,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAE,KAAiC,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;YACpE,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,cAAc,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** A tool the app serves to the agent over MCP. */
|
|
2
|
+
export interface McpTool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: Record<string, unknown>;
|
|
6
|
+
call(args: Record<string, unknown>): Promise<string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Answer one MCP Streamable HTTP message: JSON responses only, no server-sent stream.
|
|
10
|
+
* Returns null for notifications (reply 202 with no body).
|
|
11
|
+
*/
|
|
12
|
+
export declare function handleMcpMessage(message: unknown, tools: readonly McpTool[], serverInfo: {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
}): Promise<object | null>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const DEFAULT_PROTOCOL = '2025-06-18';
|
|
2
|
+
/**
|
|
3
|
+
* Answer one MCP Streamable HTTP message: JSON responses only, no server-sent stream.
|
|
4
|
+
* Returns null for notifications (reply 202 with no body).
|
|
5
|
+
*/
|
|
6
|
+
export async function handleMcpMessage(message, tools, serverInfo) {
|
|
7
|
+
if (!isRequest(message))
|
|
8
|
+
return rpcError(null, -32600, 'invalid request');
|
|
9
|
+
const { id, method, params } = message;
|
|
10
|
+
if (id === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
switch (method) {
|
|
13
|
+
case 'initialize':
|
|
14
|
+
return result(id, {
|
|
15
|
+
protocolVersion: typeof params?.protocolVersion === 'string' ? params.protocolVersion : DEFAULT_PROTOCOL,
|
|
16
|
+
capabilities: { tools: { listChanged: false } },
|
|
17
|
+
serverInfo,
|
|
18
|
+
});
|
|
19
|
+
case 'ping':
|
|
20
|
+
return result(id, {});
|
|
21
|
+
case 'tools/list':
|
|
22
|
+
return result(id, {
|
|
23
|
+
tools: tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })),
|
|
24
|
+
});
|
|
25
|
+
case 'tools/call': {
|
|
26
|
+
const tool = tools.find((candidate) => candidate.name === params?.name);
|
|
27
|
+
if (!tool)
|
|
28
|
+
return rpcError(id, -32602, `unknown tool ${String(params?.name)}`);
|
|
29
|
+
const args = isObject(params?.arguments) ? params.arguments : {};
|
|
30
|
+
try {
|
|
31
|
+
return result(id, { content: [{ type: 'text', text: await tool.call(args) }] });
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return result(id, { content: [{ type: 'text', text: error.message }], isError: true });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
default:
|
|
38
|
+
return rpcError(id, -32601, `method not found: ${method}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function result(id, value) {
|
|
42
|
+
return { jsonrpc: '2.0', id, result: value };
|
|
43
|
+
}
|
|
44
|
+
function rpcError(id, code, message) {
|
|
45
|
+
return { jsonrpc: '2.0', id, error: { code, message } };
|
|
46
|
+
}
|
|
47
|
+
function isObject(value) {
|
|
48
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
function isRequest(value) {
|
|
51
|
+
return isObject(value) && value.jsonrpc === '2.0' && typeof value.method === 'string';
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=mcp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../../src/app-agent/mcp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CodingAgent } from '../agents/types.js';
|
|
2
|
+
import type { AppAgentMode } from './state.js';
|
|
3
|
+
/**
|
|
4
|
+
* The agents an app agent offers by default: the drivers that take per-run MCP servers,
|
|
5
|
+
* locked down per mode. In `use` mode they get no shell and no file edits, only the app's
|
|
6
|
+
* tools. In `dev` mode they edit files and may run `commands` (each with any arguments), never
|
|
7
|
+
* commit or push (that goes through `propose_commit`). Codex has no per-command allowlist:
|
|
8
|
+
* in `dev` mode its shell is bounded only by the `workspace-write` sandbox. In both modes the
|
|
9
|
+
* only MCP server is the app's: the user's own configured servers are switched off. Claude's
|
|
10
|
+
* hooks are off in both modes; Copilot's in `use` mode only (through settings in the app
|
|
11
|
+
* agent's own working directory), since in `dev` mode they live in the user's repository.
|
|
12
|
+
*/
|
|
13
|
+
export declare function defaultAppAgents(mode: AppAgentMode, commands?: readonly string[]): CodingAgent[];
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { claude } from '../agents/drivers/claude.js';
|
|
2
|
+
import { codex } from '../agents/drivers/codex.js';
|
|
3
|
+
import { copilot } from '../agents/drivers/copilot.js';
|
|
4
|
+
const WEB_TOOLS = ['WebFetch', 'WebSearch'];
|
|
5
|
+
/**
|
|
6
|
+
* The agents an app agent offers by default: the drivers that take per-run MCP servers,
|
|
7
|
+
* locked down per mode. In `use` mode they get no shell and no file edits, only the app's
|
|
8
|
+
* tools. In `dev` mode they edit files and may run `commands` (each with any arguments), never
|
|
9
|
+
* commit or push (that goes through `propose_commit`). Codex has no per-command allowlist:
|
|
10
|
+
* in `dev` mode its shell is bounded only by the `workspace-write` sandbox. In both modes the
|
|
11
|
+
* only MCP server is the app's: the user's own configured servers are switched off. Claude's
|
|
12
|
+
* hooks are off in both modes; Copilot's in `use` mode only (through settings in the app
|
|
13
|
+
* agent's own working directory), since in `dev` mode they live in the user's repository.
|
|
14
|
+
*/
|
|
15
|
+
export function defaultAppAgents(mode, commands = []) {
|
|
16
|
+
if (mode === 'use') {
|
|
17
|
+
return [
|
|
18
|
+
claude({
|
|
19
|
+
permissionMode: 'dontAsk',
|
|
20
|
+
strictMcpConfig: true,
|
|
21
|
+
// The user's hooks would run shell commands on prompts that carry app data.
|
|
22
|
+
settings: { disableAllHooks: true },
|
|
23
|
+
// Read tools too: user settings may pre-allow them, and dontAsk runs pre-allowed tools.
|
|
24
|
+
disallowedTools: [
|
|
25
|
+
'Bash',
|
|
26
|
+
'Read',
|
|
27
|
+
'Grep',
|
|
28
|
+
'Glob',
|
|
29
|
+
'LS',
|
|
30
|
+
'Edit',
|
|
31
|
+
'Write',
|
|
32
|
+
'MultiEdit',
|
|
33
|
+
'NotebookEdit',
|
|
34
|
+
'NotebookRead',
|
|
35
|
+
'Task',
|
|
36
|
+
...WEB_TOOLS,
|
|
37
|
+
],
|
|
38
|
+
}),
|
|
39
|
+
// The sandbox alone still lets the shell read host files: drop the shell, image viewer and
|
|
40
|
+
// web search.
|
|
41
|
+
codex({
|
|
42
|
+
sandbox: 'read-only',
|
|
43
|
+
// Apps (connectors) are on by default and bypass the sandbox's network rules.
|
|
44
|
+
config: {
|
|
45
|
+
'features.shell_tool': false,
|
|
46
|
+
'features.view_image': false,
|
|
47
|
+
'features.apps': false,
|
|
48
|
+
'features.hooks': false,
|
|
49
|
+
web_search: 'disabled',
|
|
50
|
+
},
|
|
51
|
+
onlyTaskMcpServers: true,
|
|
52
|
+
}),
|
|
53
|
+
copilot({
|
|
54
|
+
allowAllTools: false,
|
|
55
|
+
denyTools: ['shell', 'write', 'read', 'url', 'memory'],
|
|
56
|
+
// A deny rule leaves read-only tools such as web search in place; only this removes them.
|
|
57
|
+
excludedTools: ['web_fetch', 'web_search'],
|
|
58
|
+
onlyTaskMcpServers: true,
|
|
59
|
+
}),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
// Each command with any arguments. No git rules: `git diff --no-index` or `git log --output`
|
|
63
|
+
// reach outside cwd; git asks through the agent's own permission prompt instead.
|
|
64
|
+
const allowed = [...commands];
|
|
65
|
+
return [
|
|
66
|
+
claude({
|
|
67
|
+
permissionMode: 'acceptEdits',
|
|
68
|
+
strictMcpConfig: true,
|
|
69
|
+
// acceptEdits already reads and edits inside cwd without asking; no bare Read/Edit rules,
|
|
70
|
+
// which would match every path on the machine.
|
|
71
|
+
allowedTools: allowed.map((c) => `Bash(${c}:*)`),
|
|
72
|
+
disallowedTools: ['Bash(git commit:*)', 'Bash(git push:*)', ...WEB_TOOLS],
|
|
73
|
+
// Hooks run commands outside these rules, `git commit` included.
|
|
74
|
+
settings: { disableAllHooks: true },
|
|
75
|
+
}),
|
|
76
|
+
codex({
|
|
77
|
+
sandbox: 'workspace-write',
|
|
78
|
+
config: {
|
|
79
|
+
'features.apps': false,
|
|
80
|
+
'features.hooks': false,
|
|
81
|
+
web_search: 'disabled',
|
|
82
|
+
// The user's own config may open the sandbox up: no network, no roots beyond cwd.
|
|
83
|
+
'sandbox_workspace_write.network_access': false,
|
|
84
|
+
'sandbox_workspace_write.writable_roots': [],
|
|
85
|
+
},
|
|
86
|
+
onlyTaskMcpServers: true,
|
|
87
|
+
}),
|
|
88
|
+
copilot({
|
|
89
|
+
allowAllTools: false,
|
|
90
|
+
// `shell(npm test)` is the exact command only; `:*` adds any arguments.
|
|
91
|
+
allowTools: ['write', ...allowed.flatMap((c) => [`shell(${c})`, `shell(${c}:*)`])],
|
|
92
|
+
denyTools: ['shell(git commit)', 'shell(git commit:*)', 'shell(git push)', 'shell(git push:*)'],
|
|
93
|
+
onlyTaskMcpServers: true,
|
|
94
|
+
}),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/app-agent/policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,6BAA6B,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,8BAA8B,CAAC;AAIvD,MAAM,SAAS,GAAG,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAE5C;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAkB,EAAE,WAA8B,EAAE;IACnF,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,OAAO;YACL,MAAM,CAAC;gBACL,cAAc,EAAE,SAAS;gBACzB,eAAe,EAAE,IAAI;gBACrB,4EAA4E;gBAC5E,QAAQ,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;gBACnC,wFAAwF;gBACxF,eAAe,EAAE;oBACf,MAAM;oBACN,MAAM;oBACN,MAAM;oBACN,MAAM;oBACN,IAAI;oBACJ,MAAM;oBACN,OAAO;oBACP,WAAW;oBACX,cAAc;oBACd,cAAc;oBACd,MAAM;oBACN,GAAG,SAAS;iBACb;aACF,CAAC;YACF,2FAA2F;YAC3F,cAAc;YACd,KAAK,CAAC;gBACJ,OAAO,EAAE,WAAW;gBACpB,8EAA8E;gBAC9E,MAAM,EAAE;oBACN,qBAAqB,EAAE,KAAK;oBAC5B,qBAAqB,EAAE,KAAK;oBAC5B,eAAe,EAAE,KAAK;oBACtB,gBAAgB,EAAE,KAAK;oBACvB,UAAU,EAAE,UAAU;iBACvB;gBACD,kBAAkB,EAAE,IAAI;aACzB,CAAC;YACF,OAAO,CAAC;gBACN,aAAa,EAAE,KAAK;gBACpB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;gBACtD,0FAA0F;gBAC1F,aAAa,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC;gBAC1C,kBAAkB,EAAE,IAAI;aACzB,CAAC;SACH,CAAC;IACJ,CAAC;IACD,6FAA6F;IAC7F,iFAAiF;IACjF,MAAM,OAAO,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC9B,OAAO;QACL,MAAM,CAAC;YACL,cAAc,EAAE,aAAa;YAC7B,eAAe,EAAE,IAAI;YACrB,0FAA0F;YAC1F,+CAA+C;YAC/C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC;YAChD,eAAe,EAAE,CAAC,oBAAoB,EAAE,kBAAkB,EAAE,GAAG,SAAS,CAAC;YACzE,iEAAiE;YACjE,QAAQ,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;SACpC,CAAC;QACF,KAAK,CAAC;YACJ,OAAO,EAAE,iBAAiB;YAC1B,MAAM,EAAE;gBACN,eAAe,EAAE,KAAK;gBACtB,gBAAgB,EAAE,KAAK;gBACvB,UAAU,EAAE,UAAU;gBACtB,kFAAkF;gBAClF,wCAAwC,EAAE,KAAK;gBAC/C,wCAAwC,EAAE,EAAE;aAC7C;YACD,kBAAkB,EAAE,IAAI;SACzB,CAAC;QACF,OAAO,CAAC;YACN,aAAa,EAAE,KAAK;YACpB,wEAAwE;YACxE,UAAU,EAAE,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YAClF,SAAS,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,mBAAmB,CAAC;YAC/F,kBAAkB,EAAE,IAAI;SACzB,CAAC;KACH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { SessionState, SessionTurn } from '../agents/session.js';
|
|
2
|
+
/** `use`: the agent works the app through its tools. `dev`: it also edits the app's own code. */
|
|
3
|
+
export type AppAgentMode = 'use' | 'dev';
|
|
4
|
+
export type AppActionStatus = 'pending' | 'running' | 'done' | 'failed' | 'rejected';
|
|
5
|
+
/** A write waiting for the user, or done. */
|
|
6
|
+
export interface AppAction {
|
|
7
|
+
id: string;
|
|
8
|
+
kind: 'tool' | 'commit';
|
|
9
|
+
/** The one line the user reads before deciding. */
|
|
10
|
+
summary: string;
|
|
11
|
+
tool?: {
|
|
12
|
+
name: string;
|
|
13
|
+
args: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
/** `base`: the commit the reviewed tree is a change on; null before the first commit. */
|
|
16
|
+
commit?: {
|
|
17
|
+
message: string;
|
|
18
|
+
tree: string;
|
|
19
|
+
base: string | null;
|
|
20
|
+
ref: string | null;
|
|
21
|
+
};
|
|
22
|
+
status: AppActionStatus;
|
|
23
|
+
result?: string;
|
|
24
|
+
createdAt: number;
|
|
25
|
+
}
|
|
26
|
+
/** A chip the agent offers under a reply; clicking it opens that page of the app. */
|
|
27
|
+
export interface AppLink {
|
|
28
|
+
turnId: number;
|
|
29
|
+
label: string;
|
|
30
|
+
path: string;
|
|
31
|
+
}
|
|
32
|
+
export interface AppAgentState {
|
|
33
|
+
name: string;
|
|
34
|
+
mode: AppAgentMode;
|
|
35
|
+
/** `dev` is offered only when the app configured it. */
|
|
36
|
+
modes: AppAgentMode[];
|
|
37
|
+
agent: string;
|
|
38
|
+
agents: Array<{
|
|
39
|
+
name: string;
|
|
40
|
+
installed: boolean;
|
|
41
|
+
}>;
|
|
42
|
+
session: SessionState | null;
|
|
43
|
+
/**
|
|
44
|
+
* Turns from before the last agent or mode switch, oldest first. They keep their place in
|
|
45
|
+
* the conversation with negative ids, so links still point at them.
|
|
46
|
+
*/
|
|
47
|
+
history: SessionTurn[];
|
|
48
|
+
actions: AppAction[];
|
|
49
|
+
links: AppLink[];
|
|
50
|
+
/** The page the app last reported as open. */
|
|
51
|
+
view: string | null;
|
|
52
|
+
/** A bumped id asks the page to open `path` once. */
|
|
53
|
+
navigation: {
|
|
54
|
+
id: number;
|
|
55
|
+
path: string;
|
|
56
|
+
} | null;
|
|
57
|
+
}
|
|
58
|
+
/** What a proposed commit would change, for the approval card. */
|
|
59
|
+
export interface TreeChanges {
|
|
60
|
+
files: Array<{
|
|
61
|
+
status: string;
|
|
62
|
+
path: string;
|
|
63
|
+
}>;
|
|
64
|
+
diff: string;
|
|
65
|
+
/** The diff was cut to fit: it cannot be reviewed here, so the commit cannot be approved. */
|
|
66
|
+
truncated?: boolean;
|
|
67
|
+
/** Changed files whose contents the diff cannot show (binary). */
|
|
68
|
+
binary?: string[];
|
|
69
|
+
}
|
|
70
|
+
/** Separates the briefing and page context from what the user typed. */
|
|
71
|
+
export declare const PROMPT_MARKER = "\n\nUser message:\n";
|
|
72
|
+
/** What the user typed, without the briefing or page context in front of it. */
|
|
73
|
+
export declare function userText(prompt: string): string;
|
|
74
|
+
/** Notes the app sends the agent (an action's outcome) start with this. */
|
|
75
|
+
export declare const APP_NOTE = "[app] ";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Separates the briefing and page context from what the user typed. */
|
|
2
|
+
export const PROMPT_MARKER = '\n\nUser message:\n';
|
|
3
|
+
/** What the user typed, without the briefing or page context in front of it. */
|
|
4
|
+
export function userText(prompt) {
|
|
5
|
+
const at = prompt.lastIndexOf(PROMPT_MARKER);
|
|
6
|
+
return at < 0 ? prompt : prompt.slice(at + PROMPT_MARKER.length);
|
|
7
|
+
}
|
|
8
|
+
/** Notes the app sends the agent (an action's outcome) start with this. */
|
|
9
|
+
export const APP_NOTE = '[app] ';
|
|
10
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/app-agent/state.ts"],"names":[],"mappings":"AA4DA,wEAAwE;AACxE,MAAM,CAAC,MAAM,aAAa,GAAG,qBAAqB,CAAC;AAEnD,gFAAgF;AAChF,MAAM,UAAU,QAAQ,CAAC,MAAc;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC;IAC7C,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AACnE,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AppAgentError, createAppAgent } from './app-agent/app-agent.js';
|
|
2
|
+
export type { AppAgent, AppAgentOptions, AppAgentTrace, AppTool } from './app-agent/app-agent.js';
|
|
3
|
+
export { createAppAgentHandler } from './app-agent/http.js';
|
|
4
|
+
export type { AppAgentHandler, AppAgentHandlerOptions } from './app-agent/http.js';
|
|
5
|
+
export { defaultAppAgents } from './app-agent/policy.js';
|
|
6
|
+
export { APP_NOTE, PROMPT_MARKER, userText } from './app-agent/state.js';
|
|
7
|
+
export type { AppAction, AppActionStatus, AppAgentMode, AppAgentState, AppLink, TreeChanges, } from './app-agent/state.js';
|
|
8
|
+
export type { McpTool } from './app-agent/mcp.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AppAgentError, createAppAgent } from './app-agent/app-agent.js';
|
|
2
|
+
export { createAppAgentHandler } from './app-agent/http.js';
|
|
3
|
+
export { defaultAppAgents } from './app-agent/policy.js';
|
|
4
|
+
export { APP_NOTE, PROMPT_MARKER, userText } from './app-agent/state.js';
|
|
5
|
+
//# sourceMappingURL=app-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-agent.js","sourceRoot":"","sources":["../src/app-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAEzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
|