@agent-native/core 0.7.19 → 0.7.20

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 (258) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  3. package/dist/agent/engine/builder-engine.js +45 -2
  4. package/dist/agent/engine/builder-engine.js.map +1 -1
  5. package/dist/agent/loop-settings.d.ts +37 -0
  6. package/dist/agent/loop-settings.d.ts.map +1 -0
  7. package/dist/agent/loop-settings.js +127 -0
  8. package/dist/agent/loop-settings.js.map +1 -0
  9. package/dist/agent/production-agent.d.ts +8 -0
  10. package/dist/agent/production-agent.d.ts.map +1 -1
  11. package/dist/agent/production-agent.js +268 -29
  12. package/dist/agent/production-agent.js.map +1 -1
  13. package/dist/agent/run-manager.d.ts.map +1 -1
  14. package/dist/agent/run-manager.js +76 -3
  15. package/dist/agent/run-manager.js.map +1 -1
  16. package/dist/agent/run-store.d.ts +1 -1
  17. package/dist/agent/run-store.d.ts.map +1 -1
  18. package/dist/agent/run-store.js +65 -2
  19. package/dist/agent/run-store.js.map +1 -1
  20. package/dist/agent/thread-data-builder.d.ts +3 -0
  21. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  22. package/dist/agent/thread-data-builder.js +52 -10
  23. package/dist/agent/thread-data-builder.js.map +1 -1
  24. package/dist/agent/tool-search.d.ts +37 -0
  25. package/dist/agent/tool-search.d.ts.map +1 -0
  26. package/dist/agent/tool-search.js +201 -0
  27. package/dist/agent/tool-search.js.map +1 -0
  28. package/dist/agent/types.d.ts +8 -1
  29. package/dist/agent/types.d.ts.map +1 -1
  30. package/dist/agent/types.js.map +1 -1
  31. package/dist/cli/create.d.ts.map +1 -1
  32. package/dist/cli/create.js +44 -9
  33. package/dist/cli/create.js.map +1 -1
  34. package/dist/cli/workspacify.d.ts +2 -0
  35. package/dist/cli/workspacify.d.ts.map +1 -1
  36. package/dist/cli/workspacify.js +34 -1
  37. package/dist/cli/workspacify.js.map +1 -1
  38. package/dist/client/AssistantChat.d.ts.map +1 -1
  39. package/dist/client/AssistantChat.js +277 -18
  40. package/dist/client/AssistantChat.js.map +1 -1
  41. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  42. package/dist/client/ConnectBuilderCard.js +1 -1
  43. package/dist/client/ConnectBuilderCard.js.map +1 -1
  44. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  45. package/dist/client/MultiTabAssistantChat.js +14 -6
  46. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  47. package/dist/client/NewWorkspaceAppFlow.d.ts +14 -0
  48. package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -0
  49. package/dist/client/NewWorkspaceAppFlow.js +200 -0
  50. package/dist/client/NewWorkspaceAppFlow.js.map +1 -0
  51. package/dist/client/PoweredByBadge.d.ts +10 -1
  52. package/dist/client/PoweredByBadge.d.ts.map +1 -1
  53. package/dist/client/PoweredByBadge.js +120 -8
  54. package/dist/client/PoweredByBadge.js.map +1 -1
  55. package/dist/client/agent-chat-adapter.d.ts +3 -5
  56. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  57. package/dist/client/agent-chat-adapter.js +26 -19
  58. package/dist/client/agent-chat-adapter.js.map +1 -1
  59. package/dist/client/agent-chat.d.ts.map +1 -1
  60. package/dist/client/agent-chat.js +15 -3
  61. package/dist/client/agent-chat.js.map +1 -1
  62. package/dist/client/analytics.d.ts +1 -1
  63. package/dist/client/analytics.d.ts.map +1 -1
  64. package/dist/client/analytics.js +141 -1
  65. package/dist/client/analytics.js.map +1 -1
  66. package/dist/client/builder-frame.d.ts +10 -0
  67. package/dist/client/builder-frame.d.ts.map +1 -0
  68. package/dist/client/builder-frame.js +94 -0
  69. package/dist/client/builder-frame.js.map +1 -0
  70. package/dist/client/composer/MentionPopover.d.ts.map +1 -1
  71. package/dist/client/composer/MentionPopover.js +5 -1
  72. package/dist/client/composer/MentionPopover.js.map +1 -1
  73. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  74. package/dist/client/composer/TiptapComposer.js +11 -6
  75. package/dist/client/composer/TiptapComposer.js.map +1 -1
  76. package/dist/client/error-format.d.ts +20 -1
  77. package/dist/client/error-format.d.ts.map +1 -1
  78. package/dist/client/error-format.js +53 -5
  79. package/dist/client/error-format.js.map +1 -1
  80. package/dist/client/index.d.ts +3 -1
  81. package/dist/client/index.d.ts.map +1 -1
  82. package/dist/client/index.js +3 -1
  83. package/dist/client/index.js.map +1 -1
  84. package/dist/client/onboarding/OnboardingPanel.d.ts.map +1 -1
  85. package/dist/client/onboarding/OnboardingPanel.js +88 -6
  86. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  87. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  88. package/dist/client/settings/SettingsPanel.js +145 -9
  89. package/dist/client/settings/SettingsPanel.js.map +1 -1
  90. package/dist/client/settings/useBuilderStatus.d.ts +13 -0
  91. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  92. package/dist/client/settings/useBuilderStatus.js +50 -9
  93. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  94. package/dist/client/sse-event-processor.d.ts +3 -0
  95. package/dist/client/sse-event-processor.d.ts.map +1 -1
  96. package/dist/client/sse-event-processor.js +88 -7
  97. package/dist/client/sse-event-processor.js.map +1 -1
  98. package/dist/client/tools/ToolsListPage.d.ts.map +1 -1
  99. package/dist/client/tools/ToolsListPage.js +16 -1
  100. package/dist/client/tools/ToolsListPage.js.map +1 -1
  101. package/dist/client/tools/ToolsSidebarSection.d.ts.map +1 -1
  102. package/dist/client/tools/ToolsSidebarSection.js +63 -8
  103. package/dist/client/tools/ToolsSidebarSection.js.map +1 -1
  104. package/dist/client/tools/tool-order.d.ts +7 -0
  105. package/dist/client/tools/tool-order.d.ts.map +1 -0
  106. package/dist/client/tools/tool-order.js +47 -0
  107. package/dist/client/tools/tool-order.js.map +1 -0
  108. package/dist/client/transcription/BuilderTranscriptionCta.d.ts.map +1 -1
  109. package/dist/client/transcription/BuilderTranscriptionCta.js +71 -6
  110. package/dist/client/transcription/BuilderTranscriptionCta.js.map +1 -1
  111. package/dist/client/use-send-to-agent-chat.d.ts.map +1 -1
  112. package/dist/client/use-send-to-agent-chat.js +11 -3
  113. package/dist/client/use-send-to-agent-chat.js.map +1 -1
  114. package/dist/client/useProductionAgent.d.ts.map +1 -1
  115. package/dist/client/useProductionAgent.js +1 -1
  116. package/dist/client/useProductionAgent.js.map +1 -1
  117. package/dist/db/client.d.ts.map +1 -1
  118. package/dist/db/client.js +5 -1
  119. package/dist/db/client.js.map +1 -1
  120. package/dist/deploy/build.d.ts +1 -0
  121. package/dist/deploy/build.d.ts.map +1 -1
  122. package/dist/deploy/build.js +4 -1
  123. package/dist/deploy/build.js.map +1 -1
  124. package/dist/oauth-tokens/index.d.ts +1 -1
  125. package/dist/oauth-tokens/index.d.ts.map +1 -1
  126. package/dist/oauth-tokens/index.js +1 -1
  127. package/dist/oauth-tokens/index.js.map +1 -1
  128. package/dist/oauth-tokens/store.d.ts.map +1 -1
  129. package/dist/oauth-tokens/store.js +6 -0
  130. package/dist/oauth-tokens/store.js.map +1 -1
  131. package/dist/observability/store.d.ts.map +1 -1
  132. package/dist/observability/store.js +19 -19
  133. package/dist/observability/store.js.map +1 -1
  134. package/dist/onboarding/default-steps.d.ts.map +1 -1
  135. package/dist/onboarding/default-steps.js +95 -61
  136. package/dist/onboarding/default-steps.js.map +1 -1
  137. package/dist/onboarding/plugin.d.ts.map +1 -1
  138. package/dist/onboarding/plugin.js +17 -8
  139. package/dist/onboarding/plugin.js.map +1 -1
  140. package/dist/org/migrations.js +2 -2
  141. package/dist/org/migrations.js.map +1 -1
  142. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  143. package/dist/scripts/agent-engines/list-agent-engines.js +2 -3
  144. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  145. package/dist/scripts/db/exec.d.ts +2 -1
  146. package/dist/scripts/db/exec.d.ts.map +1 -1
  147. package/dist/scripts/db/exec.js +264 -61
  148. package/dist/scripts/db/exec.js.map +1 -1
  149. package/dist/scripts/db/schema.d.ts.map +1 -1
  150. package/dist/scripts/db/schema.js +16 -4
  151. package/dist/scripts/db/schema.js.map +1 -1
  152. package/dist/scripts/dev/index.d.ts.map +1 -1
  153. package/dist/scripts/dev/index.js +36 -11
  154. package/dist/scripts/dev/index.js.map +1 -1
  155. package/dist/scripts/manage-agent-loop-settings.d.ts +7 -0
  156. package/dist/scripts/manage-agent-loop-settings.d.ts.map +1 -0
  157. package/dist/scripts/manage-agent-loop-settings.js +63 -0
  158. package/dist/scripts/manage-agent-loop-settings.js.map +1 -0
  159. package/dist/scripts/runner.d.ts.map +1 -1
  160. package/dist/scripts/runner.js +11 -0
  161. package/dist/scripts/runner.js.map +1 -1
  162. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  163. package/dist/server/agent-chat-plugin.js +60 -18
  164. package/dist/server/agent-chat-plugin.js.map +1 -1
  165. package/dist/server/app-url.d.ts +5 -4
  166. package/dist/server/app-url.d.ts.map +1 -1
  167. package/dist/server/app-url.js +8 -4
  168. package/dist/server/app-url.js.map +1 -1
  169. package/dist/server/auth.d.ts +8 -0
  170. package/dist/server/auth.d.ts.map +1 -1
  171. package/dist/server/auth.js +82 -29
  172. package/dist/server/auth.js.map +1 -1
  173. package/dist/server/better-auth-instance.d.ts.map +1 -1
  174. package/dist/server/better-auth-instance.js +16 -5
  175. package/dist/server/better-auth-instance.js.map +1 -1
  176. package/dist/server/builder-browser.d.ts +12 -0
  177. package/dist/server/builder-browser.d.ts.map +1 -1
  178. package/dist/server/builder-browser.js +36 -4
  179. package/dist/server/builder-browser.js.map +1 -1
  180. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  181. package/dist/server/core-routes-plugin.js +350 -53
  182. package/dist/server/core-routes-plugin.js.map +1 -1
  183. package/dist/server/credential-provider.d.ts +21 -3
  184. package/dist/server/credential-provider.d.ts.map +1 -1
  185. package/dist/server/credential-provider.js +51 -21
  186. package/dist/server/credential-provider.js.map +1 -1
  187. package/dist/server/google-oauth.d.ts +3 -0
  188. package/dist/server/google-oauth.d.ts.map +1 -1
  189. package/dist/server/google-oauth.js +27 -3
  190. package/dist/server/google-oauth.js.map +1 -1
  191. package/dist/server/index.d.ts +4 -3
  192. package/dist/server/index.d.ts.map +1 -1
  193. package/dist/server/index.js +4 -3
  194. package/dist/server/index.js.map +1 -1
  195. package/dist/server/schema-prompt.d.ts.map +1 -1
  196. package/dist/server/schema-prompt.js +2 -1
  197. package/dist/server/schema-prompt.js.map +1 -1
  198. package/dist/server/security-headers.d.ts +3 -0
  199. package/dist/server/security-headers.d.ts.map +1 -1
  200. package/dist/server/security-headers.js +7 -1
  201. package/dist/server/security-headers.js.map +1 -1
  202. package/dist/server/ssr-handler.d.ts.map +1 -1
  203. package/dist/server/ssr-handler.js +24 -4
  204. package/dist/server/ssr-handler.js.map +1 -1
  205. package/dist/templates/default/_gitignore +5 -1
  206. package/dist/templates/default/app/root.tsx +1 -0
  207. package/dist/templates/default/public/favicon.svg +3 -3
  208. package/dist/templates/default/public/icon-180.svg +3 -3
  209. package/dist/templates/default/public/icon-192.svg +3 -3
  210. package/dist/templates/default/public/icon-512.svg +3 -3
  211. package/dist/templates/workspace-core/AGENTS.md +23 -7
  212. package/dist/templates/workspace-core/package.json +2 -1
  213. package/dist/templates/workspace-core/src/credentials.ts +22 -11
  214. package/dist/templates/workspace-root/.env.example +7 -0
  215. package/dist/templates/workspace-root/README.md +6 -3
  216. package/dist/templates/workspace-root/_gitignore +3 -0
  217. package/dist/templates/workspace-root/package.json +3 -1
  218. package/dist/templates/workspace-root/scripts/workspace-dev.ts +410 -0
  219. package/dist/tools/actions.d.ts.map +1 -1
  220. package/dist/tools/actions.js +2 -0
  221. package/dist/tools/actions.js.map +1 -1
  222. package/dist/tools/html-shell.d.ts.map +1 -1
  223. package/dist/tools/html-shell.js +13 -1
  224. package/dist/tools/html-shell.js.map +1 -1
  225. package/dist/tools/store.d.ts.map +1 -1
  226. package/dist/tools/store.js +10 -10
  227. package/dist/tools/store.js.map +1 -1
  228. package/dist/tracking/providers.d.ts +1 -0
  229. package/dist/tracking/providers.d.ts.map +1 -1
  230. package/dist/tracking/providers.js +72 -0
  231. package/dist/tracking/providers.js.map +1 -1
  232. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  233. package/dist/vite/action-types-plugin.js +106 -9
  234. package/dist/vite/action-types-plugin.js.map +1 -1
  235. package/dist/vite/client.d.ts.map +1 -1
  236. package/dist/vite/client.js +67 -2
  237. package/dist/vite/client.js.map +1 -1
  238. package/docs/content/authentication.md +17 -13
  239. package/docs/content/deployment.md +11 -11
  240. package/docs/content/mcp-clients.md +2 -2
  241. package/docs/content/onboarding.md +32 -30
  242. package/docs/content/security.md +1 -1
  243. package/docs/content/tools.md +4 -0
  244. package/package.json +2 -2
  245. package/src/templates/default/_gitignore +5 -1
  246. package/src/templates/default/app/root.tsx +1 -0
  247. package/src/templates/default/public/favicon.svg +3 -3
  248. package/src/templates/default/public/icon-180.svg +3 -3
  249. package/src/templates/default/public/icon-192.svg +3 -3
  250. package/src/templates/default/public/icon-512.svg +3 -3
  251. package/src/templates/workspace-core/AGENTS.md +23 -7
  252. package/src/templates/workspace-core/package.json +2 -1
  253. package/src/templates/workspace-core/src/credentials.ts +22 -11
  254. package/src/templates/workspace-root/.env.example +7 -0
  255. package/src/templates/workspace-root/README.md +6 -3
  256. package/src/templates/workspace-root/_gitignore +3 -0
  257. package/src/templates/workspace-root/package.json +3 -1
  258. package/src/templates/workspace-root/scripts/workspace-dev.ts +410 -0
