@agent-native/core 0.19.1 → 0.20.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.
Files changed (167) hide show
  1. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  2. package/dist/agent/engine/builder-engine.js +12 -1
  3. package/dist/agent/engine/builder-engine.js.map +1 -1
  4. package/dist/agent/production-agent.d.ts.map +1 -1
  5. package/dist/agent/production-agent.js +5 -0
  6. package/dist/agent/production-agent.js.map +1 -1
  7. package/dist/agent/thread-data-builder.d.ts +17 -0
  8. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  9. package/dist/agent/thread-data-builder.js +210 -0
  10. package/dist/agent/thread-data-builder.js.map +1 -1
  11. package/dist/cli/code-agent-executor.d.ts +2 -0
  12. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  13. package/dist/cli/code-agent-executor.js +39 -1
  14. package/dist/cli/code-agent-executor.js.map +1 -1
  15. package/dist/cli/code-agent-runs.d.ts +3 -0
  16. package/dist/cli/code-agent-runs.d.ts.map +1 -1
  17. package/dist/cli/code-agent-runs.js +3 -0
  18. package/dist/cli/code-agent-runs.js.map +1 -1
  19. package/dist/cli/connect.d.ts +3 -2
  20. package/dist/cli/connect.d.ts.map +1 -1
  21. package/dist/cli/connect.js +12 -8
  22. package/dist/cli/connect.js.map +1 -1
  23. package/dist/cli/mcp-config-writers.d.ts +3 -3
  24. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  25. package/dist/cli/mcp-config-writers.js +19 -8
  26. package/dist/cli/mcp-config-writers.js.map +1 -1
  27. package/dist/client/AgentPanel.d.ts.map +1 -1
  28. package/dist/client/AgentPanel.js +8 -3
  29. package/dist/client/AgentPanel.js.map +1 -1
  30. package/dist/client/AssistantChat.d.ts +58 -0
  31. package/dist/client/AssistantChat.d.ts.map +1 -1
  32. package/dist/client/AssistantChat.js +122 -50
  33. package/dist/client/AssistantChat.js.map +1 -1
  34. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  35. package/dist/client/agent-chat-adapter.js +172 -13
  36. package/dist/client/agent-chat-adapter.js.map +1 -1
  37. package/dist/client/agent-sidebar-state.d.ts +2 -0
  38. package/dist/client/agent-sidebar-state.d.ts.map +1 -1
  39. package/dist/client/agent-sidebar-state.js +32 -0
  40. package/dist/client/agent-sidebar-state.js.map +1 -1
  41. package/dist/client/code-agent-chat-adapter.d.ts +81 -0
  42. package/dist/client/code-agent-chat-adapter.d.ts.map +1 -0
  43. package/dist/client/code-agent-chat-adapter.js +297 -0
  44. package/dist/client/code-agent-chat-adapter.js.map +1 -0
  45. package/dist/client/composer/PromptComposer.d.ts +11 -0
  46. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  47. package/dist/client/composer/PromptComposer.js +7 -3
  48. package/dist/client/composer/PromptComposer.js.map +1 -1
  49. package/dist/client/composer/TiptapComposer.d.ts +12 -1
  50. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  51. package/dist/client/composer/TiptapComposer.js +16 -7
  52. package/dist/client/composer/TiptapComposer.js.map +1 -1
  53. package/dist/client/composer/index.d.ts +1 -0
  54. package/dist/client/composer/index.d.ts.map +1 -1
  55. package/dist/client/composer/index.js +1 -0
  56. package/dist/client/composer/index.js.map +1 -1
  57. package/dist/client/composer/prompt-attachments.d.ts +11 -0
  58. package/dist/client/composer/prompt-attachments.d.ts.map +1 -0
  59. package/dist/client/composer/prompt-attachments.js +45 -0
  60. package/dist/client/composer/prompt-attachments.js.map +1 -0
  61. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  62. package/dist/client/conversation/AgentConversation.js +124 -1
  63. package/dist/client/conversation/AgentConversation.js.map +1 -1
  64. package/dist/client/conversation/code-agent-transcript.d.ts +25 -0
  65. package/dist/client/conversation/code-agent-transcript.d.ts.map +1 -0
  66. package/dist/client/conversation/code-agent-transcript.js +200 -0
  67. package/dist/client/conversation/code-agent-transcript.js.map +1 -0
  68. package/dist/client/conversation/index.d.ts +2 -1
  69. package/dist/client/conversation/index.d.ts.map +1 -1
  70. package/dist/client/conversation/index.js +1 -0
  71. package/dist/client/conversation/index.js.map +1 -1
  72. package/dist/client/conversation/types.d.ts +8 -0
  73. package/dist/client/conversation/types.d.ts.map +1 -1
  74. package/dist/client/conversation/types.js.map +1 -1
  75. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -1
  76. package/dist/client/conversation/use-near-bottom-autoscroll.js +26 -9
  77. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -1
  78. package/dist/client/index.d.ts +5 -2
  79. package/dist/client/index.d.ts.map +1 -1
  80. package/dist/client/index.js +5 -2
  81. package/dist/client/index.js.map +1 -1
  82. package/dist/client/settings/useBuilderStatus.d.ts +2 -0
  83. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  84. package/dist/client/settings/useBuilderStatus.js +21 -3
  85. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  86. package/dist/client/settings/useBuilderStatus.spec.js +16 -2
  87. package/dist/client/settings/useBuilderStatus.spec.js.map +1 -1
  88. package/dist/client/sse-event-processor.d.ts.map +1 -1
  89. package/dist/client/sse-event-processor.js +3 -0
  90. package/dist/client/sse-event-processor.js.map +1 -1
  91. package/dist/client/use-chat-models.d.ts +6 -1
  92. package/dist/client/use-chat-models.d.ts.map +1 -1
  93. package/dist/client/use-chat-models.js +7 -3
  94. package/dist/client/use-chat-models.js.map +1 -1
  95. package/dist/client/use-chat-models.spec.d.ts +2 -0
  96. package/dist/client/use-chat-models.spec.d.ts.map +1 -0
  97. package/dist/client/use-chat-models.spec.js +39 -0
  98. package/dist/client/use-chat-models.spec.js.map +1 -0
  99. package/dist/code-agents/background-controller.d.ts.map +1 -1
  100. package/dist/code-agents/background-controller.js +16 -0
  101. package/dist/code-agents/background-controller.js.map +1 -1
  102. package/dist/code-agents/index.d.ts +2 -0
  103. package/dist/code-agents/index.d.ts.map +1 -1
  104. package/dist/code-agents/index.js +2 -0
  105. package/dist/code-agents/index.js.map +1 -1
  106. package/dist/code-agents/prompt-attachments.d.ts +11 -0
  107. package/dist/code-agents/prompt-attachments.d.ts.map +1 -0
  108. package/dist/code-agents/prompt-attachments.js +23 -0
  109. package/dist/code-agents/prompt-attachments.js.map +1 -0
  110. package/dist/code-agents/transcript-normalizer.js +14 -5
  111. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  112. package/dist/code-agents/transcript-order.d.ts +16 -0
  113. package/dist/code-agents/transcript-order.d.ts.map +1 -0
  114. package/dist/code-agents/transcript-order.js +47 -0
  115. package/dist/code-agents/transcript-order.js.map +1 -0
  116. package/dist/extensions/routes.d.ts.map +1 -1
  117. package/dist/extensions/routes.js +18 -14
  118. package/dist/extensions/routes.js.map +1 -1
  119. package/dist/mcp/build-server.d.ts +33 -1
  120. package/dist/mcp/build-server.d.ts.map +1 -1
  121. package/dist/mcp/build-server.js +39 -12
  122. package/dist/mcp/build-server.js.map +1 -1
  123. package/dist/mcp/builtin-tools.d.ts +3 -1
  124. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  125. package/dist/mcp/builtin-tools.js +40 -10
  126. package/dist/mcp/builtin-tools.js.map +1 -1
  127. package/dist/mcp/connect-route.d.ts.map +1 -1
  128. package/dist/mcp/connect-route.js +299 -97
  129. package/dist/mcp/connect-route.js.map +1 -1
  130. package/dist/mcp/server.d.ts.map +1 -1
  131. package/dist/mcp/server.js +17 -3
  132. package/dist/mcp/server.js.map +1 -1
  133. package/dist/secrets/substitution.d.ts +18 -0
  134. package/dist/secrets/substitution.d.ts.map +1 -1
  135. package/dist/secrets/substitution.js +74 -0
  136. package/dist/secrets/substitution.js.map +1 -1
  137. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  138. package/dist/server/agent-chat-plugin.js +33 -0
  139. package/dist/server/agent-chat-plugin.js.map +1 -1
  140. package/dist/server/auth.d.ts +8 -0
  141. package/dist/server/auth.d.ts.map +1 -1
  142. package/dist/server/auth.js +8 -1
  143. package/dist/server/auth.js.map +1 -1
  144. package/dist/server/deep-link.d.ts +0 -18
  145. package/dist/server/deep-link.d.ts.map +1 -1
  146. package/dist/server/deep-link.js +2 -1
  147. package/dist/server/deep-link.js.map +1 -1
  148. package/dist/server/open-route.d.ts.map +1 -1
  149. package/dist/server/open-route.js +23 -4
  150. package/dist/server/open-route.js.map +1 -1
  151. package/dist/shared/agent-sidebar-url.d.ts +6 -0
  152. package/dist/shared/agent-sidebar-url.d.ts.map +1 -0
  153. package/dist/shared/agent-sidebar-url.js +37 -0
  154. package/dist/shared/agent-sidebar-url.js.map +1 -0
  155. package/dist/shared/index.d.ts +1 -0
  156. package/dist/shared/index.d.ts.map +1 -1
  157. package/dist/shared/index.js +1 -0
  158. package/dist/shared/index.js.map +1 -1
  159. package/dist/styles/agent-conversation.css +403 -0
  160. package/dist/styles/agent-native.css +2 -0
  161. package/dist/vite/client.d.ts.map +1 -1
  162. package/dist/vite/client.js +1 -0
  163. package/dist/vite/client.js.map +1 -1
  164. package/docs/content/external-agents.md +27 -6
  165. package/docs/content/mcp-clients.md +2 -0
  166. package/docs/content/mcp-protocol.md +4 -2
  167. 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, token, options) {
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: { Authorization: `Bearer ${token}` },
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,80 +207,103 @@ 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: #08080a; --panel: #131316; --panel-2: #0d0d10;
180
- --border: rgba(255,255,255,0.08); --border-strong: rgba(255,255,255,0.14);
181
- --text: #fafafa; --muted: #a1a1aa; --subtle: #71717a;
182
- --accent: #fafafa; --accent-fg: #09090b;
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;
214
+ --accent: #f4f4f5; --accent-fg: #09090b;
183
215
  --ring: rgba(250,250,250,0.55);
184
216
  --error: #fca5a5; --error-bg: rgba(127,29,29,0.18);
185
- --ok: #86efac; --ok-bg: rgba(20,83,45,0.2);
217
+ --ok: #86efac; --ok-bg: rgba(20,83,45,0.12); --ok-border: rgba(134,239,172,0.18);
186
218
  }
