@agent-native/core 0.164.12 → 0.164.17

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 (90) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/netlify.toml +4 -0
  3. package/corpus/templates/clips/actions/request-transcript.ts +10 -6
  4. package/corpus/templates/content/actions/_database-source-utils.ts +25 -2
  5. package/corpus/templates/content/actions/_database-utils.ts +2 -0
  6. package/corpus/templates/content/actions/_local-folder-source.ts +186 -2
  7. package/corpus/templates/content/actions/connect-local-folder-source.ts +23 -8
  8. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +3 -3
  9. package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +2 -2
  10. package/corpus/templates/content/actions/sync-local-folder-source.ts +130 -12
  11. package/corpus/templates/content/app/components/LocalFolderLiveSync.tsx +77 -0
  12. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +298 -3
  13. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +354 -8
  14. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +23 -1
  15. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +81 -1
  16. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +36 -0
  17. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +18 -4
  18. package/corpus/templates/content/app/components/editor/local-source-write-state.ts +52 -0
  19. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +244 -6
  20. package/corpus/templates/content/app/components/sidebar/local-source-hierarchy.ts +189 -0
  21. package/corpus/templates/content/app/hooks/use-create-page.ts +26 -4
  22. package/corpus/templates/content/app/hooks/use-documents.ts +83 -0
  23. package/corpus/templates/content/app/i18n-data.ts +239 -0
  24. package/corpus/templates/content/app/lib/desktop-content-files.ts +57 -1
  25. package/corpus/templates/content/app/lib/local-content-source-files.ts +210 -34
  26. package/corpus/templates/content/app/lib/local-folder-live-sync.ts +191 -0
  27. package/corpus/templates/content/app/root.tsx +2 -1
  28. package/corpus/templates/content/app/routes/_app.local-files.tsx +103 -38
  29. package/corpus/templates/content/docs/product/capabilities/content.source.file-folder.md +2 -2
  30. package/corpus/templates/content/docs/product/features/content.feature.bring-your-local-work.md +3 -3
  31. package/corpus/templates/content/docs/product/roadmap.md +2 -2
  32. package/corpus/templates/content/docs/solutions/2026-08-18-colored-text-highlighting-shape.md +693 -0
  33. package/corpus/templates/content/docs/solutions/2026-08-18-content-navigation-coherence-shape.md +482 -0
  34. package/corpus/templates/content/package.json +1 -0
  35. package/corpus/templates/content/shared/api.ts +25 -0
  36. package/corpus/templates/content/shared/nfm.ts +14 -3
  37. package/corpus/templates/factory/AGENTS.md +24 -29
  38. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +8 -28
  39. package/corpus/templates/factory/actions/get-triage-config.ts +1 -0
  40. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +67 -59
  41. package/corpus/templates/factory/actions/list-triage-items.ts +6 -1
  42. package/corpus/templates/factory/actions/reconcile-triage-run.ts +7 -1
  43. package/corpus/templates/factory/actions/save-triage-config.ts +27 -7
  44. package/corpus/templates/factory/actions/start-builder-for-item.ts +113 -31
  45. package/corpus/templates/factory/actions/view-screen.ts +12 -0
  46. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +457 -0
  47. package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +53 -0
  48. package/corpus/templates/factory/app/components/layout/Layout.tsx +2 -0
  49. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +3 -1
  50. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +10 -7
  51. package/corpus/templates/factory/app/i18n/en-US.ts +3 -0
  52. package/corpus/templates/factory/app/routes/agents.tsx +22 -1
  53. package/corpus/templates/factory/app/routes/factory-settings.tsx +34 -0
  54. package/corpus/templates/factory/app/routes/factory.tsx +33 -323
  55. package/corpus/templates/factory/server/connectors/slack.ts +62 -0
  56. package/corpus/templates/factory/server/db/schema.ts +1 -0
  57. package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +30 -0
  58. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -8
  59. package/corpus/templates/factory/server/plugins/factory-migrations.ts +7 -0
  60. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +43 -25
  61. package/corpus/templates/factory/server/triage/ai-services-git.ts +2 -0
  62. package/corpus/templates/factory/server/triage/pr-monitor.ts +2 -1
  63. package/corpus/templates/factory/server/triage/pr-policy.ts +197 -19
  64. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +127 -0
  65. package/corpus/templates/factory/server/triage/slack-client.ts +79 -6
  66. package/dist/app-config/auth.d.ts +4 -0
  67. package/dist/app-config/auth.js +7 -0
  68. package/dist/app-config/schema.d.ts +3 -0
  69. package/dist/app-config/schema.js +2 -0
  70. package/dist/cli/code-agent-executor.js +6 -1
  71. package/dist/cli/code-agent-mcp-config.d.ts +2 -2
  72. package/dist/cli/code-agent-mcp-config.js +34 -9
  73. package/dist/client/MultiTabAssistantChat.js +28 -1
  74. package/dist/client/org/OrgSwitcher.js +8 -4
  75. package/dist/client/use-chat-threads.d.ts +1 -0
  76. package/dist/client/use-chat-threads.js +13 -3
  77. package/dist/collab/awareness.d.ts +2 -2
  78. package/dist/collab/routes.d.ts +1 -1
  79. package/dist/mcp-client/config.d.ts +11 -0
  80. package/dist/mcp-client/config.js +33 -26
  81. package/dist/notifications/routes.d.ts +3 -3
  82. package/dist/observability/routes.d.ts +5 -5
  83. package/dist/progress/routes.d.ts +1 -1
  84. package/dist/resources/handlers.d.ts +2 -2
  85. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  86. package/dist/server/auth.js +17 -3
  87. package/dist/server/realtime-token.d.ts +1 -1
  88. package/dist/server/transcribe-voice.d.ts +1 -1
  89. package/dist/vite/client.js +2 -0
  90. package/package.json +1 -1