@@ -8,8 +8,10 @@ import { PROVIDER_TO_ENV } from "./engine/provider-env-vars.js";
8
8
  import { readAppState } from "../application-state/script-helpers.js";
9
9
  import { startRun, subscribeToRun, getActiveRunForThread, getActiveRunForThreadAsync, getRun, abortRun, } from "./run-manager.js";
10
10
  import { readBody } from "../server/h3-helpers.js";
11
- import { getRequestUserEmail } from "../server/request-context.js";
11
+ import { getRequestOrgId, getRequestUserEmail, } from "../server/request-context.js";
12
12
  import { isMcpToolAllowedForRequest } from "../mcp-client/visibility.js";
13
+ import { createToolSearchEntry, TOOL_SEARCH_ACTION_NAME, } from "./tool-search.js";
14
+ import { getDefaultMaxIterations, normalizeMaxIterations, readAgentLoopSettings, } from "./loop-settings.js";
13
15
  // Register built-in engines on first import
14
16
  registerBuiltinEngines();
15
17
  export { PROVIDER_TO_ENV };
@@ -129,7 +131,159 @@ export async function getOwnerActiveApiKey(ownerEmail) {
129
131
  export async function getOwnerAnthropicApiKey(ownerEmail) {
130
132
  return getOwnerApiKey("anthropic", ownerEmail);
131
133
  }
132
- const MAX_ITERATIONS = 100;
134
+ export const PLAN_MODE_SYSTEM_PROMPT = `## Plan Mode Active
135
+
136
+ You are in Plan mode. This turn is for research, clarification, and a proposed approach only.
137
+
138
+ Hard rules:
139
+ - Use only read-only tools. Do not edit files, write resources, run shell commands, mutate SQL rows, navigate the UI, send notifications, create jobs, create tools, call external agents, or change external systems.
140
+ - If a needed detail is unclear, ask a concise clarifying question before proposing a plan.
141
+ - When ready, present a concrete plan with the files/tools you expect to touch, the intended changes, validation steps, and notable risks.
142
+ - Do not treat approval as implicit while Plan mode is still active. Tell the user to switch to Act mode with the mode selector, Shift+Tab, or /act before implementation.`;
143
+ const PLAN_MODE_BLOCKED_READONLY_TOOLS = new Set([
144
+ "refresh-screen",
145
+ "set-search-params",
146
+ "set-url-path",
147
+ ]);
148
+ const PLAN_MODE_ALLOWED_ACTIONS = {
149
+ resources: ["list", "read"],
150
+ "chat-history": ["search"],
151
+ "agent-teams": ["status", "read-result", "list"],
152
+ "manage-jobs": ["list"],
153
+ "manage-automations": ["list-events", "list"],
154
+ "manage-notifications": ["list"],
155
+ "manage-progress": ["list"],
156
+ "manage-agent-engine": ["list"],
157
+ };
158
+ const PLAN_MODE_WEB_REQUEST_METHODS = new Set(["GET", "HEAD"]);
159
+ function getToolAction(name, args) {
160
+ const raw = args && typeof args === "object" && "action" in args
161
+ ? args.action
162
+ : undefined;
163
+ if (raw == null && name === "chat-history")
164
+ return "search";
165
+ return String(raw ?? "").toLowerCase();
166
+ }
167
+ function getWebRequestMethod(args) {
168
+ const raw = args && typeof args === "object" && "method" in args
169
+ ? args.method
170
+ : undefined;
171
+ return String(raw ?? "GET").toUpperCase();
172
+ }
173
+ function restrictActionEnum(parameters, allowedActions) {
174
+ if (!parameters)
175
+ return parameters;
176
+ const actionParam = parameters.properties.action;
177
+ if (!actionParam)
178
+ return parameters;
179
+ return {
180
+ ...parameters,
181
+ properties: {
182
+ ...parameters.properties,
183
+ action: {
184
+ ...actionParam,
185
+ enum: [...allowedActions],
186
+ },
187
+ },
188
+ };
189
+ }
190
+ function restrictWebRequestMethods(parameters) {
191
+ if (!parameters)
192
+ return parameters;
193
+ const methodParam = parameters.properties.method;
194
+ if (!methodParam)
195
+ return parameters;
196
+ return {
197
+ ...parameters,
198
+ properties: {
199
+ ...parameters.properties,
200
+ method: {
201
+ ...methodParam,
202
+ enum: [...PLAN_MODE_WEB_REQUEST_METHODS],
203
+ },
204
+ },
205
+ };
206
+ }
207
+ function planModeBlockedMessage(toolName, reason) {
208
+ return (`Plan mode blocked \`${toolName}\`` +
209
+ (reason ? ` (${reason})` : "") +
210
+ ". Switch to Act mode after the user approves the plan, then retry the action.");
211
+ }
212
+ export function isPlanModeToolCallAllowed(name, input, entry) {
213
+ if (PLAN_MODE_BLOCKED_READONLY_TOOLS.has(name))
214
+ return false;
215
+ if (name === "web-request") {
216
+ return PLAN_MODE_WEB_REQUEST_METHODS.has(getWebRequestMethod(input));
217
+ }
218
+ const allowedActions = PLAN_MODE_ALLOWED_ACTIONS[name];
219
+ if (allowedActions) {
220
+ return allowedActions.includes(getToolAction(name, input));
221
+ }
222
+ return entry.readOnly === true;
223
+ }
224
+ function createPlanModeGuardedAction(name, entry, allowedActions) {
225
+ return {
226
+ ...entry,
227
+ readOnly: true,
228
+ tool: {
229
+ ...entry.tool,
230
+ description: `${entry.tool.description}\n\nPlan mode: only these read-only actions are available: ` +
231
+ allowedActions.map((action) => `"${action}"`).join(", ") +
232
+ ".",
233
+ parameters: restrictActionEnum(entry.tool.parameters, allowedActions),
234
+ },
235
+ run: async (args, context) => {
236
+ const action = getToolAction(name, args);
237
+ if (!allowedActions.includes(action)) {
238
+ return planModeBlockedMessage(name, `action="${action || "(missing)"}"`);
239
+ }
240
+ return entry.run(args, context);
241
+ },
242
+ };
243
+ }
244
+ function createPlanModeWebRequestAction(entry) {
245
+ return {
246
+ ...entry,
247
+ readOnly: true,
248
+ tool: {
249
+ ...entry.tool,
250
+ description: `${entry.tool.description}\n\nPlan mode: only GET and HEAD requests are allowed.`,
251
+ parameters: restrictWebRequestMethods(entry.tool.parameters),
252
+ },
253
+ run: async (args, context) => {
254
+ const method = getWebRequestMethod(args);
255
+ if (!PLAN_MODE_WEB_REQUEST_METHODS.has(method)) {
256
+ return planModeBlockedMessage("web-request", `method="${method}"`);
257
+ }
258
+ return entry.run(args, context);
259
+ },
260
+ };
261
+ }
262
+ export function createPlanModeActionRegistry(actions) {
263
+ const filtered = {};
264
+ for (const [name, entry] of Object.entries(actions)) {
265
+ if (name === TOOL_SEARCH_ACTION_NAME)
266
+ continue;
267
+ if (PLAN_MODE_BLOCKED_READONLY_TOOLS.has(name))
268
+ continue;
269
+ const allowedActions = PLAN_MODE_ALLOWED_ACTIONS[name];
270
+ if (allowedActions) {
271
+ filtered[name] = createPlanModeGuardedAction(name, entry, allowedActions);
272
+ continue;
273
+ }
274
+ if (name === "web-request") {
275
+ filtered[name] = createPlanModeWebRequestAction(entry);
276
+ continue;
277
+ }
278
+ if (entry.readOnly === true) {
279
+ filtered[name] = entry;
280
+ }
281
+ }
282
+ if (actions[TOOL_SEARCH_ACTION_NAME]) {
283
+ filtered[TOOL_SEARCH_ACTION_NAME] = createToolSearchEntry(() => filtered);
284
+ }
285
+ return filtered;
286
+ }
133
287
  const MAX_RETRIES = 3;
134
288
  const RETRY_BASE_DELAY_MS = 2000;
135
289
  function generateRunId() {
@@ -157,11 +311,22 @@ function isRetryableError(err) {
157
311
  if (!(err instanceof Error))
158
312
  return false;
159
313
  const msg = err.message.toLowerCase();
160
- return (msg.includes("overloaded") ||
314
+ const code = err instanceof EngineError ? (err.errorCode ?? "").toLowerCase() : "";
315
+ return (code === "http_502" ||
316
+ code === "http_503" ||
317
+ code === "http_504" ||
318
+ code === "timeout" ||
319
+ msg.includes("overloaded") ||
161
320
  msg.includes("rate_limit") ||
162
321
  msg.includes("529") ||
322
+ msg.includes("502") ||
163
323
  msg.includes("503") ||
164
- msg.includes("too many requests"));
324
+ msg.includes("504") ||
325
+ msg.includes("too many requests") ||
326
+ msg.includes("timeout") ||
327
+ msg.includes("gateway timeout") ||
328
+ msg.includes("inactivity timeout") ||
329
+ msg.includes("too much time has passed without sending any data"));
165
330
  }
166
331
  /** Wait with exponential backoff, respecting abort signal */
167
332
  function retryDelay(attempt, signal) {
@@ -268,14 +433,34 @@ function enrichMessage(message, references) {
268
433
  * Convert ActionEntry registry to EngineTool array.
269
434
  */
270
435
  export function actionsToEngineTools(actions) {
271
- return Object.entries(actions).map(([name, entry]) => ({
272
- name,
273
- description: entry.tool.description,
274
- inputSchema: (entry.tool.parameters ?? {
275
- type: "object",
276
- properties: {},
277
- }),
278
- }));
436
+ const tools = [];
437
+ for (const [name, entry] of Object.entries(actions)) {
438
+ const inputSchema = normalizeToolInputSchema(entry.tool.parameters);
439
+ if (!inputSchema) {
440
+ console.warn(`[agent] Skipping tool "${name}" because its input schema is not an object.`);
441
+ continue;
442
+ }
443
+ tools.push({
444
+ name,
445
+ description: entry.tool.description,
446
+ inputSchema,
447
+ });
448
+ }
449
+ return tools;
450
+ }
451
+ function normalizeToolInputSchema(schema) {
452
+ if (!schema)
453
+ return { type: "object", properties: {} };
454
+ if (schema.type !== "object")
455
+ return null;
456
+ return {
457
+ ...schema,
458
+ type: "object",
459
+ properties: schema.properties && typeof schema.properties === "object"
460
+ ? schema.properties
461
+ : {},
462
+ required: Array.isArray(schema.required) ? schema.required : [],
463
+ };
279
464
  }
280
465
  /**
281
466
  * The core agent loop — calls the engine iteratively until no more tool calls.
@@ -291,12 +476,13 @@ export async function runAgentLoop(opts) {
291
476
  cacheWriteTokens: 0,
292
477
  model,
293
478
  };
479
+ const maxIterations = normalizeMaxIterations(opts.maxIterations, getDefaultMaxIterations());
294
480
  let iterations = 0;
295
481
  while (true) {
296
482
  if (signal.aborted)
297
483
  break;
298
- if (++iterations > MAX_ITERATIONS) {
299
- send({ type: "loop_limit" });
484
+ if (++iterations > maxIterations) {
485
+ send({ type: "loop_limit", maxIterations });
300
486
  break;
301
487
  }
302
488
  let assistantContent;
@@ -372,9 +558,7 @@ export async function runAgentLoop(opts) {
372
558
  const toolCallParts = assistantContent.filter((p) => p.type === "tool-call");
373
559
  if (toolCallParts.length === 0)
374
560
  break;
375
- // Run all tool calls in parallel — engines often return multiple tool-call
376
- // blocks in one turn. Running them concurrently saves wall-clock time.
377
- const toolResultParts = await Promise.all(toolCallParts.map(async (toolCall) => {
561
+ const runToolCall = async (toolCall) => {
378
562
  const actionEntry = actions[toolCall.name];
379
563
  if (!actionEntry) {
380
564
  const result = `Error: Unknown tool "${toolCall.name}"`;
@@ -397,6 +581,18 @@ export async function runAgentLoop(opts) {
397
581
  tool: toolCall.name,
398
582
  input: toolCall.input,
399
583
  });
584
+ if (opts.executionMode === "plan" &&
585
+ !isPlanModeToolCallAllowed(toolCall.name, toolCall.input, actionEntry)) {
586
+ const result = planModeBlockedMessage(toolCall.name);
587
+ send({ type: "tool_done", tool: toolCall.name, result });
588
+ return {
589
+ type: "tool-result",
590
+ toolCallId: toolCall.id,
591
+ toolName: toolCall.name,
592
+ content: result,
593
+ isError: true,
594
+ };
595
+ }
400
596
  const MAX_TOOL_RESULT_CHARS = 50_000;
401
597
  const TOOL_TIMEOUT_MS = 60_000;
402
598
  let result;
@@ -429,10 +625,14 @@ export async function runAgentLoop(opts) {
429
625
  if (!isError && actionEntry.readOnly !== true) {
430
626
  try {
431
627
  const { recordChange } = await import("../server/poll.js");
628
+ const owner = opts.ownerEmail ?? getRequestUserEmail() ?? undefined;
629
+ const orgId = opts.orgId ?? getRequestOrgId() ?? undefined;
432
630
  recordChange({
433
631
  source: "action",
434
632
  type: "change",
435
633
  key: toolCall.name,
634
+ ...(owner ? { owner } : {}),
635
+ ...(orgId ? { orgId } : {}),
436
636
  });
437
637
  }
438
638
  catch {
@@ -447,7 +647,24 @@ export async function runAgentLoop(opts) {
447
647
  content: result,
448
648
  ...(isError ? { isError } : {}),
449
649
  };
450
- }));
650
+ };
651
+ const hasMutatingToolCall = toolCallParts.some((toolCall) => {
652
+ const entry = actions[toolCall.name];
653
+ return entry && entry.readOnly !== true;
654
+ });
655
+ // Engines can emit several tool-call blocks in one turn. Keep read-only
656
+ // calls parallel for latency, but serialize any batch containing a mutating
657
+ // call so DB writes and UI refresh events preserve model order and do not
658
+ // interleave or partially race.
659
+ const toolResultParts = [];
660
+ if (hasMutatingToolCall) {
661
+ for (const toolCall of toolCallParts) {
662
+ toolResultParts.push(await runToolCall(toolCall));
663
+ }
664
+ }
665
+ else {
666
+ toolResultParts.push(...(await Promise.all(toolCallParts.map(runToolCall))));
667
+ }
451
668
  messages.push({ role: "user", content: toolResultParts });
452
669
  }
453
670
  send({ type: "done" });
@@ -463,9 +680,9 @@ export function createProductionAgentHandler(options) {
463
680
  // the settings UI) show up to the LLM without a process restart. MCP tools
464
681
  // are also scope-filtered per request — a user-scope server added by Alice
465
682
  // must not appear in Bob's tool list in a shared-process deployment.
466
- const getEngineTools = () => {
683
+ const getEngineTools = (actions = resolvedActions) => {
467
684
  const filtered = {};
468
- for (const [name, entry] of Object.entries(resolvedActions)) {
685
+ for (const [name, entry] of Object.entries(actions)) {
469
686
  if (name.startsWith("mcp__") && !isMcpToolAllowedForRequest(name)) {
470
687
  continue;
471
688
  }
@@ -487,6 +704,7 @@ export function createProductionAgentHandler(options) {
487
704
  return { error: "Invalid request body" };
488
705
  }
489
706
  const { message, history = [], references = [], threadId, attachments, model: requestModel, engine: requestEngine, } = body;
707
+ const requestMode = body.mode === "plan" ? "plan" : "act";
490
708
  if (!message) {
491
709
  setResponseStatus(event, 400);
492
710
  return { error: "message is required" };
@@ -578,6 +796,10 @@ export function createProductionAgentHandler(options) {
578
796
  // the DB or invokes an action; running them sequentially was the
579
797
  // single biggest contributor to pre-LLM latency.
580
798
  const enrichedMessage = enrichMessage(message, references);
799
+ const loopSettingsPromise = readAgentLoopSettings({
800
+ userEmail: ownerEmail ?? getRequestUserEmail() ?? null,
801
+ orgId: getRequestOrgId() ?? null,
802
+ }).catch(() => readAgentLoopSettings({}));
581
803
  let systemPromptError = null;
582
804
  const systemPromptPromise = (async () => {
583
805
  try {
@@ -732,12 +954,13 @@ export function createProductionAgentHandler(options) {
732
954
  }
733
955
  return filesContext;
734
956
  })();
735
- const [systemPrompt, screenBlock, urlBlock, selectionBlock, filesContext] = await Promise.all([
957
+ const [systemPrompt, screenBlock, urlBlock, selectionBlock, filesContext, loopSettings,] = await Promise.all([
736
958
  systemPromptPromise,
737
959
  screenContextPromise,
738
960
  urlContextPromise,
739
961
  selectionContextPromise,
740
962
  filesContextPromise,
963
+ loopSettingsPromise,
741
964
  ]);
742
965
  if (systemPromptError) {
743
966
  setResponseHeader(event, "Content-Type", "text/event-stream");
@@ -752,11 +975,21 @@ export function createProductionAgentHandler(options) {
752
975
  });
753
976
  }
754
977
  const screenContext = screenBlock + urlBlock + selectionBlock;
978
+ const requestActions = requestMode === "plan"
979
+ ? createPlanModeActionRegistry(resolvedActions)
980
+ : resolvedActions;
981
+ const requestTools = getEngineTools(requestActions);
982
+ const requestSystemPrompt = requestMode === "plan"
983
+ ? `${systemPrompt}\n\n${PLAN_MODE_SYSTEM_PROMPT}`
984
+ : systemPrompt;
755
985
  // Pre-compute agent references for A2A resolution inside the run
756
986
  const agentRefs = references.filter((r) => r.type === "agent");
757
987
  const customAgentRefs = references.filter((r) => r.type === "custom-agent");
988
+ const planModeAgentNote = requestMode === "plan" && agentRefs.length > 0
989
+ ? "\n\n<plan-mode-note>Connected external agent mentions were not called because Plan mode is read-only. Mention that they can be called after the user switches to Act mode if the plan needs them.</plan-mode-note>"
990
+ : "";
758
991
  const userContent = buildUserContentWithAttachments({
759
- text: enrichedMessage + screenContext + filesContext,
992
+ text: enrichedMessage + screenContext + filesContext + planModeAgentNote,
760
993
  attachments,
761
994
  });
762
995
  const messages = [
@@ -804,7 +1037,7 @@ export function createProductionAgentHandler(options) {
804
1037
  if (!profile) {
805
1038
  throw new Error("Profile not found");
806
1039
  }
807
- const profilePrompt = `${systemPrompt}\n\n<custom-agent-profile name="${profile.name}" path="${profile.path}">\n` +
1040
+ const profilePrompt = `${requestSystemPrompt}\n\n<custom-agent-profile name="${profile.name}" path="${profile.path}">\n` +
808
1041
  (profile.description ? `${profile.description}\n\n` : "") +
809
1042
  `${profile.instructions}\n</custom-agent-profile>`;
810
1043
  let responseText = "";
@@ -812,7 +1045,7 @@ export function createProductionAgentHandler(options) {
812
1045
  engine,
813
1046
  model: profile.model ?? model,
814
1047
  systemPrompt: profilePrompt,
815
- tools: getEngineTools(),
1048
+ tools: requestTools,
816
1049
  messages: [
817
1050
  {
818
1051
  role: "user",
@@ -821,7 +1054,7 @@ export function createProductionAgentHandler(options) {
821
1054
  ],
822
1055
  },
823
1056
  ],
824
- actions: resolvedActions,
1057
+ actions: requestActions,
825
1058
  send: (event) => {
826
1059
  if (event.type === "text") {
827
1060
  responseText += event.text;
@@ -834,6 +1067,8 @@ export function createProductionAgentHandler(options) {
834
1067
  },
835
1068
  signal,
836
1069
  providerOptions: options.providerOptions,
1070
+ executionMode: requestMode,
1071
+ maxIterations: loopSettings.maxIterations,
837
1072
  });
838
1073
  // Attribute custom-agent sub-calls under their own label
839
1074
  // so the Usage panel separates them from the main chat.
@@ -889,7 +1124,7 @@ export function createProductionAgentHandler(options) {
889
1124
  }
890
1125
  }
891
1126
  // Resolve connected agent @-mentions via A2A calls.
892
- if (agentRefs.length > 0) {
1127
+ if (agentRefs.length > 0 && requestMode !== "plan") {
893
1128
  const { A2AClient, callAgent } = await import("../a2a/client.js");
894
1129
  const results = await Promise.allSettled(agentRefs.map(async (ref) => {
895
1130
  send({
@@ -1003,13 +1238,17 @@ export function createProductionAgentHandler(options) {
1003
1238
  const agentLoopOpts = {
1004
1239
  engine,
1005
1240
  model: effectiveModel,
1006
- systemPrompt,
1007
- tools: getEngineTools(),
1241
+ systemPrompt: requestSystemPrompt,
1242
+ tools: requestTools,
1008
1243
  messages,
1009
- actions: resolvedActions,
1244
+ actions: requestActions,
1010
1245
  send,
1011
1246
  signal,
1247
+ ownerEmail,
1248
+ orgId: getRequestOrgId() ?? null,
1012
1249
  providerOptions: options.providerOptions,
1250
+ executionMode: requestMode,
1251
+ maxIterations: loopSettings.maxIterations,
1013
1252
  };
1014
1253
  let loopUsage;
1015
1254
  let instrumented = false;