@agent-native/core 0.157.9 → 0.157.10

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 (107) hide show
  1. package/corpus/templates/analytics/scripts/first-party-analytics-postgres-retention-dry-run.sql +5 -2
  2. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +3 -1
  3. package/corpus/templates/analytics/server/lib/first-party-analytics-purge.ts +18 -6
  4. package/corpus/templates/analytics/server/plugins/db.ts +139 -0
  5. package/corpus/templates/slides/AGENTS.md +14 -10
  6. package/corpus/templates/slides/actions/export-pptx.ts +1094 -94
  7. package/corpus/templates/slides/actions/import-file.ts +16 -1
  8. package/corpus/templates/slides/actions/import-pptx.ts +61 -43
  9. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +222 -29
  10. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +71 -0
  11. package/corpus/templates/slides/app/global.css +19 -1
  12. package/corpus/templates/slides/app/lib/export-google-slides-client.ts +44 -5
  13. package/corpus/templates/slides/app/lib/export-pdf-client.ts +13 -6
  14. package/corpus/templates/slides/app/lib/export-pptx-client.ts +556 -25
  15. package/corpus/templates/slides/app/pages/DeckEditor.tsx +18 -1
  16. package/corpus/templates/slides/server/handlers/import/html-converter.ts +1176 -26
  17. package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +13 -0
  18. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +24 -0
  19. package/corpus/templates/slides/shared/aspect-ratios.ts +13 -1
  20. package/dist/agent/harness/migrations.js +24 -3
  21. package/dist/agent/production-agent.js +11 -0
  22. package/dist/agent/run-loop-with-resume.js +21 -4
  23. package/dist/automations/service.d.ts +6 -0
  24. package/dist/automations/service.js +35 -0
  25. package/dist/cli/code-agent-connector.d.ts +4 -1
  26. package/dist/cli/code-agent-connector.js +154 -23
  27. package/dist/cli/code-agent-executor.js +255 -13
  28. package/dist/cli/code-agent-runs.d.ts +1 -0
  29. package/dist/cli/code-agent-runs.js +2 -1
  30. package/dist/cli/portal-workspace.d.ts +49 -0
  31. package/dist/cli/portal-workspace.js +320 -0
  32. package/dist/client/AssistantChat.d.ts +1 -1
  33. package/dist/client/AssistantChat.js +39 -61
  34. package/dist/client/ConnectBuilderCard.js +33 -5
  35. package/dist/client/agent-chat-adapter.js +4 -0
  36. package/dist/client/analytics-platform.d.ts +2 -0
  37. package/dist/client/analytics-platform.js +12 -0
  38. package/dist/client/analytics.d.ts +3 -0
  39. package/dist/client/analytics.js +10 -1
  40. package/dist/client/chat/index.d.ts +1 -0
  41. package/dist/client/chat/index.js +1 -0
  42. package/dist/client/chat/run-recovery.d.ts +2 -1
  43. package/dist/client/chat/run-recovery.js +2 -2
  44. package/dist/client/chat-first/apps-rail.d.ts +1 -1
  45. package/dist/client/chat-first/apps-rail.js +11 -11
  46. package/dist/client/chat-first/surface-tabs.d.ts +1 -1
  47. package/dist/client/chat-first/surface-tabs.js +38 -36
  48. package/dist/client/chat-first/types.d.ts +5 -0
  49. package/dist/client/chat-first.d.ts +1 -1
  50. package/dist/client/chat-first.js +4 -4
  51. package/dist/client/desktop-local-code-change.d.ts +10 -0
  52. package/dist/client/desktop-local-code-change.js +16 -0
  53. package/dist/client/index.d.ts +1 -0
  54. package/dist/client/index.js +1 -0
  55. package/dist/client/observability/ThumbsFeedback.js +21 -7
  56. package/dist/client/terminal/AgentTerminal.d.ts +9 -1
  57. package/dist/client/terminal/AgentTerminal.js +38 -3
  58. package/dist/client/terminal/index.d.ts +1 -1
  59. package/dist/client/terminal/index.js +1 -1
  60. package/dist/client/track.js +9 -1
  61. package/dist/client/use-action.js +3 -0
  62. package/dist/collab/struct-routes.d.ts +1 -1
  63. package/dist/deploy/build.js +1 -1
  64. package/dist/ingestion/pptx.d.ts +78 -0
  65. package/dist/ingestion/pptx.js +991 -107
  66. package/dist/integrations/index.d.ts +3 -3
  67. package/dist/integrations/index.js +2 -2
  68. package/dist/integrations/plugin.js +95 -6
  69. package/dist/integrations/remote-commands-store.d.ts +5 -0
  70. package/dist/integrations/remote-commands-store.js +63 -22
  71. package/dist/integrations/remote-devices-store.d.ts +2 -1
  72. package/dist/integrations/remote-devices-store.js +46 -0
  73. package/dist/integrations/remote-types.d.ts +20 -0
  74. package/dist/jobs/actions/list-recurring-jobs.d.ts +3 -0
  75. package/dist/jobs/actions/list-recurring-jobs.js +5 -1
  76. package/dist/jobs/frontmatter.d.ts +16 -0
  77. package/dist/jobs/frontmatter.js +53 -0
  78. package/dist/jobs/remote-execution.d.ts +42 -0
  79. package/dist/jobs/remote-execution.js +287 -0
  80. package/dist/jobs/scheduler.js +102 -3
  81. package/dist/jobs/tools.js +50 -3
  82. package/dist/notifications/routes.d.ts +4 -4
  83. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  84. package/dist/provider-api/actions/provider-api.d.ts +14 -14
  85. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  86. package/dist/resources/handlers.d.ts +1 -1
  87. package/dist/server/action-routes.js +4 -2
  88. package/dist/server/agent-chat-plugin.js +6 -0
  89. package/dist/server/agent-run-context.d.ts +1 -0
  90. package/dist/server/agent-run-context.js +9 -0
  91. package/dist/server/core-routes-plugin.js +8 -1
  92. package/dist/server/request-context.d.ts +2 -0
  93. package/dist/shared/analytics-platform.d.ts +6 -0
  94. package/dist/shared/analytics-platform.js +14 -0
  95. package/dist/shared/mcp-embed-headers.d.ts +1 -1
  96. package/dist/shared/mcp-embed-headers.js +1 -1
  97. package/dist/styles/agent-native.css +83 -103
  98. package/dist/terminal/cli-registry.js +5 -0
  99. package/dist/tracking/registry.js +9 -1
  100. package/dist/triggers/actions/list-automations.d.ts +3 -0
  101. package/dist/triggers/actions/list-automations.js +3 -0
  102. package/dist/triggers/actions/manage-automation.d.ts +9 -0
  103. package/dist/triggers/actions/manage-automation.js +14 -2
  104. package/dist/triggers/actions.js +82 -7
  105. package/docs/content/template-content-developers.mdx +48 -0
  106. package/docs/content/toolkit-ui.mdx +57 -0
  107. package/package.json +2 -2