@@ -2,13 +2,18 @@ import { resolveConnectorSecret } from "../connectors/credentials.js";
2
2
  import {
3
3
  getChannelHistory as readChannelHistory,
4
4
  addEyesReaction as writeEyesReaction,
5
+ authTest as readAuthTest,
6
+ getEyesReaction as readEyesReaction,
5
7
  getThread as readThread,
6
8
  getTeamInfo as readTeamInfo,
7
9
  getUserInfo as readUserInfo,
8
10
  postThreadReply as writeThreadReply,
9
11
  type ChannelHistoryResult,
12
+ type SlackAuthTestResult,
13
+ type SlackMessage,
10
14
  type SlackPostMessageResult,
11
15
  type SlackReactionResult,
16
+ type SlackReactionState,
12
17
  type SlackTeamInfo,
13
18
  type SlackUserInfo,
14
19
  type SlackTokenResolver,
@@ -40,14 +45,38 @@ function createTokenResolver({
40
45
 
41
46
  export function createSlackReader(identity: SlackReaderIdentity) {
42
47
  const tokenResolver = createTokenResolver(identity);
48
+ const identities = new Map<Workspace, SlackAuthTestResult>();
49
+
50
+ async function getAgentNativeIdentity(
51
+ workspace: Workspace,
52
+ ): Promise<SlackAuthTestResult> {
53
+ const existing = identities.get(workspace);
54
+ if (existing) return existing;
55
+ const auth = await readAuthTest(workspace, tokenResolver);
56
+ const userName = auth.userName.trim().replace(/^@/, "").toLowerCase();
57
+ if (userName !== "agent-native") {
58
+ throw new Error(
59
+ `Slack credential authenticated as @${auth.userName}, not @agent-native.`,
60
+ );
61
+ }
62
+ identities.set(workspace, auth);
63
+ return auth;
64
+ }
65
+
66
+ async function verifyAgentNativeIdentity(
67
+ workspace: Workspace,
68
+ ): Promise<void> {
69
+ await getAgentNativeIdentity(workspace);
70
+ }
43
71
 
44
72
  return {
45
- getChannelHistory(
73
+ async getChannelHistory(
46
74
  workspace: Workspace,
47
75
  channelId: string,
48
76
  limit?: number,
49
77
  cursor?: string,
50
78
  ): Promise<ChannelHistoryResult> {
79
+ await verifyAgentNativeIdentity(workspace);
51
80
  return readChannelHistory(
52
81
  workspace,
53
82
  channelId,
@@ -56,19 +85,25 @@ export function createSlackReader(identity: SlackReaderIdentity) {
56
85
  tokenResolver,
57
86
  );
58
87
  },
59
- getTeamInfo(workspace: Workspace): Promise<SlackTeamInfo> {
88
+ async getTeamInfo(workspace: Workspace): Promise<SlackTeamInfo> {
89
+ await verifyAgentNativeIdentity(workspace);
60
90
  return readTeamInfo(workspace, tokenResolver);
61
91
  },
62
- getUserInfo(workspace: Workspace, userId: string): Promise<SlackUserInfo> {
92
+ async getUserInfo(
93
+ workspace: Workspace,
94
+ userId: string,
95
+ ): Promise<SlackUserInfo> {
96
+ await verifyAgentNativeIdentity(workspace);
63
97
  return readUserInfo(workspace, userId, tokenResolver);
64
98
  },
65
- getThread(
99
+ async getThread(
66
100
  workspace: Workspace,
67
101
  channelId: string,
68
102
  threadTs: string,
69
103
  limit?: number,
70
104
  cursor?: string,
71
105
  ): Promise<ThreadRepliesResult> {
106
+ await verifyAgentNativeIdentity(workspace);
72
107
  return readThread(
73
108
  workspace,
74
109
  channelId,
@@ -78,19 +113,21 @@ export function createSlackReader(identity: SlackReaderIdentity) {
78
113
  tokenResolver,
79
114
  );
80
115
  },
81
- addEyesReaction(
116
+ async addEyesReaction(
82
117
  workspace: Workspace,
83
118
  channelId: string,
84
119
  timestamp: string,
85
120
  ): Promise<SlackReactionResult> {
121
+ await verifyAgentNativeIdentity(workspace);
86
122
  return writeEyesReaction(workspace, channelId, timestamp, tokenResolver);
87
123
  },
88
- postThreadReply(
124
+ async postThreadReply(
89
125
  workspace: Workspace,
90
126
  channelId: string,
91
127
  threadTs: string,
92
128
  text: string,
93
129
  ): Promise<SlackPostMessageResult> {
130
+ await verifyAgentNativeIdentity(workspace);
94
131
  return writeThreadReply(
95
132
  workspace,
96
133
  channelId,
@@ -99,5 +136,41 @@ export function createSlackReader(identity: SlackReaderIdentity) {
99
136
  tokenResolver,
100
137
  );
101
138
  },
139
+ async getEyesReaction(
140
+ workspace: Workspace,
141
+ channelId: string,
142
+ timestamp: string,
143
+ ): Promise<SlackReactionState> {
144
+ await verifyAgentNativeIdentity(workspace);
145
+ return readEyesReaction(workspace, channelId, timestamp, tokenResolver);
146
+ },
147
+ async getCompleteThread(
148
+ workspace: Workspace,
149
+ channelId: string,
150
+ threadTs: string,
151
+ ): Promise<{ messages: SlackMessage[]; hasMore: boolean }> {
152
+ const messages: SlackMessage[] = [];
153
+ let cursor: string | undefined;
154
+ for (let page = 0; page < 100; page += 1) {
155
+ const result = await this.getThread(
156
+ workspace,
157
+ channelId,
158
+ threadTs,
159
+ 100,
160
+ cursor,
161
+ );
162
+ messages.push(...result.messages);
163
+ if (!result.has_more) return { messages, hasMore: false };
164
+ if (!result.next_cursor) {
165
+ throw new Error(
166
+ "Slack thread pagination is incomplete because the provider omitted its next cursor.",
167
+ );
168
+ }
169
+ cursor = result.next_cursor;
170
+ }
171
+ return { messages, hasMore: true };
172
+ },
173
+ verifyAgentNativeIdentity,
174
+ getAgentNativeIdentity,
102
175
  };
103
176
  }
@@ -0,0 +1,4 @@
1
+ import { z } from "zod";
2
+ export declare const authConfig: z.ZodObject<{
3
+ disableDesktopSsoFallbackInDevelopment: z.ZodDefault<z.ZodBoolean>;
4
+ }, z.core.$strip>;
@@ -0,0 +1,7 @@
1
+ import { z } from "zod";
2
+ export const authConfig = z.object({
3
+ disableDesktopSsoFallbackInDevelopment: z.boolean().default(false).meta({
4
+ env: "AGENT_NATIVE_DISABLE_DESKTOP_SSO_FALLBACK",
5
+ doc: "Disable the loopback Desktop SSO fallback in development so isolated acceptance runs can use their configured local identity. Ignored in production.",
6
+ }),
7
+ });
@@ -32,6 +32,9 @@ export declare const appConfigSchema: z.ZodObject<{
32
32
  packageName: z.ZodOptional<z.ZodString>;
33
33
  template: z.ZodOptional<z.ZodString>;
34
34
  }, z.core.$strip>>;
35
+ auth: z.ZodPrefault<z.ZodObject<{
36
+ disableDesktopSsoFallbackInDevelopment: z.ZodDefault<z.ZodBoolean>;
37
+ }, z.core.$strip>>;
35
38
  integrations: z.ZodPrefault<z.ZodObject<{
36
39
  allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
37
40
  }, z.core.$strip>>;
@@ -2,6 +2,7 @@ import { z } from "zod";
2
2
  import { a2aConfig } from "./a2a.js";
3
3
  import { agentConfig } from "./agent.js";
4
4
  import { appConfig } from "./app.js";
5
+ import { authConfig } from "./auth.js";
5
6
  import { integrationsConfig } from "./integrations.js";
6
7
  import { privateBlobConfig } from "./private-blob.js";
7
8
  import { workspaceConfig } from "./workspace.js";
@@ -25,6 +26,7 @@ export const appConfigSchema = z.object({
25
26
  a2a: a2aConfig.prefault({}),
26
27
  agent: agentConfig.prefault({}),
27
28
  app: appConfig.prefault({}),
29
+ auth: authConfig.prefault({}),
28
30
  integrations: integrationsConfig.prefault({}),
29
31
  privateBlob: privateBlobConfig.prefault({}),
30
32
  workspace: workspaceConfig.prefault({}),
@@ -1226,8 +1226,13 @@ async function executeCodexCliRun(options) {
1226
1226
  const streamToolOutputToStdout = options.streamToolOutputToStdout ??
1227
1227
  process.env.AGENT_NATIVE_CODE_AGENT_STRUCTURED_STDOUT !== "1";
1228
1228
  const additionalSkillsRoot = process.env.AGENT_NATIVE_CODE_AGENT_SKILLS_ROOT?.trim();
1229
+ // Desktop has already resolved and capability-scoped its MCP catalog before
1230
+ // spawning the packaged runner. Reading persisted settings again here would
1231
+ // both widen that boundary and require the app's native database binding in
1232
+ // the standalone runner process.
1233
+ const mcpConfig = process.env.MCP_SERVERS === undefined ? await buildMergedConfig() : null;
1229
1234
  const args = [
1230
- ...codexMcpConfigArgs(),
1235
+ ...codexMcpConfigArgs(mcpConfig),
1231
1236
  "--ask-for-approval",
1232
1237
  "never",
1233
1238
  "--sandbox",
@@ -1,4 +1,4 @@
1
- import type { McpConfig } from "../mcp-client/config.js";
1
+ import { type McpConfig } from "../mcp-client/config.js";
2
2
  /**
3
3
  * Keep a desktop coding run on the app registry's MCP servers. The normal
4
4
  * server-side manager merges persisted MCP settings for web requests, but a
@@ -18,4 +18,4 @@ export declare function mergeCodeAgentMcpConfig(config: McpConfig | null, enviro
18
18
  * coding session receives the workspace apps, not the user's global MCP
19
19
  * catalog.
20
20
  */
21
- export declare function codexMcpConfigArgs(environment?: NodeJS.ProcessEnv): string[];
21
+ export declare function codexMcpConfigArgs(config?: McpConfig | null, environment?: NodeJS.ProcessEnv): string[];
@@ -1,3 +1,4 @@
1
+ import { resolveDesktopChildComputerMcpServer, } from "../mcp-client/config.js";
1
2
  const DESKTOP_MCP_ALLOWLIST_ENV = "AGENT_NATIVE_CODE_AGENT_MCP_SERVER_ALLOWLIST";
2
3
  function parseServerAllowlist(raw) {
3
4
  if (raw === undefined)
@@ -17,6 +18,9 @@ export function restrictCodeAgentMcpConfig(config, environment = process.env) {
17
18
  const allowlist = parseServerAllowlist(environment[DESKTOP_MCP_ALLOWLIST_ENV]);
18
19
  if (!allowlist || !config)
19
20
  return config;
21
+ const desktopServer = findDesktopChildComputerMcpServer(config, environment);
22
+ if (desktopServer)
23
+ allowlist.add(desktopServer.id);
20
24
  const servers = Object.fromEntries(Object.entries(config.servers).filter(([id]) => allowlist.has(id)));
21
25
  return { ...config, servers };
22
26
  }
@@ -59,6 +63,18 @@ function parseMcpServers(environment) {
59
63
  return [id, value];
60
64
  }));
61
65
  }
66
+ function desktopChildComputerServerConfig(environment) {
67
+ return resolveDesktopChildComputerMcpServer({}, environment)?.config ?? null;
68
+ }
69
+ function findDesktopChildComputerMcpServer(config, environment) {
70
+ const expected = desktopChildComputerServerConfig(environment);
71
+ if (!expected || expected.type !== "http")
72
+ return null;
73
+ const match = Object.entries(config.servers).find(([, server]) => server.type === "http" &&
74
+ server.url === expected.url &&
75
+ server.headers?.Authorization === expected.headers?.Authorization);
76
+ return match ? { id: match[0], config: match[1] } : null;
77
+ }
62
78
  /**
63
79
  * Add host-provided app/plugin servers to the normal merged config. The
64
80
  * filesystem and environment layers have different precedence rules, so the
@@ -66,14 +82,23 @@ function parseMcpServers(environment) {
66
82
  */
67
83
  export function mergeCodeAgentMcpConfig(config, environment = process.env) {
68
84
  const environmentServers = parseMcpServers(environment);
69
- if (Object.keys(environmentServers).length === 0)
85
+ const servers = {
86
+ ...(config?.servers ?? {}),
87
+ ...environmentServers,
88
+ };
89
+ const existingDesktopServer = config
90
+ ? findDesktopChildComputerMcpServer(config, environment)
91
+ : null;
92
+ const desktopServer = existingDesktopServer
93
+ ? null
94
+ : resolveDesktopChildComputerMcpServer(servers, environment);
95
+ if (desktopServer)
96
+ servers[desktopServer.id] = desktopServer.config;
97
+ if (Object.keys(servers).length === 0)
70
98
  return config;
71
99
  return {
72
100
  ...(config ?? { servers: {} }),
73
- servers: {
74
- ...(config?.servers ?? {}),
75
- ...environmentServers,
76
- },
101
+ servers,
77
102
  source: config?.source
78
103
  ? `${config.source}+desktop-environment`
79
104
  : "env:MCP_SERVERS",
@@ -85,8 +110,8 @@ export function mergeCodeAgentMcpConfig(config, environment = process.env) {
85
110
  * coding session receives the workspace apps, not the user's global MCP
86
111
  * catalog.
87
112
  */
88
- export function codexMcpConfigArgs(environment = process.env) {
89
- const servers = parseMcpServers(environment);
113
+ export function codexMcpConfigArgs(config = null, environment = process.env) {
114
+ const servers = restrictCodeAgentMcpConfig(mergeCodeAgentMcpConfig(config, environment), environment)?.servers ?? {};
90
115
  if (Object.keys(servers).length === 0)
91
116
  return [];
92
117
  const args = [];
@@ -94,9 +119,9 @@ export function codexMcpConfigArgs(environment = process.env) {
94
119
  if (server.type !== "http" || !server.url)
95
120
  continue;
96
121
  const key = codexConfigKey(serverId);
97
- args.push("-c", `mcp_servers.${tomlString(key)}.url=${tomlString(server.url)}`);
122
+ args.push("-c", `mcp_servers.${key}.url=${tomlString(server.url)}`);
98
123
  if (server.headers && Object.keys(server.headers).length > 0) {
99
- args.push("-c", `mcp_servers.${tomlString(key)}.http_headers=${tomlInlineTable(server.headers)}`);
124
+ args.push("-c", `mcp_servers.${key}.http_headers=${tomlInlineTable(server.headers)}`);
100
125
  }
101
126
  }
102
127
  return args;
@@ -515,7 +515,7 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
515
515
  threadUrlParamName,
516
516
  threadUrlSyncEnabled,
517
517
  ]);
518
- const { threads, activeThreadId, isLoading, createThread, switchThread: switchThreadState, forkThread, saveThreadData, generateTitle, searchThreads, loadMoreThreads, refreshThreads, hasMoreThreads, isLoadingMoreThreads, threadsLoadError, isNewThread, pinThread, renameThread, } = useChatThreads(apiUrl, storageKey, scope, {
518
+ const { threads, activeThreadId, isLoading, createThread, switchThread: switchThreadState, forkThread, saveThreadData, generateTitle, searchThreads, loadMoreThreads, refreshThreads, hasMoreThreads, isLoadingMoreThreads, threadsLoadError, restoredThreadIdOnListFailure, isNewThread, pinThread, renameThread, } = useChatThreads(apiUrl, storageKey, scope, {
519
519
  restoreActiveThread,
520
520
  routeThreadId: threadUrlSyncEnabled
521
521
  ? urlThreadId
@@ -1017,7 +1017,34 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
1017
1017
  // optimistic id for a brand-new session); the activeThreadId effect above
1018
1018
  // adds it to openTabIds without spinning up a duplicate thread.
1019
1019
  const autoCreatingRef = useRef(false);
1020
+ const restoreFailureReplacementInFlightRef = useRef(null);
1020
1021
  const lastTabReplacementInFlightRef = useRef(false);
1022
+ useEffect(() => {
1023
+ if (isLoading ||
1024
+ !restoredThreadIdOnListFailure ||
1025
+ activeThreadId !== restoredThreadIdOnListFailure ||
1026
+ restoreFailureReplacementInFlightRef.current ===
1027
+ restoredThreadIdOnListFailure) {
1028
+ return;
1029
+ }
1030
+ restoreFailureReplacementInFlightRef.current =
1031
+ restoredThreadIdOnListFailure;
1032
+ void createThread().then((id) => {
1033
+ if (!id) {
1034
+ restoreFailureReplacementInFlightRef.current = null;
1035
+ return;
1036
+ }
1037
+ newThreadIds.current.add(id);
1038
+ setOpenTabIds([id]);
1039
+ writeThreadUrl(null, { replace: true });
1040
+ });
1041
+ }, [
1042
+ activeThreadId,
1043
+ createThread,
1044
+ isLoading,
1045
+ restoredThreadIdOnListFailure,
1046
+ writeThreadUrl,
1047
+ ]);
1021
1048
  useEffect(() => {
1022
1049
  if (isLoading || autoCreatingRef.current)
1023
1050
  return;
@@ -7,6 +7,7 @@ import { setBrowserDemoModeEnabled } from "../../demo/browser-state.js";
7
7
  import { shouldOfferWorkspace } from "../../org/workspace-url.js";
8
8
  import { agentNativePath } from "../api-path.js";
9
9
  import { useT } from "../i18n.js";
10
+ import { buildSignInReturnHref } from "../require-session.js";
10
11
  import { useDemoModeStatus } from "../use-demo-mode-status.js";
11
12
  import { notifySessionInvalidated, useSession } from "../use-session.js";
12
13
  import { useOrg, useSwitchOrg, useCreateOrg, useInviteMember, useAcceptInvitation, useJoinByDomain, } from "./hooks.js";
@@ -142,16 +143,19 @@ export function OrgSwitcher({ className, hideWhenSingle, reserveSpace, compact,
142
143
  return;
143
144
  setSigningOut(true);
144
145
  try {
145
- await fetch(agentNativePath("/_agent-native/auth/logout"), {
146
+ const response = await fetch(agentNativePath("/_agent-native/auth/logout"), {
146
147
  method: "POST",
147
148
  credentials: "include",
148
149
  });
150
+ if (!response.ok) {
151
+ console.warn("Logout request returned an error before sign-in", response.status);
152
+ }
149
153
  }
150
- catch {
151
- /* fall through to reload — server may already have cleared the cookie */
154
+ catch (error) {
155
+ console.warn("Unable to complete logout request before sign-in", error);
152
156
  }
153
157
  notifySessionInvalidated();
154
- window.location.reload();
158
+ window.location.replace(buildSignInReturnHref());
155
159
  };
156
160
  if (!org) {
157
161
  return reserveSpace && isLoading ? (_jsx(OrgSwitcherLoadingPlaceholder, { className: className })) : null;
@@ -96,6 +96,7 @@ export declare function useChatThreads(apiUrl?: string, storageKey?: string, sco
96
96
  hasMoreThreads: boolean;
97
97
  isLoadingMoreThreads: boolean;
98
98
  threadsLoadError: string | null;
99
+ restoredThreadIdOnListFailure: string | null;
99
100
  isNewThread: (id: string) => boolean;
100
101
  };
101
102
  export {};
@@ -157,6 +157,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
157
157
  const [hasMoreThreads, setHasMoreThreads] = useState(false);
158
158
  const [isLoadingMoreThreads, setIsLoadingMoreThreads] = useState(false);
159
159
  const [threadsLoadError, setThreadsLoadError] = useState(null);
160
+ const [restoredThreadIdOnListFailure, setRestoredThreadIdOnListFailure] = useState(null);
160
161
  const nextThreadsOffsetRef = useRef(0);
161
162
  const latestFetchRequestRef = useRef(0);
162
163
  const threadsRef = useRef(threads);
@@ -483,12 +484,20 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
483
484
  const loadedThreads = await fetchThreads();
484
485
  const restoredId = activeThreadIdRef.current;
485
486
  if (loadedThreads === undefined) {
486
- // Thread-list fetch failed. Do not reclassify a saved id as a new
487
- // optimistic tab; AssistantChat should still get a chance to restore
488
- // the specific saved thread via /threads/:id.
487
+ // Keep the saved id intact for retry/history, but tell the owning
488
+ // surface to replace the visible tab. A failed list request cannot
489
+ // prove that the saved thread is gone; it only proves that this mount
490
+ // cannot safely restore it.
491
+ if (restoredId &&
492
+ autoCreate &&
493
+ !routeControlsActiveThread &&
494
+ !newlyCreatedRef.current.has(restoredId)) {
495
+ setRestoredThreadIdOnListFailure(restoredId);
496
+ }
489
497
  setIsLoading(false);
490
498
  return;
491
499
  }
500
+ setRestoredThreadIdOnListFailure(null);
492
501
  // Exempts route-owned threads (the URL names what the user asked for) and
493
502
  // ids this client generated, which have never reached the server.
494
503
  const lookupRestored = Boolean(restoredId &&
@@ -1074,6 +1083,7 @@ export function useChatThreads(apiUrl = agentNativePath("/_agent-native/agent-ch
1074
1083
  hasMoreThreads,
1075
1084
  isLoadingMoreThreads,
1076
1085
  threadsLoadError,
1086
+ restoredThreadIdOnListFailure,
1077
1087
  isNewThread,
1078
1088
  };
1079
1089
  }
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
62
62
  error: string;
63
63
  states?: undefined;
64
64
  } | {
65
- error?: undefined;
66
65
  states: {
67
66
  clientId: number;
68
67
  state: string;
69
68
  }[];
69
+ error?: undefined;
70
70
  }>>;
71
71
  /**
72
72
  * GET /_agent-native/collab/:docId/users
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
77
77
  error: string;
78
78
  users?: undefined;
79
79
  } | {
80
- error?: undefined;
81
80
  users: {
82
81
  clientId: number;
83
82
  lastSeen: number;
84
83
  }[];
84
+ error?: undefined;
85
85
  }>>;
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
26
26
  * Body: { update: string (base64), requestSource?: string }
27
27
  */
28
28
  export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
29
- error: string;
30
29
  ok?: undefined;
30
+ error: string;
31
31
  } | {
32
32
  error?: undefined;
33
33
  ok: boolean;
@@ -60,12 +60,23 @@ export interface McpConfig {
60
60
  /** Where the config was loaded from (workspace root path, app path, or "env") */
61
61
  source?: string;
62
62
  }
63
+ export interface DesktopChildComputerMcpServer {
64
+ id: string;
65
+ config: McpHttpServerConfig;
66
+ }
63
67
  /**
64
68
  * Load MCP configuration.
65
69
  *
66
70
  * @param startDir - Directory to start the upward search from (defaults to cwd)
67
71
  */
68
72
  export declare function loadMcpConfig(startDir?: string): McpConfig | null;
73
+ /**
74
+ * The desktop process supplies this configuration only to a child it spawned
75
+ * for a specific Agent run. Raw MCP config cannot opt into this trust path:
76
+ * the explicit child gate, loopback URL, and strong per-run bearer are all
77
+ * required. Existing user servers always win on key collisions.
78
+ */
79
+ export declare function resolveDesktopChildComputerMcpServer(existingServers: Record<string, McpServerConfig>, environment?: NodeJS.ProcessEnv): DesktopChildComputerMcpServer | null;
69
80
  /**
70
81
  * Auto-detect the claude-in-chrome MCP server if it's installed but no
71
82
  * config file exists. Gated by `AGENT_NATIVE_DISABLE_MCP_AUTODETECT`.
@@ -96,39 +96,46 @@ export function loadMcpConfig(startDir) {
96
96
  * the explicit child gate, loopback URL, and strong per-run bearer are all
97
97
  * required. Existing user servers always win on key collisions.
98
98
  */
99
- function mergeDesktopChildComputerConfig(base) {
100
- if (process.env.AGENT_NATIVE_DESKTOP_CHILD !== "1")
101
- return base;
102
- const url = process.env.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_URL?.trim();
103
- const token = process.env.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_TOKEN?.trim();
99
+ export function resolveDesktopChildComputerMcpServer(existingServers, environment = process.env) {
100
+ if (environment.AGENT_NATIVE_DESKTOP_CHILD !== "1")
101
+ return null;
102
+ const url = environment.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_URL?.trim();
103
+ const token = environment.AGENT_NATIVE_DESKTOP_COMPUTER_MCP_TOKEN?.trim();
104
104
  if (!url || !token || !/^[A-Za-z0-9_-]{32,}$/.test(token))
105
- return base;
106
- try {
107
- const parsed = new URL(url);
108
- if (parsed.protocol !== "http:" ||
109
- parsed.hostname !== "127.0.0.1" ||
110
- parsed.pathname !== "/mcp" ||
111
- parsed.username ||
112
- parsed.password) {
113
- return base;
114
- }
115
- }
116
- catch {
117
- return base;
105
+ return null;
106
+ if (!URL.canParse(url))
107
+ return null;
108
+ const parsed = new URL(url);
109
+ if (parsed.protocol !== "http:" ||
110
+ parsed.hostname !== "127.0.0.1" ||
111
+ parsed.pathname !== "/mcp" ||
112
+ parsed.username ||
113
+ parsed.password) {
114
+ return null;
118
115
  }
119
- const servers = { ...(base?.servers ?? {}) };
120
116
  let serverId = DESKTOP_COMPUTER_SERVER_ID;
121
- for (let suffix = 2; servers[serverId]; suffix += 1) {
117
+ for (let suffix = 2; existingServers[serverId]; suffix += 1) {
122
118
  serverId = `${DESKTOP_COMPUTER_SERVER_ID}-${suffix}`;
123
119
  }
124
- servers[serverId] = {
125
- type: "http",
126
- url,
127
- headers: { Authorization: `Bearer ${token}` },
128
- description: "Authenticated computer control for this Agent Native desktop task",
120
+ return {
121
+ id: serverId,
122
+ config: {
123
+ type: "http",
124
+ url,
125
+ headers: { Authorization: `Bearer ${token}` },
126
+ description: "Authenticated computer control for this Agent Native desktop task",
127
+ },
129
128
  };
129
+ }
130
+ function mergeDesktopChildComputerConfig(base) {
131
+ const desktopServer = resolveDesktopChildComputerMcpServer(base?.servers ?? {});
132
+ if (!desktopServer)
133
+ return base;
130
134
  return {
131
- servers,
135
+ servers: {
136
+ ...(base?.servers ?? {}),
137
+ [desktopServer.id]: desktopServer.config,
138
+ },
132
139
  source: base?.source ? `${base.source}+desktop-child` : "desktop-child",
133
140
  };
134
141
  }
@@ -11,14 +11,14 @@
11
11
  * DELETE /_agent-native/notifications/:id — delete
12
12
  */
13
13
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
14
- error?: undefined;
15
14
  count: number;
16
15
  updated?: undefined;
16
+ error?: undefined;
17
17
  ok?: undefined;
18
18
  } | {
19
- error?: undefined;
20
19
  count?: undefined;
21
20
  updated: number;
21
+ error?: undefined;
22
22
  ok?: undefined;
23
23
  } | {
24
24
  count?: undefined;
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
26
26
  error: string;
27
27
  ok?: undefined;
28
28
  } | {
29
- error?: undefined;
30
29
  count?: undefined;
31
30
  updated?: undefined;
31
+ error?: undefined;
32
32
  ok: boolean;
33
33
  }>>;
@@ -41,27 +41,27 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
41
41
  thumbsUpRate: number;
42
42
  avgEvalScore: number;
43
43
  } | {
44
- error?: undefined;
45
- ok?: undefined;
46
44
  summary: import("./types.js").TraceSummary;
47
45
  spans: import("./types.js").TraceSpan[];
48
46
  id?: undefined;
49
- } | {
50
47
  error?: undefined;
51
48
  ok?: undefined;
49
+ } | {
52
50
  summary?: undefined;
53
51
  spans?: undefined;
54
52
  id: string;
55
- } | {
53
+ error?: undefined;
56
54
  ok?: undefined;
55
+ } | {
57
56
  summary?: undefined;
58
57
  spans?: undefined;
59
58
  id?: undefined;
60
59
  error: any;
60
+ ok?: undefined;
61
61
  } | {
62
- error?: undefined;
63
62
  summary?: undefined;
64
63
  spans?: undefined;
65
64
  id?: undefined;
65
+ error?: undefined;
66
66
  ok: boolean;
67
67
  }>>;
@@ -15,6 +15,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
15
15
  error: string;
16
16
  ok?: undefined;
17
17
  } | {
18
- error?: undefined;
19
18
  ok: boolean;
19
+ error?: undefined;
20
20
  }>>;
@@ -51,8 +51,8 @@ export declare function handleDeleteResource(event: any): Promise<{
51
51
  error: string;
52
52
  ok?: undefined;
53
53
  } | {
54
- ok: boolean;
55
54
  error?: undefined;
55
+ ok: boolean;
56
56
  }>;
57
57
  /** POST /_agent-native/resources/upload — upload a file as a resource */
58
58
  export declare function handleUploadResource(event: any): Promise<import("./store.js").Resource | {
@@ -73,10 +73,10 @@ export declare function handleUploadResource(event: any): Promise<import("./stor
73
73
  runId: string | null;
74
74
  expiresAt: number | null;
75
75
  metadata: string | null;
76
+ error?: undefined;
76
77
  url: string;
77
78
  provider: string;
78
79
  storageSetupRequired?: undefined;
79
- error?: undefined;
80
80
  } | {
81
81
  error: string;
82
82
  storageSetupRequired: boolean;