187
219
  html, body { -webkit-font-smoothing: antialiased; }
188
220
  body {
189
221
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
190
- background:
191
- radial-gradient(900px 540px at 50% -14%, rgba(255,255,255,0.05), transparent 60%),
192
- linear-gradient(180deg, #121215 0%, var(--bg) 56%);
222
+ background: linear-gradient(180deg, #101013 0%, var(--bg) 58%);
193
223
  color: var(--text); display: flex; align-items: center;
194
224
  justify-content: center; min-height: 100vh; padding: 1.5rem 1rem;
195
225
  }
196
226
  .card {
197
- width: 100%; max-width: 420px;
227
+ width: 100%; max-width: 440px;
198
228
  background: var(--panel); border: 1px solid var(--border);
199
- border-radius: 18px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,
229
+ border-radius: 8px; box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset,
200
230
  0 30px 90px rgba(0,0,0,0.5);
201
- padding: 2.25rem 2rem 1.75rem;
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;
202
247
  }
203
- /* App-to-app glyph */
204
- .glyph {
248
+ .hero { padding: 0 0.75rem; text-align: center; }
249
+ .flow {
205
250
  display: flex; align-items: center; justify-content: center;
206
- gap: 0; margin: 0 auto 1.5rem; width: fit-content;
251
+ gap: 0; margin: 0 auto 1.1rem; width: fit-content;
207
252
  }
208
- .glyph .tile {
209
- width: 52px; height: 52px; border-radius: 14px;
253
+ .flow .tile {
254
+ width: 42px; height: 42px; border-radius: 8px;
210
255
  display: flex; align-items: center; justify-content: center;
211
256
  background: var(--panel-2); border: 1px solid var(--border-strong);
212
257
  color: var(--text); flex-shrink: 0;
213
258
  }
214
- .glyph .tile svg { display: block; }
215
- .glyph .conn {
216
- width: 38px; height: 2px; flex-shrink: 0;
217
- background-image: radial-gradient(circle, var(--subtle) 1px, transparent 1.4px);
218
- background-size: 7px 2px; background-repeat: repeat-x;
259
+ .flow-mark { width: 26px; height: auto; display: block; }
260
+ .flow .agent-symbol {
261
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
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);
219
267
  background-position: center;
220
268
  }
221
269
  .eyebrow {
222
270
  text-align: center; font-size: 0.72rem; font-weight: 600;
223
271
  letter-spacing: 0.08em; text-transform: uppercase;
224
- color: var(--subtle); margin-bottom: 0.5rem;
272
+ color: var(--subtle); margin-bottom: 0.55rem;
225
273
  }
226
274
  h1 {
227
- text-align: center; font-size: 1.4rem; font-weight: 680;
275
+ text-align: center; font-size: 1.45rem; font-weight: 680;
228
276
  line-height: 1.25; margin-bottom: 0.55rem;
229
277
  letter-spacing: -0.01em;
230
278
  }
231
279
  .sub {
232
280
  text-align: center; color: var(--muted); font-size: 0.9rem;
233
- line-height: 1.5; margin: 0 auto 0.85rem; max-width: 34ch;
281
+ line-height: 1.5; margin: 0 auto 0.9rem; max-width: 36ch;
234
282
  }
235
283
  .identity {
236
- text-align: center; color: var(--subtle); font-size: 0.78rem;
237
- margin-bottom: 1.5rem;
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;
238
287
  }
239
288
  .identity strong { color: var(--muted); font-weight: 600; }
240
- .code-callout {
241
- border: 1px solid var(--border-strong); border-radius: 12px;
242
- padding: 0.85rem 1rem; margin-bottom: 1.25rem;
243
- background: var(--panel-2); text-align: center;
244
- }
245
- .code-callout .label { font-size: 0.68rem; color: var(--subtle);
246
- text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 0.4rem; }
247
- .code-callout .value { font-size: 1.6rem; font-weight: 700;
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;
248
301
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
249
- letter-spacing: 0.14em; color: var(--text); }
302
+ letter-spacing: 0.08em; color: var(--text);
303
+ }
250
304
  button {
251
305
  cursor: pointer; font: inherit; font-weight: 600; border: none;
252
- border-radius: 10px; padding: 0.8rem 1.1rem;
306
+ border-radius: 8px; padding: 0.78rem 1rem;
253
307
  }
254
308
  button:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
255
309
  .primary {
@@ -265,7 +319,7 @@ function renderConnectPage(params) {
265
319
  }
266
320
  .ghost:hover:not(:disabled) { color: var(--text); border-color: var(--subtle); }
267
321
  pre {
268
- background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px;
322
+ background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px;
269
323
  padding: 0.9rem; font-size: 0.78rem; line-height: 1.5; overflow-x: auto;
270
324
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
271
325
  color: #d4d4d8; margin: 0.5rem 0 1rem;
@@ -283,9 +337,11 @@ function renderConnectPage(params) {
283
337
  .advanced > summary:focus-visible { outline: 2px solid var(--ring);
284
338
  outline-offset: 2px; border-radius: 6px; }
285
339
  .advanced > summary .chev {
286
- width: 14px; height: 14px; transition: transform 0.15s ease;
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;
287
343
  }
288
- .advanced[open] > summary .chev { transform: rotate(180deg); }
344
+ .advanced[open] > summary .chev { transform: rotate(225deg); margin-top: 2px; }
289
345
  .advanced-body {
290
346
  padding: 0.85rem 0.1rem 0.25rem;
291
347
  }
@@ -302,56 +358,108 @@ function renderConnectPage(params) {
302
358
  outline: none; border-color: var(--ring);
303
359
  box-shadow: 0 0 0 3px rgba(250,250,250,0.12);
304
360
  }
305
- .inline { display: flex; gap: 0.5rem; }
306
- .inline input { flex: 1; }
307
- .tokens { margin-top: 1.5rem; border-top: 1px solid var(--border);
308
- padding-top: 1.25rem; }
309
- .tokens h2 { font-size: 0.78rem; font-weight: 600; color: var(--muted);
310
- text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 0.6rem; }
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; }
311
387
  .tok { display: flex; align-items: center; justify-content: space-between;
312
388
  gap: 0.75rem; padding: 0.6rem 0; border-bottom: 1px solid var(--border);
313
389
  font-size: 0.83rem; }
314
390
  .tok:last-child { border-bottom: none; }
315
391
  .tok .meta { color: var(--subtle); font-size: 0.74rem; margin-top: 0.1rem; }
316
392
  .tok.revoked { opacity: 0.45; }
317
- .msg { font-size: 0.83rem; padding: 0.6rem 0.8rem; border-radius: 8px;
318
- margin-bottom: 1rem; display: none; }
319
- .msg.err { display: block; color: var(--error); background: var(--error-bg); }
320
- .msg.ok { display: block; color: var(--ok); background: var(--ok-bg); }
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
+ }
321
425
  .hidden { display: none !important; }
322
426
  </style>
323
427
  </head>
324
428
  <body>
325
429
  <div class="card">
326
- <!-- "Connect an external agent" — kept as an accessible label / consent eyebrow -->
327
- <div class="glyph" role="img" aria-label="Connect an external agent to ${safeApp}">
328
- <span class="tile" aria-hidden="true">
329
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
330
- <rect x="3" y="3" width="8" height="8" rx="2.2" fill="currentColor"/>
331
- <rect x="13" y="3" width="8" height="8" rx="2.2" fill="currentColor" opacity="0.55"/>
332
- <rect x="3" y="13" width="8" height="8" rx="2.2" fill="currentColor" opacity="0.55"/>
333
- <rect x="13" y="13" width="8" height="8" rx="2.2" fill="currentColor"/>
334
- </svg>
335
- </span>
336
- <span class="conn" aria-hidden="true"></span>
337
- <span class="tile" aria-hidden="true">
338
- <svg width="22" height="22" viewBox="0 0 24 24" fill="none"
339
- stroke="currentColor" stroke-width="2" stroke-linecap="round"
340
- stroke-linejoin="round">
341
- <path d="M9 8 L5 12 L9 16"/>
342
- <path d="M15 8 L19 12 L15 16"/>
343
- </svg>
344
- </span>
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>
345
436
  </div>
346
437
 
347
- <div class="eyebrow">Connect an external agent</div>
348
- <h1>Authorize ${safeApp}?</h1>
349
- <p class="sub">Mint a personal token so a coding agent (Claude Code, Codex, Cowork) can act as you on ${safeApp}.</p>
350
- <p class="identity">Signed in as <strong>${safeEmail}</strong> &middot; ${safeOrigin}</p>
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">&lt;/&gt;</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">&middot;</span>
456
+ <span class="origin">${safeOrigin}</span>
457
+ </p>
458
+ </div>
351
459
 
352
- <div id="codeCallout" class="code-callout ${safeUserCode ? "" : "hidden"}">
353
- <div class="label">Authorizing device code</div>
354
- <div class="value" id="userCodeValue">${safeUserCode}</div>
460
+ <div id="codeCallout" class="device-strip ${safeUserCode ? "" : "hidden"}">
461
+ <span class="label">Device code</span>
462
+ <span class="value" id="userCodeValue">${safeUserCode}</span>
355
463
  </div>
356
464
 
357
465
  <div id="msg" class="msg"></div>
@@ -361,9 +469,7 @@ function renderConnectPage(params) {
361
469
  <details class="advanced">
362
470
  <summary>
363
471
  Advanced options
364
- <svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor"
365
- stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
366
- aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
472
+ <span class="chev" aria-hidden="true"></span>
367
473
  </summary>
368
474
  <div class="advanced-body">
369
475
  <div class="field">
@@ -378,23 +484,38 @@ function renderConnectPage(params) {
378
484
  </details>
379
485
  </div>
380
486
 
381
- <div id="result" class="hidden">
382
- <p class="sub" id="resultMsg">Token created. Paste this into your agent's MCP config:</p>
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>
383
491
  <pre id="mcpJson"></pre>
384
- <p class="sub">Or from a terminal:</p>
385
- <pre id="cliLine"></pre>
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>
386
501
  </div>
387
502
 
388
- <div class="tokens">
389
- <h2>Your connections</h2>
390
- <div id="tokenList"><div class="meta">Loading…</div></div>
391
- </div>
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>
392
511
  </div>
393
512
  <script>
394
513
  (function () {
395
514
  var BASE = ${JSON.stringify(joinAppPath(connectBasePath, "/_agent-native/mcp/connect"))};
396
515
  var USER_CODE = ${JSON.stringify(safeUserCode || null)};
397
516
  var msgEl = document.getElementById("msg");
517
+ var connectionsEl = document.getElementById("connections");
518
+ var connectionsStateEl = document.getElementById("connectionsState");
398
519
  function showMsg(text, kind) {
399
520
  msgEl.textContent = text;
400
521
  msgEl.className = "msg " + (kind || "err");
@@ -427,10 +548,23 @@ function renderConnectPage(params) {
427
548
  var listEl = document.getElementById("tokenList");
428
549
  try {
429
550
  var res = await fetch(BASE + "/tokens", { credentials: "same-origin" });
430
- if (!res.ok) { listEl.innerHTML = '<div class="meta">Could not load.</div>'; return; }
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
+ }
431
557
  var data = await res.json();
432
558
  var tokens = (data && data.tokens) || [];
433
- if (!tokens.length) { listEl.innerHTML = '<div class="meta">No connections yet.</div>'; return; }
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;
434
568
  listEl.innerHTML = "";
435
569
  tokens.forEach(function (t) {
436
570
  var div = document.createElement("div");
@@ -460,7 +594,9 @@ function renderConnectPage(params) {
460
594
  listEl.appendChild(div);
461
595
  });
462
596
  } catch (e) {
463
- listEl.innerHTML = '<div class="meta">Could not load.</div>';
597
+ connectionsStateEl.textContent = "Unavailable";
598
+ connectionsEl.open = true;
599
+ listEl.innerHTML = '<div class="empty-state">Could not load connections.</div>';
464
600
  }
465
601
  }
466
602
 
@@ -478,9 +614,57 @@ function renderConnectPage(params) {
478
614
  showMsg((a.data && a.data.error) || "Could not authorize this device code.");
479
615
  return;
480
616
  }
481
- showMsg("Device authorized. You can return to your terminal — it will connect automatically.", "ok");
617
+ showMsg("Device authorized finishing connection… you can return to your terminal.", "ok");
482
618
  btn.classList.add("hidden");
483
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;
484
668
  } else {
485
669
  var m = await postJson("/token", { label: label, ttlDays: ttlDays });
486
670
  if (!m.ok) {
@@ -565,6 +749,9 @@ export async function handleMcpConnect(event, subpath, options = {}) {
565
749
  if (!session?.email)
566
750
  return json({ error: "Unauthorized" }, 401);
567
751
  if (!process.env.A2A_SECRET) {
752
+ if (canUseDevOpenConnect(event)) {
753
+ return json(mcpResultPayload(appUrl, options, { ownerEmail: session.email }));
754
+ }
568
755
  return json({
569
756
  error: "This deployment has no A2A_SECRET configured, so connect tokens cannot be minted.",
570
757
  }, 503);
@@ -581,7 +768,7 @@ export async function handleMcpConnect(event, subpath, options = {}) {
581
768
  label,
582
769
  ttlDays,
583
770
  });
584
- return json(mcpResultPayload(appUrl, token, options));
771
+ return json(mcpResultPayload(appUrl, options, { token }));
585
772
  }
586
773
  catch {
587
774
  return json({ error: "Failed to mint token." }, 500);
@@ -663,6 +850,21 @@ export async function handleMcpConnect(event, subpath, options = {}) {
663
850
  }
664
851
  // status === "approved" && ownerEmail bound → mint exactly once.
665
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
+ }
666
868
  return json({ status: "error", error: "A2A_SECRET not configured" }, 503);
667
869
  }
668
870
  try {
@@ -700,7 +902,7 @@ export async function handleMcpConnect(event, subpath, options = {}) {
700
902
  }
701
903
  return json({
702
904
  status: "approved",
703
- ...mcpResultPayload(appUrl, token, options),
905
+ ...mcpResultPayload(appUrl, options, { token }),
704
906
  });
705
907
  }
706
908
  catch {