@@ -0,0 +1,320 @@
1
+ import { execFile as execFileCallback } from "node:child_process";
2
+ import crypto from "node:crypto";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { promisify } from "node:util";
7
+ import { codeAgentStoreRoot } from "./code-agent-runs.js";
8
+ const execFile = promisify(execFileCallback);
9
+ const MAX_GIT_OUTPUT_BYTES = 1_000_000;
10
+ const PORTAL_ENV_FILES = [".env", ".env.local"];
11
+ const PORTAL_PROTECTED_ENV_KEYS = new Set([
12
+ "AGENT_NATIVE_CODE_AGENTS_HOME",
13
+ "AGENT_NATIVE_CODE_AGENT_PERMISSION_MODE",
14
+ "AGENT_NATIVE_COMPUTER_BRIDGE_TOKEN",
15
+ "AGENT_NATIVE_COMPUTER_BRIDGE_URL",
16
+ "AGENT_NATIVE_REMOTE_DEVICE_PATH",
17
+ "ELECTRON_RUN_AS_NODE",
18
+ "HOME",
19
+ "NODE_OPTIONS",
20
+ "PATH",
21
+ "PWD",
22
+ "SHELL",
23
+ ]);
24
+ const runPortalGit = async (args, cwd, env) => {
25
+ try {
26
+ const result = await execFile("git", args, {
27
+ cwd,
28
+ encoding: "utf8",
29
+ env: {
30
+ ...process.env,
31
+ GIT_TERMINAL_PROMPT: "0",
32
+ ...env,
33
+ },
34
+ maxBuffer: MAX_GIT_OUTPUT_BYTES,
35
+ });
36
+ return {
37
+ status: 0,
38
+ stdout: String(result.stdout),
39
+ stderr: String(result.stderr),
40
+ };
41
+ }
42
+ catch (error) {
43
+ const candidate = error;
44
+ return {
45
+ status: typeof candidate.code === "number" ? candidate.code : 1,
46
+ stdout: candidate.stdout ? String(candidate.stdout) : undefined,
47
+ stderr: candidate.stderr ? String(candidate.stderr) : undefined,
48
+ error: error instanceof Error ? error : new Error(String(error)),
49
+ };
50
+ }
51
+ };
52
+ export async function createPortalHandoff(input) {
53
+ const executeGit = input.runGit ?? runPortalGit;
54
+ const sourceCandidate = path.resolve(input.sourcePath);
55
+ const remoteName = input.remoteName ?? "origin";
56
+ assertSafeRemoteName(remoteName);
57
+ const handoffId = input.handoffId ?? crypto.randomUUID();
58
+ assertSafeHandoffId(handoffId);
59
+ const repositoryPath = await requiredGitOutput(executeGit, ["rev-parse", "--show-toplevel"], sourceCandidate, "Selected folder is not a Git repository. Choose a Git folder to use Portal.");
60
+ const head = await requiredGitOutput(executeGit, ["rev-parse", "--verify", "HEAD"], repositoryPath, "Portal could not determine the current Git commit.");
61
+ const status = await executeGit(["status", "--porcelain", "--untracked-files=all"], repositoryPath);
62
+ if (status.status !== 0) {
63
+ throw gitFailure(status, "Portal could not inspect the local Git working tree.");
64
+ }
65
+ const remoteUrl = await requiredGitOutput(executeGit, ["remote", "get-url", remoteName], repositoryPath, `Portal could not find the ${remoteName} Git remote.`);
66
+ if (!remoteUrl) {
67
+ throw new Error(`Portal could not find the ${remoteName} Git remote.`);
68
+ }
69
+ const branchResult = await executeGit(["branch", "--show-current"], repositoryPath);
70
+ const sourceBranch = branchResult.status === 0
71
+ ? branchResult.stdout?.trim() || undefined
72
+ : undefined;
73
+ const sourceDirty = Boolean(status.stdout?.trim());
74
+ let commit = head;
75
+ let createdCommit = false;
76
+ if (sourceDirty) {
77
+ const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), "agent-native-portal-index-"));
78
+ const indexPath = path.join(tempRoot, "index");
79
+ try {
80
+ const indexEnv = { GIT_INDEX_FILE: indexPath };
81
+ const added = await executeGit(["add", "--all", "--", "."], repositoryPath, indexEnv);
82
+ if (added.status !== 0) {
83
+ throw gitFailure(added, "Portal could not snapshot local code.");
84
+ }
85
+ const tree = await requiredGitOutput(executeGit, ["write-tree"], repositoryPath, "Portal could not write the local code snapshot.", indexEnv);
86
+ const authorName = (await optionalGitOutput(executeGit, ["config", "--get", "user.name"], repositoryPath)) ?? "Agent Native Portal";
87
+ const authorEmail = (await optionalGitOutput(executeGit, ["config", "--get", "user.email"], repositoryPath)) ?? "portal@agent-native.local";
88
+ const committed = await requiredGitOutput(executeGit, ["commit-tree", tree, "-p", head, "-m", `Portal handoff ${handoffId}`], repositoryPath, "Portal could not create the local code snapshot.", {
89
+ ...indexEnv,
90
+ GIT_AUTHOR_NAME: authorName,
91
+ GIT_AUTHOR_EMAIL: authorEmail,
92
+ GIT_COMMITTER_NAME: authorName,
93
+ GIT_COMMITTER_EMAIL: authorEmail,
94
+ });
95
+ commit = committed;
96
+ createdCommit = true;
97
+ }
98
+ finally {
99
+ await fs.promises.rm(tempRoot, { recursive: true, force: true });
100
+ }
101
+ }
102
+ const branch = portalBranchName(input.now ?? new Date(), handoffId);
103
+ const pushed = await executeGit(["push", "--no-verify", remoteName, `${commit}:refs/heads/${branch}`], repositoryPath);
104
+ if (pushed.status !== 0) {
105
+ throw gitFailure(pushed, "Portal could not push the code snapshot to the Git remote.");
106
+ }
107
+ return {
108
+ schemaVersion: 1,
109
+ handoffId,
110
+ branch,
111
+ remoteRef: `refs/heads/${branch}`,
112
+ commit,
113
+ sourceBranch,
114
+ sourceDirty,
115
+ createdCommit,
116
+ repositoryName: path.basename(repositoryPath),
117
+ remoteName,
118
+ envPolicy: "load-local",
119
+ };
120
+ }
121
+ export async function preparePortalWorkspace(input) {
122
+ const executeGit = input.runGit ?? runPortalGit;
123
+ const handoff = parsePortalHandoff(input.handoff);
124
+ const configuredWorkspace = input.workspacePath?.trim();
125
+ if (!configuredWorkspace) {
126
+ throw new Error("Portal needs a configured workspacePath on the paired computer.");
127
+ }
128
+ const repositoryPath = await requiredGitOutput(executeGit, ["rev-parse", "--show-toplevel"], path.resolve(configuredWorkspace), "Portal could not find a Git repository on the paired computer.");
129
+ const trackingRef = `refs/remotes/${handoff.remoteName}/${handoff.branch}`;
130
+ const fetched = await executeGit([
131
+ "fetch",
132
+ "--no-tags",
133
+ "--no-prune",
134
+ handoff.remoteName,
135
+ `${handoff.remoteRef}:${trackingRef}`,
136
+ ], repositoryPath);
137
+ if (fetched.status !== 0) {
138
+ throw gitFailure(fetched, "Portal could not fetch the code snapshot on the paired computer.");
139
+ }
140
+ const verified = await executeGit(["cat-file", "-e", `${handoff.commit}^{commit}`], repositoryPath);
141
+ if (verified.status !== 0) {
142
+ throw gitFailure(verified, "Portal fetched the branch but could not verify its commit.");
143
+ }
144
+ const worktreeRoot = path.resolve(input.worktreeRoot ?? path.join(codeAgentStoreRoot(), "portals"));
145
+ await fs.promises.mkdir(worktreeRoot, { recursive: true });
146
+ const baseName = `${handoff.handoffId}-${handoff.commit.slice(0, 8)}`;
147
+ let workspacePath = path.join(worktreeRoot, baseName);
148
+ let reused = false;
149
+ if (fs.existsSync(workspacePath)) {
150
+ const existingHead = await executeGit(["rev-parse", "HEAD"], workspacePath);
151
+ const existingStatus = await executeGit(["status", "--porcelain", "--untracked-files=all"], workspacePath);
152
+ if (existingHead.status === 0 &&
153
+ existingHead.stdout?.trim() === handoff.commit &&
154
+ existingStatus.status === 0 &&
155
+ !existingStatus.stdout?.trim()) {
156
+ reused = true;
157
+ }
158
+ else {
159
+ workspacePath = path.join(worktreeRoot, `${baseName}-${crypto.randomUUID().slice(0, 8)}`);
160
+ }
161
+ }
162
+ if (!reused) {
163
+ const created = await executeGit(["worktree", "add", "--detach", workspacePath, handoff.commit], repositoryPath);
164
+ if (created.status !== 0) {
165
+ throw gitFailure(created, "Portal could not create the isolated workspace on the paired computer.");
166
+ }
167
+ }
168
+ return {
169
+ handoff,
170
+ repositoryPath,
171
+ workspacePath,
172
+ environmentRoot: repositoryPath,
173
+ environmentFiles: listPortalEnvironmentFiles(repositoryPath),
174
+ reused,
175
+ };
176
+ }
177
+ export function parsePortalHandoff(value) {
178
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
179
+ throw new Error("Portal handoff metadata is missing.");
180
+ }
181
+ const record = value;
182
+ const handoffId = stringField(record.handoffId);
183
+ const branch = stringField(record.branch);
184
+ const remoteRef = stringField(record.remoteRef);
185
+ const commit = stringField(record.commit);
186
+ const repositoryName = stringField(record.repositoryName);
187
+ const remoteName = stringField(record.remoteName);
188
+ if (!handoffId || !branch || !remoteRef || !commit || !repositoryName) {
189
+ throw new Error("Portal handoff metadata is incomplete.");
190
+ }
191
+ assertSafeHandoffId(handoffId);
192
+ assertSafeBranch(branch);
193
+ assertSafeRemoteName(remoteName || "");
194
+ if (remoteRef !== `refs/heads/${branch}`) {
195
+ throw new Error("Portal handoff metadata has an invalid remote ref.");
196
+ }
197
+ if (!/^[a-f0-9]{7,64}$/i.test(commit)) {
198
+ throw new Error("Portal handoff metadata has an invalid commit.");
199
+ }
200
+ if (record.schemaVersion !== 1 || record.envPolicy !== "load-local") {
201
+ throw new Error("Portal handoff metadata has an unsupported version.");
202
+ }
203
+ if (typeof record.sourceDirty !== "boolean" ||
204
+ typeof record.createdCommit !== "boolean") {
205
+ throw new Error("Portal handoff metadata has invalid snapshot state.");
206
+ }
207
+ const sourceBranch = stringField(record.sourceBranch);
208
+ return {
209
+ schemaVersion: 1,
210
+ handoffId,
211
+ branch,
212
+ remoteRef,
213
+ commit,
214
+ sourceBranch,
215
+ sourceDirty: record.sourceDirty,
216
+ createdCommit: record.createdCommit,
217
+ repositoryName,
218
+ remoteName: remoteName || "origin",
219
+ envPolicy: "load-local",
220
+ };
221
+ }
222
+ export function listPortalEnvironmentFiles(root, mode = process.env.NODE_ENV || "development") {
223
+ const normalizedMode = mode.trim().replace(/[^A-Za-z0-9_-]/g, "");
224
+ const names = [
225
+ ...PORTAL_ENV_FILES,
226
+ ...(normalizedMode ? [`.env.${normalizedMode}`] : []),
227
+ ...(normalizedMode ? [`.env.${normalizedMode}.local`] : []),
228
+ ];
229
+ return [...new Set(names)].filter((name) => {
230
+ try {
231
+ return fs.statSync(path.join(root, name)).isFile();
232
+ // coercion-ok: A missing local env file is an intentional absent value.
233
+ }
234
+ catch {
235
+ return false;
236
+ }
237
+ });
238
+ }
239
+ export function loadPortalEnvironment(root, mode = process.env.NODE_ENV || "development") {
240
+ const resolvedRoot = path.resolve(root);
241
+ const files = listPortalEnvironmentFiles(resolvedRoot, mode);
242
+ const values = {};
243
+ for (const name of files) {
244
+ const filePath = path.join(resolvedRoot, name);
245
+ let content;
246
+ try {
247
+ content = fs.readFileSync(filePath, "utf8");
248
+ }
249
+ catch (error) {
250
+ throw new Error(`Portal could not read ${name} on the paired computer: ${error instanceof Error ? error.message : String(error)}`);
251
+ }
252
+ for (const line of content.split(/\r?\n/)) {
253
+ const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
254
+ if (!match || PORTAL_PROTECTED_ENV_KEYS.has(match[1]))
255
+ continue;
256
+ values[match[1]] = parsePortalEnvValue(match[2]);
257
+ }
258
+ }
259
+ return { values, files, root: resolvedRoot };
260
+ }
261
+ function parsePortalEnvValue(value) {
262
+ const trimmed = value.trim();
263
+ if (trimmed.length >= 2 &&
264
+ ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
265
+ (trimmed.startsWith("'") && trimmed.endsWith("'")))) {
266
+ return trimmed.slice(1, -1);
267
+ }
268
+ const comment = trimmed.indexOf(" #");
269
+ return comment >= 0 ? trimmed.slice(0, comment).trimEnd() : trimmed;
270
+ }
271
+ function portalBranchName(now, handoffId) {
272
+ const timestamp = now.toISOString().replace(/\D/g, "").slice(0, 14);
273
+ const suffix = handoffId.slice(0, 8).replace(/[-.]+$/g, "") || "handoff";
274
+ return `portal/${timestamp}-${suffix}`;
275
+ }
276
+ function stringField(value) {
277
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
278
+ }
279
+ function assertSafeHandoffId(value) {
280
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(value)) {
281
+ throw new Error("Portal handoff id is invalid.");
282
+ }
283
+ }
284
+ function assertSafeBranch(value) {
285
+ if (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,180}$/.test(value) ||
286
+ value.includes("..") ||
287
+ value.includes("//") ||
288
+ value.endsWith("/") ||
289
+ value.endsWith(".")) {
290
+ throw new Error("Portal branch is invalid.");
291
+ }
292
+ }
293
+ function assertSafeRemoteName(value) {
294
+ if (!/^[A-Za-z0-9._-]{1,64}$/.test(value)) {
295
+ throw new Error("Portal Git remote name is invalid.");
296
+ }
297
+ }
298
+ async function requiredGitOutput(executeGit, args, cwd, fallback, env) {
299
+ const result = await executeGit(args, cwd, env);
300
+ if (result.status !== 0 || !result.stdout?.trim()) {
301
+ throw gitFailure(result, fallback);
302
+ }
303
+ return result.stdout.trim();
304
+ }
305
+ async function optionalGitOutput(executeGit, args, cwd) {
306
+ const result = await executeGit(args, cwd);
307
+ return result.status === 0 ? result.stdout?.trim() || undefined : undefined;
308
+ }
309
+ function gitFailure(result, fallback) {
310
+ const detail = sanitizeGitError(result.stderr?.trim() || result.error?.message);
311
+ return new Error(detail ? `${fallback} ${detail}` : fallback);
312
+ }
313
+ function sanitizeGitError(value) {
314
+ if (!value)
315
+ return undefined;
316
+ return value
317
+ .replace(/https?:\/\/[^\s/@]+(?::[^\s/@]*)?@/gi, "https://REDACTED@")
318
+ .replace(/\s+/g, " ")
319
+ .slice(0, 800);
320
+ }
@@ -318,7 +318,7 @@ export interface AssistantChatProps {
318
318
  composerAreaClassName?: string;
319
319
  /** Placeholder for the shared composer in its normal idle state. */
320
320
  composerPlaceholder?: string;
321
- /** Sidebar uses a compact setup CTA above the composer; page chat keeps the default below-composer CTA. */
321
+ /** Controls the compactness of the provider setup panel attached above the composer. */
322
322
  missingApiKeySetupLayout?: BuilderSetupCardLayout;
323
323
  /** Visual density for the shared composer shell. */
324
324
  composerLayoutVariant?: AgentComposerLayoutVariant;
@@ -22,13 +22,12 @@ import { readAssistantChatComposerDraft, writeAssistantChatComposerDraft, } from
22
22
  import { TextStreamingContext } from "./chat/markdown-renderer.js";
23
23
  import { CheckpointContext, MessageActionsContext, assistantMessageRunId, UserMessage, AssistantMessage, SelectionAttachedPill, RunningActivityStatus, displayableUserMessageText, isHiddenUserMessage, ServerRunActiveContext, UserStoppedRunContext, } from "./chat/message-components.js";
24
24
  import { repoHasAssistantMessage, getRepoMessages, getRepoMessage, shouldImportServerThreadData, dedupeRepoMessagesById, dropEmptyAssistantMessages, withLastAssistantRunDuration, } from "./chat/repo-helpers.js";
25
- import { BuilderSetupCard, BuilderSetupContent, LoopLimitContinueCard, RunErrorRecoveryCard, PlanModeCallout, getLoopLimitMetadata, getRunErrorMetadata, getRequestModeMetadata, runErrorKey, } from "./chat/run-recovery.js";
25
+ import { BuilderSetupCard, LoopLimitContinueCard, RunErrorRecoveryCard, PlanModeCallout, getLoopLimitMetadata, getRunErrorMetadata, getRequestModeMetadata, runErrorKey, } from "./chat/run-recovery.js";
26
26
  import { createAgentChatRuntimeAdapter, } from "./chat/runtime.js";
27
27
  import { ChatRunningContext, ChatRunningRunIdContext, ChatRunningTurnIdContext, ChatRunDurationContext, SuppressInlineOpenAppContext, ApprovalContext, ReconnectStreamMessage, } from "./chat/tool-call-display.js";
28
28
  import { useAgentChatLifecycleTracking } from "./chat/use-agent-chat-lifecycle-tracking.js";
29
29
  import { useReconnectReaderOwner } from "./chat/use-reconnect-reader-owner.js";
30
30
  import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, useMessageScroller, } from "./components/ui/message-scroller.js";
