@agent-native/core 0.19.0 → 0.19.3
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/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +1 -1
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +34 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +47 -256
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/connect.d.ts +3 -2
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +12 -8
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +3 -3
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +19 -8
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -3
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +4 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts +43 -0
- package/dist/client/dynamic-suggestions.d.ts.map +1 -0
- package/dist/client/dynamic-suggestions.js +344 -0
- package/dist/client/dynamic-suggestions.js.map +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +2 -2
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/coding-tools/index.d.ts +31 -0
- package/dist/coding-tools/index.d.ts.map +1 -0
- package/dist/coding-tools/index.js +411 -0
- package/dist/coding-tools/index.js.map +1 -0
- package/dist/mcp/build-server.d.ts +33 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +33 -10
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts +3 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +115 -26
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +382 -74
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/mcp/org-directory.d.ts +83 -0
- package/dist/mcp/org-directory.d.ts.map +1 -0
- package/dist/mcp/org-directory.js +201 -0
- package/dist/mcp/org-directory.js.map +1 -0
- package/dist/mcp/server.d.ts +38 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +222 -77
- package/dist/mcp/server.js.map +1 -1
- package/dist/scripts/dev/index.d.ts +6 -4
- package/dist/scripts/dev/index.d.ts.map +1 -1
- package/dist/scripts/dev/index.js +28 -13
- package/dist/scripts/dev/index.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -6
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +65 -32
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.js +2 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/agents-bundle.d.ts +3 -3
- package/dist/server/agents-bundle.js +5 -5
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/auth.d.ts +8 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +8 -1
- package/dist/server/auth.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +17 -2
- package/dist/server/sentry.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/client.md +15 -0
- package/docs/content/code-agents-ui.md +11 -1
- package/docs/content/drop-in-agent.md +3 -1
- package/docs/content/external-agents.md +27 -6
- package/docs/content/frames.md +1 -1
- package/docs/content/mcp-clients.md +2 -0
- package/docs/content/mcp-protocol.md +4 -2
- package/docs/content/migration-workbench.md +5 -0
- package/package.json +1 -1
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { getMethod, getHeader } from "h3";
|
|
30
30
|
import { readBody } from "../server/h3-helpers.js";
|
|
31
|
-
import { getSession, getConfiguredLoginHtml } from "../server/auth.js";
|
|
31
|
+
import { getSession, getConfiguredLoginHtml, isLoopbackRequest, } from "../server/auth.js";
|
|
32
32
|
import { signA2AToken } from "../a2a/client.js";
|
|
33
33
|
import { getOrgDomain } from "../org/context.js";
|
|
34
34
|
import { randomUUID } from "node:crypto";
|
|
35
|
-
import { recordMintedToken, listTokens, revokeToken, createDeviceCode, getDeviceCode, approveDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, releaseDeviceCodeMint, expireDeviceCode, MCP_CONNECT_SCOPE, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
|
|
35
|
+
import { recordMintedToken, listTokens, revokeToken, createDeviceCode, getDeviceCode, approveDeviceCode, consumeDeviceCode, claimDeviceCodeForMint, finishDeviceCodeMint, releaseDeviceCodeMint, expireDeviceCode, MCP_CONNECT_SCOPE, DEFAULT_TOKEN_TTL_DAYS, MIN_TOKEN_TTL_DAYS, MAX_TOKEN_TTL_DAYS, DEVICE_CODE_TTL_MS, } from "./connect-store.js";
|
|
36
36
|
/** Device-flow poll interval hint (seconds). */
|
|
37
37
|
const DEVICE_POLL_INTERVAL_S = 3;
|
|
38
38
|
// Human-typable user code: 8 base32 chars, dashed XXXX-XXXX.
|
|
@@ -89,6 +89,17 @@ function appLabel(origin, options) {
|
|
|
89
89
|
function serverName(origin, options) {
|
|
90
90
|
return `agent-native-${appLabel(origin, options)}`;
|
|
91
91
|
}
|
|
92
|
+
function canUseDevOpenConnect(event) {
|
|
93
|
+
// Loopback determined from the real socket peer (isLoopbackRequest →
|
|
94
|
+
// getRequestIP without xForwardedFor), NOT a parsed `Host` header — the
|
|
95
|
+
// header is client-controlled, and it also handles IPv6 `::1`. A
|
|
96
|
+
// misconfigured public deploy with no secret thus can't unlock dev-open
|
|
97
|
+
// by spoofing `Host: localhost`.
|
|
98
|
+
return (isLoopbackRequest(event) &&
|
|
99
|
+
!process.env.A2A_SECRET?.trim() &&
|
|
100
|
+
!process.env.ACCESS_TOKEN?.trim() &&
|
|
101
|
+
!process.env.ACCESS_TOKENS?.trim());
|
|
102
|
+
}
|
|
92
103
|
function escapeHtml(s) {
|
|
93
104
|
return s
|
|
94
105
|
.replace(/&/g, "&")
|
|
@@ -142,17 +153,23 @@ async function mintConnectToken(params) {
|
|
|
142
153
|
});
|
|
143
154
|
return { token, jti };
|
|
144
155
|
}
|
|
145
|
-
function mcpResultPayload(appUrl,
|
|
156
|
+
function mcpResultPayload(appUrl, options, auth) {
|
|
146
157
|
const mcpUrl = `${appUrl}/_agent-native/mcp`;
|
|
147
158
|
const name = serverName(appUrl, options);
|
|
159
|
+
const headers = {};
|
|
160
|
+
if (auth.token)
|
|
161
|
+
headers.Authorization = `Bearer ${auth.token}`;
|
|
162
|
+
if (!auth.token && auth.ownerEmail) {
|
|
163
|
+
headers["X-Agent-Native-Owner-Email"] = auth.ownerEmail;
|
|
164
|
+
}
|
|
148
165
|
return {
|
|
149
|
-
token,
|
|
166
|
+
token: auth.token ?? "",
|
|
150
167
|
mcpUrl,
|
|
151
168
|
serverName: name,
|
|
152
169
|
mcpServerEntry: {
|
|
153
170
|
type: "http",
|
|
154
171
|
url: mcpUrl,
|
|
155
|
-
headers
|
|
172
|
+
...(Object.keys(headers).length ? { headers } : {}),
|
|
156
173
|
},
|
|
157
174
|
cli: `agent-native connect ${appUrl}`,
|
|
158
175
|
};
|
|
@@ -160,11 +177,25 @@ function mcpResultPayload(appUrl, token, options) {
|
|
|
160
177
|
// ---------------------------------------------------------------------------
|
|
161
178
|
// Connect page (server-rendered HTML string)
|
|
162
179
|
// ---------------------------------------------------------------------------
|
|
180
|
+
function agentNativeMarkSvg(className, gradientId) {
|
|
181
|
+
return `<svg class="${className}" width="114" height="66" viewBox="0 0 114 66" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
|
|
182
|
+
<path d="M24.5537 65.7695H0L15.0859 39.4619L37.708 0L60.4912 39.4619H39.6396L24.5537 65.7695Z" fill="white"/>
|
|
183
|
+
<path d="M89.446 0H114L76.2921 65.7704H51.7383L89.446 0Z" fill="url(#${gradientId})"/>
|
|
184
|
+
<defs>
|
|
185
|
+
<linearGradient id="${gradientId}" x1="101.702" y1="67.4791" x2="113.672" y2="-37.4275" gradientUnits="userSpaceOnUse">
|
|
186
|
+
<stop stop-color="#00B5FF"/>
|
|
187
|
+
<stop offset="1" stop-color="#48FFE4"/>
|
|
188
|
+
</linearGradient>
|
|
189
|
+
</defs>
|
|
190
|
+
</svg>`;
|
|
191
|
+
}
|
|
163
192
|
function renderConnectPage(params) {
|
|
164
193
|
const { origin, connectBasePath, email, appName, userCode } = params;
|
|
165
194
|
const safeOrigin = escapeHtml(origin);
|
|
166
195
|
const safeEmail = escapeHtml(email);
|
|
167
196
|
const safeApp = escapeHtml(appName);
|
|
197
|
+
const brandMarkSvg = agentNativeMarkSvg("brand-mark", "agent-native-connect-brand-gradient");
|
|
198
|
+
const flowMarkSvg = agentNativeMarkSvg("flow-mark", "agent-native-connect-flow-gradient");
|
|
168
199
|
const safeUserCode = userCode && USER_CODE_RE.test(userCode) ? escapeHtml(userCode) : "";
|
|
169
200
|
return `<!DOCTYPE html>
|
|
170
201
|
<html lang="en">
|
|
@@ -176,119 +207,315 @@ function renderConnectPage(params) {
|
|
|
176
207
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
177
208
|
:root {
|
|
178
209
|
color-scheme: dark;
|
|
179
|
-
--bg: #09090b; --panel: #
|
|
180
|
-
--
|
|
210
|
+
--bg: #09090b; --panel: #121214; --panel-2: #0c0c0e;
|
|
211
|
+
--panel-soft: rgba(255,255,255,0.025);
|
|
212
|
+
--border: rgba(255,255,255,0.075); --border-strong: rgba(255,255,255,0.14);
|
|
213
|
+
--text: #f7f7f8; --muted: #a1a1aa; --subtle: #74747d;
|
|
181
214
|
--accent: #f4f4f5; --accent-fg: #09090b;
|
|
215
|
+
--ring: rgba(250,250,250,0.55);
|
|
182
216
|
--error: #fca5a5; --error-bg: rgba(127,29,29,0.18);
|
|
183
|
-
--ok: #86efac; --ok-bg: rgba(20,83,45,0.
|
|
217
|
+
--ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);
|
|
184
218
|
}
|
|
219
|
+
html, body { -webkit-font-smoothing: antialiased; }
|
|
185
220
|
body {
|
|
186
221
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
187
|
-
background: linear-gradient(180deg, #
|
|
222
|
+
background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);
|
|
188
223
|
color: var(--text); display: flex; align-items: center;
|
|
189
|
-
justify-content: center; min-height: 100vh; padding: 1rem;
|
|
224
|
+
justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;
|
|
190
225
|
}
|
|
191
226
|
.card {
|
|
192
|
-
width: 100%; max-width:
|
|
227
|
+
width: 100%; max-width: 440px;
|
|
193
228
|
background: var(--panel); border: 1px solid var(--border);
|
|
194
|
-
border-radius:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
229
|
+
border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,
|
|
230
|
+
0 30px 90px rgba(0,0,0,0.5);
|
|
231
|
+
padding: 1.25rem;
|
|
232
|
+
}
|
|
233
|
+
.topbar {
|
|
234
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
235
|
+
gap: 0.75rem; margin-bottom: 1.75rem;
|
|
236
|
+
}
|
|
237
|
+
.brand-lockup {
|
|
238
|
+
display: flex; align-items: center; gap: 0.55rem;
|
|
239
|
+
color: var(--muted); font-size: 0.78rem; font-weight: 600;
|
|
240
|
+
}
|
|
241
|
+
.brand-mark { width: 18px; height: auto; display: block; }
|
|
242
|
+
.app-pill {
|
|
243
|
+
max-width: 50%; border: 1px solid var(--border);
|
|
244
|
+
border-radius: 999px; padding: 0.28rem 0.55rem;
|
|
245
|
+
color: var(--subtle); font-size: 0.72rem; line-height: 1;
|
|
246
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
247
|
+
}
|
|
248
|
+
.hero { padding: 0 0.75rem; text-align: center; }
|
|
249
|
+
.flow {
|
|
250
|
+
display: flex; align-items: center; justify-content: center;
|
|
251
|
+
gap: 0; margin: 0 auto 1.1rem; width: fit-content;
|
|
252
|
+
}
|
|
253
|
+
.flow .tile {
|
|
254
|
+
width: 42px; height: 42px; border-radius: 8px;
|
|
255
|
+
display: flex; align-items: center; justify-content: center;
|
|
256
|
+
background: var(--panel-2); border: 1px solid var(--border-strong);
|
|
257
|
+
color: var(--text); flex-shrink: 0;
|
|
258
|
+
}
|
|
259
|
+
.flow-mark { width: 26px; height: auto; display: block; }
|
|
260
|
+
.flow .agent-symbol {
|
|
206
261
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
207
|
-
letter-spacing: 0.
|
|
262
|
+
font-size: 0.95rem; font-weight: 700; letter-spacing: -0.04em;
|
|
263
|
+
}
|
|
264
|
+
.flow .conn {
|
|
265
|
+
width: 30px; height: 1px; flex-shrink: 0;
|
|
266
|
+
background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
|
|
267
|
+
background-position: center;
|
|
268
|
+
}
|
|
269
|
+
.eyebrow {
|
|
270
|
+
text-align: center; font-size: 0.72rem; font-weight: 600;
|
|
271
|
+
letter-spacing: 0.08em; text-transform: uppercase;
|
|
272
|
+
color: var(--subtle); margin-bottom: 0.55rem;
|
|
273
|
+
}
|
|
274
|
+
h1 {
|
|
275
|
+
text-align: center; font-size: 1.45rem; font-weight: 680;
|
|
276
|
+
line-height: 1.25; margin-bottom: 0.55rem;
|
|
277
|
+
letter-spacing: -0.01em;
|
|
278
|
+
}
|
|
279
|
+
.sub {
|
|
280
|
+
text-align: center; color: var(--muted); font-size: 0.9rem;
|
|
281
|
+
line-height: 1.5; margin: 0 auto 0.9rem; max-width: 36ch;
|
|
282
|
+
}
|
|
283
|
+
.identity {
|
|
284
|
+
display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
|
|
285
|
+
gap: 0.25rem 0.45rem; color: var(--subtle); font-size: 0.78rem;
|
|
286
|
+
line-height: 1.35; margin: 0 auto 1.4rem; max-width: 34ch;
|
|
287
|
+
}
|
|
288
|
+
.identity strong { color: var(--muted); font-weight: 600; }
|
|
289
|
+
.identity .origin { overflow-wrap: anywhere; }
|
|
290
|
+
.device-strip {
|
|
291
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
292
|
+
gap: 0.75rem; border: 1px solid var(--border);
|
|
293
|
+
border-radius: 8px; padding: 0.55rem 0.65rem; margin: 0 0 0.9rem;
|
|
294
|
+
background: var(--panel-soft); color: var(--muted);
|
|
295
|
+
}
|
|
296
|
+
.device-strip .label {
|
|
297
|
+
font-size: 0.76rem; font-weight: 560; color: var(--subtle);
|
|
298
|
+
}
|
|
299
|
+
.device-strip .value {
|
|
300
|
+
font-size: 0.9rem; font-weight: 700;
|
|
301
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
302
|
+
letter-spacing: 0.08em; color: var(--text);
|
|
303
|
+
}
|
|
208
304
|
button {
|
|
209
305
|
cursor: pointer; font: inherit; font-weight: 600; border: none;
|
|
210
|
-
border-radius: 8px; padding: 0.
|
|
306
|
+
border-radius: 8px; padding: 0.78rem 1rem;
|
|
307
|
+
}
|
|
308
|
+
button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
|
|
309
|
+
.primary {
|
|
310
|
+
background: var(--accent); color: var(--accent-fg); width: 100%;
|
|
311
|
+
font-size: 0.95rem;
|
|
211
312
|
}
|
|
212
|
-
.primary { background:
|
|
213
|
-
.primary:disabled { opacity: 0.
|
|
313
|
+
.primary:hover:not(:disabled) { background: #e4e4e7; }
|
|
314
|
+
.primary:disabled { opacity: 0.55; cursor: default; }
|
|
214
315
|
.ghost {
|
|
215
316
|
background: transparent; color: var(--muted);
|
|
216
|
-
border: 1px solid var(--border); padding: 0.35rem 0.7rem;
|
|
217
|
-
font-size: 0.78rem; font-weight: 500;
|
|
317
|
+
border: 1px solid var(--border-strong); padding: 0.35rem 0.7rem;
|
|
318
|
+
font-size: 0.78rem; font-weight: 500; border-radius: 8px;
|
|
218
319
|
}
|
|
320
|
+
.ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }
|
|
219
321
|
pre {
|
|
220
|
-
background:
|
|
322
|
+
background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
|
|
221
323
|
padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;
|
|
222
324
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
223
325
|
color: #d4d4d8; margin: 0.5rem 0 1rem;
|
|
224
326
|
}
|
|
225
|
-
|
|
226
|
-
.
|
|
327
|
+
/* Advanced disclosure */
|
|
328
|
+
.advanced { margin: 0 0 1rem; }
|
|
329
|
+
.advanced > summary {
|
|
330
|
+
list-style: none; cursor: pointer; user-select: none;
|
|
331
|
+
display: flex; align-items: center; justify-content: center; gap: 0.35rem;
|
|
332
|
+
color: var(--subtle); font-size: 0.8rem; font-weight: 500;
|
|
333
|
+
padding: 0.5rem 0; text-align: center;
|
|
334
|
+
}
|
|
335
|
+
.advanced > summary::-webkit-details-marker { display: none; }
|
|
336
|
+
.advanced > summary:hover { color: var(--muted); }
|
|
337
|
+
.advanced > summary:focus-visible { outline: 2px solid var(--ring);
|
|
338
|
+
outline-offset: 2px; border-radius: 6px; }
|
|
339
|
+
.advanced > summary .chev {
|
|
340
|
+
width: 7px; height: 7px; border-right: 1.5px solid currentColor;
|
|
341
|
+
border-bottom: 1.5px solid currentColor; transform: rotate(45deg);
|
|
342
|
+
transition: transform 0.15s ease; margin-top: -3px;
|
|
343
|
+
}
|
|
344
|
+
.advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }
|
|
345
|
+
.advanced-body {
|
|
346
|
+
padding: 0.85rem 0.1rem 0.25rem;
|
|
347
|
+
}
|
|
348
|
+
.field { margin-bottom: 0.9rem; }
|
|
349
|
+
.field:last-child { margin-bottom: 0; }
|
|
350
|
+
.field label { display: block; font-size: 0.78rem; color: var(--muted);
|
|
227
351
|
margin-bottom: 0.35rem; }
|
|
228
352
|
.field input {
|
|
229
|
-
width: 100%; padding: 0.
|
|
230
|
-
background:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
353
|
+
width: 100%; padding: 0.6rem 0.7rem; font: inherit; color: var(--text);
|
|
354
|
+
background: var(--panel-2); border: 1px solid var(--border-strong);
|
|
355
|
+
border-radius: 8px;
|
|
356
|
+
}
|
|
357
|
+
.field input:focus-visible {
|
|
358
|
+
outline: none; border-color: var(--ring);
|
|
359
|
+
box-shadow: 0 0 0 3px rgba(250,250,250,0.12);
|
|
360
|
+
}
|
|
361
|
+
.connections {
|
|
362
|
+
margin-top: 1.1rem; border-top: 1px solid var(--border);
|
|
363
|
+
padding-top: 0.35rem;
|
|
364
|
+
}
|
|
365
|
+
.connections > summary {
|
|
366
|
+
list-style: none; cursor: pointer; user-select: none;
|
|
367
|
+
display: flex; align-items: center; gap: 0.55rem;
|
|
368
|
+
min-height: 2.2rem; color: var(--muted); font-size: 0.82rem;
|
|
369
|
+
}
|
|
370
|
+
.connections > summary::-webkit-details-marker { display: none; }
|
|
371
|
+
.connections > summary:focus-visible {
|
|
372
|
+
outline: 2px solid var(--ring); outline-offset: 2px; border-radius: 6px;
|
|
373
|
+
}
|
|
374
|
+
.connections-title { font-weight: 600; color: var(--muted); }
|
|
375
|
+
.connections-state {
|
|
376
|
+
margin-left: auto; color: var(--subtle); font-size: 0.73rem;
|
|
377
|
+
border: 1px solid var(--border); border-radius: 999px;
|
|
378
|
+
padding: 0.18rem 0.45rem; line-height: 1;
|
|
379
|
+
}
|
|
380
|
+
.connections .chev {
|
|
381
|
+
width: 7px; height: 7px; border-right: 1.5px solid currentColor;
|
|
382
|
+
border-bottom: 1.5px solid currentColor; transform: rotate(45deg);
|
|
383
|
+
transition: transform 0.15s ease; margin: -3px 0 0 0.15rem;
|
|
384
|
+
}
|
|
385
|
+
.connections[open] .chev { transform: rotate(225deg); margin-top: 2px; }
|
|
386
|
+
.token-list { padding-top: 0.4rem; }
|
|
237
387
|
.tok { display: flex; align-items: center; justify-content: space-between;
|
|
238
|
-
gap: 0.75rem; padding: 0.
|
|
388
|
+
gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);
|
|
239
389
|
font-size: 0.83rem; }
|
|
240
390
|
.tok:last-child { border-bottom: none; }
|
|
241
|
-
.tok .meta { color: var(--subtle); font-size: 0.74rem; }
|
|
391
|
+
.tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }
|
|
242
392
|
.tok.revoked { opacity: 0.45; }
|
|
243
|
-
.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
393
|
+
.empty-state {
|
|
394
|
+
color: var(--subtle); font-size: 0.78rem; line-height: 1.45;
|
|
395
|
+
padding: 0.3rem 0 0.45rem;
|
|
396
|
+
}
|
|
397
|
+
.msg { font-size: 0.83rem; padding: 0.7rem 0.8rem; border-radius: 8px;
|
|
398
|
+
margin-bottom: 0.9rem; display: none; line-height: 1.4; }
|
|
399
|
+
.msg.err { display: block; color: var(--error); background: var(--error-bg);
|
|
400
|
+
border: 1px solid rgba(252,165,165,0.16); }
|
|
401
|
+
.msg.ok { display: block; color: var(--ok); background: var(--ok-bg);
|
|
402
|
+
border: 1px solid var(--ok-border); }
|
|
403
|
+
.result-panel { padding-top: 0.15rem; }
|
|
404
|
+
.result-title {
|
|
405
|
+
color: var(--text); font-size: 0.95rem; font-weight: 650;
|
|
406
|
+
text-align: center; margin-bottom: 0.35rem;
|
|
407
|
+
}
|
|
408
|
+
.result-copy {
|
|
409
|
+
color: var(--muted); font-size: 0.83rem; line-height: 1.45;
|
|
410
|
+
text-align: center; margin: 0 auto 0.85rem; max-width: 34ch;
|
|
411
|
+
}
|
|
412
|
+
.section-label {
|
|
413
|
+
color: var(--subtle); font-size: 0.7rem; font-weight: 650;
|
|
414
|
+
letter-spacing: 0.08em; text-transform: uppercase; margin-top: 0.85rem;
|
|
415
|
+
}
|
|
416
|
+
@media (max-width: 480px) {
|
|
417
|
+
body { align-items: flex-start; padding: 0.75rem; }
|
|
418
|
+
.card { padding: 1rem; }
|
|
419
|
+
.hero { padding: 0; }
|
|
420
|
+
.topbar { margin-bottom: 1.35rem; }
|
|
421
|
+
h1 { font-size: 1.3rem; }
|
|
422
|
+
.app-pill { max-width: 46%; }
|
|
423
|
+
pre { font-size: 0.72rem; }
|
|
424
|
+
}
|
|
247
425
|
.hidden { display: none !important; }
|
|
248
426
|
</style>
|
|
249
427
|
</head>
|
|
250
428
|
<body>
|
|
251
429
|
<div class="card">
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
430
|
+
<div class="topbar">
|
|
431
|
+
<div class="brand-lockup">
|
|
432
|
+
${brandMarkSvg}
|
|
433
|
+
<span>Agent Native</span>
|
|
434
|
+
</div>
|
|
435
|
+
<div class="app-pill" title="${safeApp}">${safeApp}</div>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div class="hero">
|
|
439
|
+
<!-- "Connect an external agent" is kept as the accessible consent label. -->
|
|
440
|
+
<div class="flow" role="img" aria-label="Connect an external agent to ${safeApp}">
|
|
441
|
+
<span class="tile" aria-hidden="true">
|
|
442
|
+
${flowMarkSvg}
|
|
443
|
+
</span>
|
|
444
|
+
<span class="conn" aria-hidden="true"></span>
|
|
445
|
+
<span class="tile" aria-hidden="true">
|
|
446
|
+
<span class="agent-symbol"></></span>
|
|
447
|
+
</span>
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
<div class="eyebrow">Connect an external agent</div>
|
|
451
|
+
<h1>${safeUserCode ? `Authorize ${safeApp} from your terminal?` : `Connect ${safeApp} to an agent`}</h1>
|
|
452
|
+
<p class="sub">Allow Claude Code, Codex, or Cowork to use ${safeApp} with your account. You can revoke access anytime.</p>
|
|
453
|
+
<p class="identity">
|
|
454
|
+
<span>Signed in as <strong>${safeEmail}</strong></span>
|
|
455
|
+
<span aria-hidden="true">·</span>
|
|
456
|
+
<span class="origin">${safeOrigin}</span>
|
|
457
|
+
</p>
|
|
458
|
+
</div>
|
|
255
459
|
|
|
256
|
-
<div id="codeCallout" class="
|
|
257
|
-
<
|
|
258
|
-
<
|
|
460
|
+
<div id="codeCallout" class="device-strip ${safeUserCode ? "" : "hidden"}">
|
|
461
|
+
<span class="label">Device code</span>
|
|
462
|
+
<span class="value" id="userCodeValue">${safeUserCode}</span>
|
|
259
463
|
</div>
|
|
260
464
|
|
|
261
465
|
<div id="msg" class="msg"></div>
|
|
262
466
|
|
|
263
467
|
<div id="mintForm">
|
|
264
|
-
<div class="field">
|
|
265
|
-
<label for="label">Label (optional)</label>
|
|
266
|
-
<input id="label" type="text" placeholder="e.g. Claude Code on my laptop" maxlength="120" />
|
|
267
|
-
</div>
|
|
268
|
-
<div class="field">
|
|
269
|
-
<label for="ttl">Expires in (days, 1–365)</label>
|
|
270
|
-
<input id="ttl" type="number" min="1" max="365" value="${DEFAULT_TOKEN_TTL_DAYS}" />
|
|
271
|
-
</div>
|
|
272
468
|
<button id="authorizeBtn" class="primary">${safeUserCode ? "Authorize device" : "Create connection token"}</button>
|
|
469
|
+
<details class="advanced">
|
|
470
|
+
<summary>
|
|
471
|
+
Advanced options
|
|
472
|
+
<span class="chev" aria-hidden="true"></span>
|
|
473
|
+
</summary>
|
|
474
|
+
<div class="advanced-body">
|
|
475
|
+
<div class="field">
|
|
476
|
+
<label for="label">Label (optional)</label>
|
|
477
|
+
<input id="label" type="text" placeholder="e.g. Claude Code on my laptop" maxlength="120" />
|
|
478
|
+
</div>
|
|
479
|
+
<div class="field">
|
|
480
|
+
<label for="ttl">Expires in (days, 1–365)</label>
|
|
481
|
+
<input id="ttl" type="number" min="1" max="365" value="${DEFAULT_TOKEN_TTL_DAYS}" />
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</details>
|
|
273
485
|
</div>
|
|
274
486
|
|
|
275
|
-
<div id="result" class="hidden">
|
|
276
|
-
<
|
|
487
|
+
<div id="result" class="result-panel hidden">
|
|
488
|
+
<div class="result-title">Connection token created</div>
|
|
489
|
+
<p class="result-copy" id="resultMsg">Paste this into your agent's MCP config. The token is shown only once.</p>
|
|
490
|
+
<div class="section-label">MCP config</div>
|
|
277
491
|
<pre id="mcpJson"></pre>
|
|
278
|
-
<
|
|
279
|
-
|
|
492
|
+
<details class="advanced">
|
|
493
|
+
<summary>
|
|
494
|
+
Terminal alternative
|
|
495
|
+
<span class="chev" aria-hidden="true"></span>
|
|
496
|
+
</summary>
|
|
497
|
+
<div class="advanced-body">
|
|
498
|
+
<pre id="cliLine"></pre>
|
|
499
|
+
</div>
|
|
500
|
+
</details>
|
|
280
501
|
</div>
|
|
281
502
|
|
|
282
|
-
<
|
|
283
|
-
<
|
|
284
|
-
|
|
285
|
-
|
|
503
|
+
<details id="connections" class="connections">
|
|
504
|
+
<summary>
|
|
505
|
+
<span class="connections-title">Existing connections</span>
|
|
506
|
+
<span id="connectionsState" class="connections-state">Checking</span>
|
|
507
|
+
<span class="chev" aria-hidden="true"></span>
|
|
508
|
+
</summary>
|
|
509
|
+
<div id="tokenList" class="token-list"><div class="empty-state">Checking connections...</div></div>
|
|
510
|
+
</details>
|
|
286
511
|
</div>
|
|
287
512
|
<script>
|
|
288
513
|
(function () {
|
|
289
514
|
var BASE = ${JSON.stringify(joinAppPath(connectBasePath, "/_agent-native/mcp/connect"))};
|
|
290
515
|
var USER_CODE = ${JSON.stringify(safeUserCode || null)};
|
|
291
516
|
var msgEl = document.getElementById("msg");
|
|
517
|
+
var connectionsEl = document.getElementById("connections");
|
|
518
|
+
var connectionsStateEl = document.getElementById("connectionsState");
|
|
292
519
|
function showMsg(text, kind) {
|
|
293
520
|
msgEl.textContent = text;
|
|
294
521
|
msgEl.className = "msg " + (kind || "err");
|
|
@@ -321,10 +548,23 @@ function renderConnectPage(params) {
|
|
|
321
548
|
var listEl = document.getElementById("tokenList");
|
|
322
549
|
try {
|
|
323
550
|
var res = await fetch(BASE + "/tokens", { credentials: "same-origin" });
|
|
324
|
-
if (!res.ok) {
|
|
551
|
+
if (!res.ok) {
|
|
552
|
+
connectionsStateEl.textContent = "Unavailable";
|
|
553
|
+
connectionsEl.open = true;
|
|
554
|
+
listEl.innerHTML = '<div class="empty-state">Could not load connections.</div>';
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
325
557
|
var data = await res.json();
|
|
326
558
|
var tokens = (data && data.tokens) || [];
|
|
327
|
-
if (!tokens.length) {
|
|
559
|
+
if (!tokens.length) {
|
|
560
|
+
connectionsStateEl.textContent = "None";
|
|
561
|
+
connectionsEl.open = false;
|
|
562
|
+
listEl.innerHTML = '<div class="empty-state">Created connections will appear here for revoking later.</div>';
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
var activeCount = tokens.filter(function (t) { return !t.revokedAt; }).length;
|
|
566
|
+
connectionsStateEl.textContent = activeCount === 1 ? "1 active" : activeCount + " active";
|
|
567
|
+
connectionsEl.open = true;
|
|
328
568
|
listEl.innerHTML = "";
|
|
329
569
|
tokens.forEach(function (t) {
|
|
330
570
|
var div = document.createElement("div");
|
|
@@ -354,7 +594,9 @@ function renderConnectPage(params) {
|
|
|
354
594
|
listEl.appendChild(div);
|
|
355
595
|
});
|
|
356
596
|
} catch (e) {
|
|
357
|
-
|
|
597
|
+
connectionsStateEl.textContent = "Unavailable";
|
|
598
|
+
connectionsEl.open = true;
|
|
599
|
+
listEl.innerHTML = '<div class="empty-state">Could not load connections.</div>';
|
|
358
600
|
}
|
|
359
601
|
}
|
|
360
602
|
|
|
@@ -372,9 +614,57 @@ function renderConnectPage(params) {
|
|
|
372
614
|
showMsg((a.data && a.data.error) || "Could not authorize this device code.");
|
|
373
615
|
return;
|
|
374
616
|
}
|
|
375
|
-
showMsg("Device authorized
|
|
617
|
+
showMsg("Device authorized — finishing connection… you can return to your terminal.", "ok");
|
|
376
618
|
btn.classList.add("hidden");
|
|
377
619
|
document.getElementById("mintForm").classList.add("hidden");
|
|
620
|
+
var cc = document.getElementById("codeCallout");
|
|
621
|
+
if (cc) cc.classList.add("hidden");
|
|
622
|
+
// The token is minted a few seconds later, when the CLI next polls
|
|
623
|
+
// /device/poll — so a single loadTokens() here runs BEFORE the row
|
|
624
|
+
// exists and the list would wrongly read "No connections yet" until
|
|
625
|
+
// a manual reload. Snapshot the EXISTING non-revoked token ids first
|
|
626
|
+
// so we announce "Connected" only when THIS device's freshly-minted
|
|
627
|
+
// token appears — a user who already has tokens must not get a false
|
|
628
|
+
// success the instant they authorize.
|
|
629
|
+
var priorIds = {};
|
|
630
|
+
try {
|
|
631
|
+
var pr = await fetch(BASE + "/tokens", { credentials: "same-origin" });
|
|
632
|
+
if (pr.ok) {
|
|
633
|
+
var pd = await pr.json();
|
|
634
|
+
((pd && pd.tokens) || []).forEach(function (t) {
|
|
635
|
+
if (!t.revokedAt) priorIds[t.id] = true;
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
} catch (e) {}
|
|
639
|
+
loadTokens();
|
|
640
|
+
var tries = 0;
|
|
641
|
+
var iv = setInterval(async function () {
|
|
642
|
+
tries++;
|
|
643
|
+
try {
|
|
644
|
+
var res = await fetch(BASE + "/tokens", { credentials: "same-origin" });
|
|
645
|
+
if (res.ok) {
|
|
646
|
+
var data = await res.json();
|
|
647
|
+
var fresh = ((data && data.tokens) || []).filter(function (t) {
|
|
648
|
+
return !t.revokedAt && !priorIds[t.id];
|
|
649
|
+
});
|
|
650
|
+
if (fresh.length > 0) {
|
|
651
|
+
clearInterval(iv);
|
|
652
|
+
showMsg("Connected. This device can now act as you — manage or revoke it below.", "ok");
|
|
653
|
+
loadTokens();
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
} catch (e) {}
|
|
658
|
+
if (tries >= 30) {
|
|
659
|
+
// No new token appeared in the window — e.g. the loopback
|
|
660
|
+
// dev-open path writes a header-only config and never mints.
|
|
661
|
+
// Don't claim "Connected" (we couldn't confirm a device token);
|
|
662
|
+
// keep the "authorized" message and just refresh the list.
|
|
663
|
+
clearInterval(iv);
|
|
664
|
+
loadTokens();
|
|
665
|
+
}
|
|
666
|
+
}, 2000);
|
|
667
|
+
return;
|
|
378
668
|
} else {
|
|
379
669
|
var m = await postJson("/token", { label: label, ttlDays: ttlDays });
|
|
380
670
|
if (!m.ok) {
|
|
@@ -459,6 +749,9 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
459
749
|
if (!session?.email)
|
|
460
750
|
return json({ error: "Unauthorized" }, 401);
|
|
461
751
|
if (!process.env.A2A_SECRET) {
|
|
752
|
+
if (canUseDevOpenConnect(event)) {
|
|
753
|
+
return json(mcpResultPayload(appUrl, options, { ownerEmail: session.email }));
|
|
754
|
+
}
|
|
462
755
|
return json({
|
|
463
756
|
error: "This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.",
|
|
464
757
|
}, 503);
|
|
@@ -475,7 +768,7 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
475
768
|
label,
|
|
476
769
|
ttlDays,
|
|
477
770
|
});
|
|
478
|
-
return json(mcpResultPayload(appUrl,
|
|
771
|
+
return json(mcpResultPayload(appUrl, options, { token }));
|
|
479
772
|
}
|
|
480
773
|
catch {
|
|
481
774
|
return json({ error: "Failed to mint token." }, 500);
|
|
@@ -557,6 +850,21 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
557
850
|
}
|
|
558
851
|
// status === "approved" && ownerEmail bound → mint exactly once.
|
|
559
852
|
if (!process.env.A2A_SECRET) {
|
|
853
|
+
if (canUseDevOpenConnect(event)) {
|
|
854
|
+
const consumed = await consumeDeviceCode(deviceCode, `dev-open-${randomUUID()}`);
|
|
855
|
+
if (!consumed) {
|
|
856
|
+
const fresh = await getDeviceCode(deviceCode);
|
|
857
|
+
if (fresh?.status === "consumed")
|
|
858
|
+
return json({ status: "consumed" });
|
|
859
|
+
return json({ status: "pending" });
|
|
860
|
+
}
|
|
861
|
+
return json({
|
|
862
|
+
status: "approved",
|
|
863
|
+
...mcpResultPayload(appUrl, options, {
|
|
864
|
+
ownerEmail: row.ownerEmail,
|
|
865
|
+
}),
|
|
866
|
+
});
|
|
867
|
+
}
|
|
560
868
|
return json({ status: "error", error: "A2A_SECRET not configured" }, 503);
|
|
561
869
|
}
|
|
562
870
|
try {
|
|
@@ -594,7 +902,7 @@ export async function handleMcpConnect(event, subpath, options = {}) {
|
|
|
594
902
|
}
|
|
595
903
|
return json({
|
|
596
904
|
status: "approved",
|
|
597
|
-
...mcpResultPayload(appUrl,
|
|
905
|
+
...mcpResultPayload(appUrl, options, { token }),
|
|
598
906
|
});
|
|
599
907
|
}
|
|
600
908
|
catch {
|