31
- import { Popover, PopoverContent, PopoverTrigger, } from "./components/ui/popover.js";
32
31
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "./components/ui/tooltip.js";
33
32
  import { AgentComposerFrame, TiptapComposer, } from "./composer/index.js";
34
33
  import { useAgentDynamicSuggestionsResult, } from "./dynamic-suggestions.js";
@@ -1501,15 +1500,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
1501
1500
  agentEngineConfigured.state === "missing" &&
1502
1501
  missingApiKey;
1503
1502
  const isComposerDisabled = composerDisabled;
1504
- const [missingKeySetupOpen, setMissingKeySetupOpen] = useState(false);
1505
- const requestMissingKeySetup = useCallback(() => {
1506
- setMissingKeySetupOpen(true);
1507
- }, []);
1508
- useEffect(() => {
1509
- if (agentEngineConfigured.state !== "configured")
1510
- return;
1511
- setMissingKeySetupOpen(false);
1512
- }, [agentEngineConfigured.state]);
1513
1503
  const [authError, setAuthError] = useState(null);
1514
1504
  const [authSessionAvailable, setAuthSessionAvailable] = useState(false);
1515
1505
  const [queuedMessages, setQueuedMessages] = useState([]);
@@ -2972,7 +2962,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
2972
2962
  // Nudge the shared hook to re-check after a Builder connect.
2973
2963
  const handleBuilderConnected = useCallback(() => {
2974
2964
  focusComposerAfterConnectRef.current = true;
2975
- setMissingKeySetupOpen(false);
2976
2965
  window.dispatchEvent(new Event("agent-engine:configured-changed"));
2977
2966
  }, []);
2978
2967
  useEffect(() => {
@@ -3615,9 +3604,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3615
3604
  const addToQueue = useCallback(async (text, images, references, attachments, requestMode, intent = "queued", recoveryAction, includeComposerContext = false, trackInRunsTray = false, preserveReconnectAutoRecoveryBudget = false, hideUserMessage = false, submitMessageId, approvedToolCalls, continuationTurnId) => {
3616
3605
  if (isAgentChatSubmitCancelled(submitMessageId))
3617
3606
  return;
3618
- if (engineSetupRequired) {
3619
- requestMissingKeySetup();
3620
- }
3621
3607
  if (!preserveReconnectAutoRecoveryBudget) {
3622
3608
  reconnectAutoRecoveryCountRef.current = 0;
3623
3609
  }
@@ -3830,7 +3816,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
3830
3816
  markOptimisticRunning,
3831
3817
  engineSetupRequired,
3832
3818
  appendThreadMessage,
3833
- requestMissingKeySetup,
3834
3819
  selectedEffort,
3835
3820
  selectedEngine,
3836
3821
  selectedModel,
@@ -4036,7 +4021,10 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4036
4021
  const latestAssistantWasPlan = latestMessageRole === "assistant" &&
4037
4022
  getRequestModeMetadata(latestMessage) === "plan";
4038
4023
  const showMissingKeySetup = engineSetupRequired && !authError;
4039
- const showInlineMissingKeySetup = showMissingKeySetup && missingApiKeySetupLayout === "sidebar";
4024
+ const [missingKeyBouncePulse, setMissingKeyBouncePulse] = useState(0);
4025
+ const bounceMissingKeySetup = useCallback(() => {
4026
+ setMissingKeyBouncePulse((pulse) => pulse + 1);
4027
+ }, []);
4040
4028
  const showPlanModeCallout = execMode === "plan" &&
4041
4029
  !planModeDisabled &&
4042
4030
  !isComposerDisabled &&
@@ -4125,7 +4113,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4125
4113
  (guidedQuestions && guidedQuestions.length > 0) ||
4126
4114
  visibleComposerContextItems.length > 0 ||
4127
4115
  showPlanModeCallout ||
4128
- showInlineMissingKeySetup);
4116
+ showMissingKeySetup);
4129
4117
  const approvalResolutionScope = threadId ?? tabId ?? "default";
4130
4118
  const [approvalResolutionState, setApprovalResolutionState] = useState(() => ({
4131
4119
  scope: approvalResolutionScope,
@@ -4224,7 +4212,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4224
4212
  t("agentChat.empty.prompt") }), emptyStateAddon, resolvedSuggestions &&
4225
4213
  resolvedSuggestions.length > 0 ? (_jsx("div", { className: "flex w-full max-w-[280px] flex-col gap-1", children: resolvedSuggestions.map((suggestion) => (_jsx("button", { onClick: () => {
4226
4214
  if (engineSetupRequired) {
4227
- requestMissingKeySetup();
4228
4215
  return;
4229
4216
  }
4230
4217
  void addToQueue(suggestion);
@@ -4287,48 +4274,39 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
4287
4274
  ? { submitLabel: guidedQuestionsSubmitLabel }
4288
4275
  : {}), className: "h-auto items-stretch justify-stretch bg-transparent" }) })), composerError && (_jsxs("div", { role: "alert", className: "shrink-0 mx-3 mb-1.5 flex items-start gap-2 rounded-md border border-destructive/40 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: [_jsx(IconAlertTriangle, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), _jsx("span", { className: "flex-1 leading-snug", children: composerError }), _jsx("button", { type: "button", "aria-label": t("agentChat.common.dismissError"), onClick: () => setComposerError(null), className: "shrink-0 opacity-70 hover:opacity-100", children: _jsx(IconX, { className: "h-3 w-3" }) })] })), _jsxs("div", { className: "agent-composer-stack", "data-agent-composer-adjacent-ui": hasComposerAccessoryAboveStack
4289
4276
  ? "true"
4290
- : undefined, children: [_jsx(SelectionAttachedPill, {}), showPlanModeCallout && (_jsx(PlanModeCallout, { canImplementPlan: canImplementPlan, onImplementPlan: handleImplementPlan, onSwitchToAct: handleSwitchToAct })), showInlineMissingKeySetup ? (_jsx(BuilderSetupCard, { fullWidth: true, layout: "sidebar", onConnected: handleBuilderConnected })) : null, _jsxs(Popover, { open: showMissingKeySetup &&
4291
- !showInlineMissingKeySetup &&
4292
- missingKeySetupOpen, onOpenChange: setMissingKeySetupOpen, children: [_jsx(AgentComposerFrame, { layoutVariant: composerLayoutVariant, className: cn(composerAreaClassName, isComposerDisabled &&
4293
- !showMissingKeySetup &&
4294
- "opacity-70"), rootClassName: cn(showMissingKeySetup &&
4295
- "agent-composer-root--missing-key"), children: showMissingKeySetup &&
4296
- !showInlineMissingKeySetup ? (_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { type: "button", className: "agent-composer-missing-key-trigger", "aria-label": t("agentChat.setup.connectToStart"), children: _jsxs("span", { className: "agent-composer-missing-key-content", children: [_jsxs("span", { className: "agent-composer-missing-key-copy", children: [_jsx("span", { className: "agent-composer-missing-key-title", children: missingApiKeySetupLayout ===
4297
- "sidebar"
4298
- ? t("agentChat.setup.connectToChat")
4299
- : t("agentChat.setup.connectToStart") }), missingApiKeySetupLayout !==
4300
- "sidebar" ? (_jsx("span", { className: "agent-composer-missing-key-description", children: t("agentChat.setup.builderCredits") })) : null] }), _jsx("span", { className: "agent-composer-missing-key-cta", children: t("agentChat.setup.connectAi") })] }) }) })) : (_jsxs(_Fragment, { children: [_jsx(ComposerAttachmentPreviewStrip, {}), _jsx(TiptapComposer, { focusRef: tiptapRef, initialText: initialComposerText ?? undefined, initialTextKey: composerDraftScope, onTextChange: isActiveComposer
4301
- ? handleComposerTextChange
4302
- : undefined, disabled: isComposerDisabled ||
4303
- showInlineMissingKeySetup, placeholder: showInlineMissingKeySetup
4304
- ? t("agentChat.setup.connectPlaceholder")
4305
- : engineSetupRequired
4306
- ? t("agentChat.setup.connectPlaceholder")
4307
- : composerDisabled
4308
- ? (composerDisabledPlaceholder ??
4309
- t("agentChat.composer.openDesktop"))
4310
- : isRunning
4311
- ? queuedMessages.length > 0
4312
- ? t("agentChat.queue.followUpWithCount", {
4313
- count: queuedMessages.length,
4314
- })
4315
- : t("agentChat.queue.followUp")
4316
- : resolveAssistantChatComposerPlaceholder(composerPlaceholder), onSubmit: isRunning ||
4317
- visibleComposerContextItems.length > 0
4318
- ? (text, references, attachments, options) => void addToQueue(text, undefined, references.length > 0
4319
- ? references
4320
- : undefined, attachments, undefined, resolveAssistantChatSubmitIntent({
4321
- isRunning,
4322
- requestedIntent: options?.intent,
4323
- }), undefined, true)
4324
- : undefined, willQueue: engineSetupRequired || isRunning, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, selectedModel: selectedModel ?? defaultModel, selectedEffort: selectedEffort, availableModels: availableModels, availableAgents: availableAgents, selectedAgent: selectedAgent, hostedHarness: hostedHarness, modelListLoading: modelListLoading, onModelChange: shouldShowAssistantChatModelSelector(showModelSelector)
4325
- ? onModelChange
4326
- : undefined, onEffortChange: onEffortChange, onAgentChange: onAgentChange, imageModelMenu: imageModelMenu, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, toolbarSlot: composerToolbarSlot, contextItems: visibleComposerContextItems, onRemoveContextItem: removeComposerContextItem, plusMenuMode: plusMenuMode, layoutVariant: composerLayoutVariant, providerConnectStatusEnabled: providerStatusChecksEnabled, voiceEnabled: true, draftScope: composerDraftScope, interceptBuildRequestsForBuilder: true, onAttachmentError: setComposerError, extraActionButton: composerExtraActionButton, stopButton: showRunningInUI ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => stopActiveRun({
4327
- preserveQueuedMessages: true,
4328
- }), "aria-label": t("agentChat.composer.stopResponse"), "data-agent-composer-slot": "stop-button", className: "shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(IconPlayerStopFilled, { className: "h-3 w-3" }) }) }), _jsx(TooltipContent, { children: t("agentChat.composer.stopResponse") })] })) : undefined })] })) }), showMissingKeySetup &&
4329
- !showInlineMissingKeySetup ? (_jsx(PopoverContent, { side: missingApiKeySetupLayout === "sidebar"
4330
- ? "top"
4331
- : "bottom", align: "center", sideOffset: 8, collisionPadding: 12, "data-agent-native-composer-popover": "true", className: "z-[260] box-border w-[min(calc(100vw-2rem),var(--radix-popover-content-available-width,26rem),26rem)] rounded-lg border-border p-3 shadow-lg", children: _jsx(BuilderSetupContent, { onConnected: handleBuilderConnected, layout: missingApiKeySetupLayout }) })) : null] })] })] }) }) }) }) }) }) }) }) }) }) }));
4277
+ : undefined, children: [_jsx(SelectionAttachedPill, {}), showPlanModeCallout && (_jsx(PlanModeCallout, { canImplementPlan: canImplementPlan, onImplementPlan: handleImplementPlan, onSwitchToAct: handleSwitchToAct })), showMissingKeySetup ? (_jsx(BuilderSetupCard, { fullWidth: true, attached: true, bouncePulse: missingKeyBouncePulse, layout: missingApiKeySetupLayout, onConnected: handleBuilderConnected })) : null, _jsx(AgentComposerFrame, { layoutVariant: composerLayoutVariant, className: cn(composerAreaClassName, showMissingKeySetup &&
4278
+ "agent-composer-area--attached-above", isComposerDisabled &&
4279
+ !showMissingKeySetup &&
4280
+ "opacity-70"), onClick: showMissingKeySetup
4281
+ ? bounceMissingKeySetup
4282
+ : undefined, children: _jsxs(_Fragment, { children: [_jsx(ComposerAttachmentPreviewStrip, {}), _jsx(TiptapComposer, { focusRef: tiptapRef, initialText: initialComposerText ?? undefined, initialTextKey: composerDraftScope, onTextChange: isActiveComposer
4283
+ ? handleComposerTextChange
4284
+ : undefined, disabled: isComposerDisabled || showMissingKeySetup, placeholder: showMissingKeySetup
4285
+ ? t("agentChat.setup.connectPlaceholder")
4286
+ : engineSetupRequired
4287
+ ? t("agentChat.setup.connectPlaceholder")
4288
+ : composerDisabled
4289
+ ? (composerDisabledPlaceholder ??
4290
+ t("agentChat.composer.openDesktop"))
4291
+ : isRunning
4292
+ ? queuedMessages.length > 0
4293
+ ? t("agentChat.queue.followUpWithCount", {
4294
+ count: queuedMessages.length,
4295
+ })
4296
+ : t("agentChat.queue.followUp")
4297
+ : resolveAssistantChatComposerPlaceholder(composerPlaceholder), onSubmit: isRunning ||
4298
+ visibleComposerContextItems.length > 0
4299
+ ? (text, references, attachments, options) => void addToQueue(text, undefined, references.length > 0
4300
+ ? references
4301
+ : undefined, attachments, undefined, resolveAssistantChatSubmitIntent({
4302
+ isRunning,
4303
+ requestedIntent: options?.intent,
4304
+ }), undefined, true)
4305
+ : undefined, willQueue: engineSetupRequired || isRunning, onSlashCommand: onSlashCommand, execMode: execMode, onExecModeChange: onExecModeChange, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, selectedModel: selectedModel ?? defaultModel, selectedEffort: selectedEffort, availableModels: availableModels, availableAgents: availableAgents, selectedAgent: selectedAgent, hostedHarness: hostedHarness, modelListLoading: modelListLoading, onModelChange: shouldShowAssistantChatModelSelector(showModelSelector)
4306
+ ? onModelChange
4307
+ : undefined, onEffortChange: onEffortChange, onAgentChange: onAgentChange, imageModelMenu: imageModelMenu, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, toolbarSlot: composerToolbarSlot, contextItems: visibleComposerContextItems, onRemoveContextItem: removeComposerContextItem, plusMenuMode: plusMenuMode, layoutVariant: composerLayoutVariant, providerConnectStatusEnabled: providerStatusChecksEnabled, voiceEnabled: true, draftScope: composerDraftScope, interceptBuildRequestsForBuilder: true, onAttachmentError: setComposerError, extraActionButton: composerExtraActionButton, stopButton: showRunningInUI ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: () => stopActiveRun({
4308
+ preserveQueuedMessages: true,
4309
+ }), "aria-label": t("agentChat.composer.stopResponse"), "data-agent-composer-slot": "stop-button", className: "shrink-0 flex h-7 w-7 cursor-pointer items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", children: _jsx(IconPlayerStopFilled, { className: "h-3 w-3" }) }) }), _jsx(TooltipContent, { children: t("agentChat.composer.stopResponse") })] })) : undefined })] }) })] })] }) }) }) }) }) }) }) }) }) }) }));
4332
4310
  });
4333
4311
  export const AssistantChat = forwardRef(function AssistantChat({ apiUrl = agentNativePath("/_agent-native/agent-chat"), tabId, browserTabId, threadId, contextScope, contextNamespace, isActiveComposer, ...props }, ref) {
4334
4312
  const modelRef = useRef(props.selectedModel);
@@ -5,6 +5,7 @@ import { withBuilderUtmTrackingParams } from "../shared/builder-link-tracking.js
5
5
  import { agentNativePath } from "./api-path.js";
6
6
  import { BuilderBMark } from "./builder-mark.js";
7
7
  import { writeClipboardText } from "./clipboard.js";
8
+ import { requestDesktopLocalCodeChange } from "./desktop-local-code-change.js";
8
9
  import { getCallbackOrigin } from "./frame.js";
9
10
  import { useBuilderConnectFlow } from "./settings/useBuilderStatus.js";
10
11
  import { cn } from "./utils.js";
@@ -18,6 +19,12 @@ function isLocalDevelopment() {
18
19
  const hostname = window.location.hostname;
19
20
  return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1");
20
21
  }
22
+ function hasElectronShellBridge() {
23
+ if (typeof window === "undefined")
24
+ return false;
25
+ const electronApi = window.electronAPI;
26
+ return Boolean(electronApi?.appConfig);
27
+ }
21
28
  /**
22
29
  * Rich inline card rendered for the `connect-builder` tool call. Shows the
23
30
  * Builder handoff when available, or routes local development requests to the
@@ -48,7 +55,9 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
48
55
  const [sendErr, setSendErr] = useState(null);
49
56
  const [copyErr, setCopyErr] = useState(null);
50
57
  const [promptCopied, setPromptCopied] = useState(false);
58
+ const [localCodeChangeRequested, setLocalCodeChangeRequested] = useState(false);
51
59
  const [localDevelopment] = useState(() => isLocalDevelopment());
60
+ const [electronShell] = useState(() => hasElectronShellBridge());
52
61
  const mountedRef = useRef(true);
53
62
  const copyResetRef = useRef(null);
54
63
  // Tracks whether the user clicked "Connect Builder" *this session*. When
@@ -154,6 +163,12 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
154
163
  const err = sendErr ?? waitlistErr ?? copyErr ?? flow.error;
155
164
  const hasPrompt = prompt.trim().length > 0;
156
165
  const canSend = configured && builderEnabled && hasPrompt;
166
+ const showDesktopLocalHandoff = electronShell && hasPrompt;
167
+ const handleDoLocally = useCallback((event) => {
168
+ if (requestDesktopLocalCodeChange(prompt, event.currentTarget)) {
169
+ setLocalCodeChangeRequested(true);
170
+ }
171
+ }, [prompt]);
157
172
  // Auto-send the user's pending prompt the moment connecting finishes
158
173
  // successfully. Without this, the connect popup closing leaves the user
159
174
  // staring at a "Send to Builder" button — feels like they have to
@@ -181,14 +196,19 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
181
196
  : `AI credits are ready to use. ${CODE_CHANGE_FALLBACK_TEXT}`;
182
197
  let title;
183
198
  let subtitle;
184
- if (runResult) {
199
+ if (localCodeChangeRequested) {
200
+ title = "Preparing a local copy";
201
+ subtitle = "Desktop is cloning this app and applying this request.";
202
+ }
203
+ else if (runResult) {
185
204
  title = "Builder is working on it";
186
205
  subtitle = (_jsxs(_Fragment, { children: ["Working on branch", " ", _jsx("span", { className: "font-mono text-foreground", children: runResult.branchName }), ". Click through to watch progress in the Visual Editor."] }));
187
206
  }
188
207
  else if (showExternalAgentHandoff) {
189
208
  title = "This requires a code change";
190
- subtitle =
191
- "Open your coding agent in this project, then paste this request.";
209
+ subtitle = showDesktopLocalHandoff
210
+ ? "Make the change in a local copy of this app, or copy the request to another coding agent."
211
+ : "Open your coding agent in this project, then paste this request.";
192
212
  }
193
213
  else if (showWaitlist) {
194
214
  title = "This requires a code change";
@@ -206,8 +226,16 @@ export function ConnectBuilderCard({ configured: initialConfigured, builderEnabl
206
226
  title = "Connect Builder.io";
207
227
  subtitle = _jsx(_Fragment, { children: "Builder.io's free tier includes AI credits." });
208
228
  }
209
- return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : showExternalAgentHandoff ? (_jsx(IconCode, { className: "h-5 w-5" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
229
+ return (_jsx("div", { className: cn("my-2 rounded-lg border border-border overflow-hidden"), children: _jsxs("div", { className: "flex items-start gap-3 px-4 py-3.5 bg-gradient-to-br from-teal-500/5 via-transparent to-transparent", children: [_jsx("div", { className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", "bg-foreground text-background"), children: runResult ? (_jsx(IconLoader2, { className: "h-5 w-5 animate-spin" })) : showExternalAgentHandoff ? (_jsx(IconCode, { className: "h-5 w-5" })) : (_jsx(BuilderBMark, { className: "h-5 w-5" })) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "flex items-center gap-2 flex-wrap", children: _jsx("span", { className: "text-sm font-semibold text-foreground", children: title }) }), _jsx("div", { className: "mt-0.5 text-xs text-muted-foreground leading-relaxed", children: subtitle }), showWaitlist && !showDesktopLocalHandoff && (_jsxs("a", { href: DESKTOP_DOWNLOAD_URL, target: "_blank", rel: "noopener noreferrer", className: "mt-2 inline-flex items-center gap-1 text-[11px] font-medium text-muted-foreground no-underline hover:text-foreground", children: ["Download desktop app", _jsx(IconExternalLink, { className: "h-3 w-3" })] })), err && _jsx("div", { className: "mt-2 text-xs text-destructive", children: err }), _jsx("div", { className: "mt-3", children: runResult ? (_jsxs("a", { href: withBuilderUtmTrackingParams(runResult.url, {
210
230
  campaign: "product",
211
231
  content: "connect_builder_card_branch",
212
- }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })) : showExternalAgentHandoff ? (_jsx("button", { type: "button", onClick: () => void handleCopyPrompt(), className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: promptCopied ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Prompt copied"] })) : (_jsxs(_Fragment, { children: [_jsx(IconCopy, { className: "h-3.5 w-3.5" }), "Copy prompt"] })) })) : showWaitlist && !waitlistJoined ? (_jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })) : !configured ? (_jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })) : null })] })] }) }));
232
+ }), target: "_blank", rel: "noopener noreferrer", className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90"), children: ["Open branch in Builder", _jsx(IconExternalLink, { className: "h-3.5 w-3.5" })] })) : canSend ? (_jsxs("div", { className: "flex flex-wrap gap-2", children: [showDesktopLocalHandoff && (_jsxs("button", { type: "button", "data-desktop-local-code-change": true, onClick: handleDoLocally, disabled: localCodeChangeRequested, className: cn("inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent", localCodeChangeRequested && "cursor-wait opacity-70"), children: [localCodeChangeRequested ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconCode, { className: "h-3.5 w-3.5" })), localCodeChangeRequested
233
+ ? "Preparing locally…"
234
+ : "Do locally"] })), _jsx("button", { type: "button", onClick: handleSend, disabled: sending, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", sending && "opacity-70 cursor-wait"), children: sending ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Sending to Builder\u2026"] })) : (_jsx(_Fragment, { children: "Send to Builder" })) })] })) : showExternalAgentHandoff ? (_jsxs("div", { className: "flex flex-wrap gap-2", children: [showDesktopLocalHandoff && (_jsxs("button", { type: "button", "data-desktop-local-code-change": true, onClick: handleDoLocally, disabled: localCodeChangeRequested, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", localCodeChangeRequested && "cursor-wait opacity-70"), children: [localCodeChangeRequested ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconCode, { className: "h-3.5 w-3.5" })), localCodeChangeRequested
235
+ ? "Preparing locally…"
236
+ : "Do locally"] })), _jsx("button", { type: "button", onClick: () => void handleCopyPrompt(), className: cn("inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent"), children: promptCopied ? (_jsxs(_Fragment, { children: [_jsx(IconCheck, { className: "h-3.5 w-3.5" }), "Prompt copied"] })) : (_jsxs(_Fragment, { children: [_jsx(IconCopy, { className: "h-3.5 w-3.5" }), "Copy prompt"] })) })] })) : showWaitlist && !waitlistJoined ? (_jsxs("div", { className: "flex flex-wrap gap-2", children: [showDesktopLocalHandoff && (_jsxs("button", { type: "button", "data-desktop-local-code-change": true, onClick: handleDoLocally, disabled: localCodeChangeRequested, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", localCodeChangeRequested && "cursor-wait opacity-70"), children: [localCodeChangeRequested ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconCode, { className: "h-3.5 w-3.5" })), localCodeChangeRequested
237
+ ? "Preparing locally…"
238
+ : "Do locally"] })), _jsx("button", { type: "button", onClick: handleJoinWaitlist, disabled: joiningWaitlist, className: cn("inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent", joiningWaitlist && "opacity-70 cursor-wait"), children: joiningWaitlist ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Joining\u2026"] })) : (_jsx(_Fragment, { children: "Join the waitlist" })) })] })) : !configured ? (_jsxs("div", { className: "flex flex-wrap gap-2", children: [showDesktopLocalHandoff && (_jsxs("button", { type: "button", "data-desktop-local-code-change": true, onClick: handleDoLocally, disabled: localCodeChangeRequested, className: cn("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors", "bg-foreground text-background hover:bg-foreground/90", localCodeChangeRequested && "cursor-wait opacity-70"), children: [localCodeChangeRequested ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconCode, { className: "h-3.5 w-3.5" })), localCodeChangeRequested
239
+ ? "Preparing locally…"
240
+ : "Do locally"] })), _jsx("button", { type: "button", onClick: () => flow.start(), disabled: connecting, className: cn("inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent", connecting && "opacity-70 cursor-wait"), children: connecting ? (_jsxs(_Fragment, { children: [_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" }), "Waiting for Builder\u2026"] })) : ("Connect Builder") })] })) : showDesktopLocalHandoff ? (_jsxs("button", { type: "button", "data-desktop-local-code-change": true, onClick: handleDoLocally, disabled: localCodeChangeRequested, className: cn("inline-flex items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent", localCodeChangeRequested && "cursor-wait opacity-70"), children: [localCodeChangeRequested ? (_jsx(IconLoader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(IconCode, { className: "h-3.5 w-3.5" })), localCodeChangeRequested ? "Preparing locally…" : "Do locally"] })) : null })] })] }) }));
213
241
  }