@agent-native/core 0.203.0 → 0.203.1-nightly-20261007183952

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 (50) hide show
  1. package/dist/action.d.ts +34 -0
  2. package/dist/agent/clip-text.d.ts +4 -0
  3. package/dist/agent/clip-text.js +17 -0
  4. package/dist/agent/follow-up-suggestions.d.ts +16 -2
  5. package/dist/agent/follow-up-suggestions.js +69 -1
  6. package/dist/agent/production-agent.d.ts +20 -2
  7. package/dist/agent/production-agent.js +322 -132
  8. package/dist/agent/provider-model-selection.d.ts +21 -2
  9. package/dist/agent/provider-model-selection.js +19 -2
  10. package/dist/agent/run-loop-with-resume.js +2 -16
  11. package/dist/agent/tool-search.d.ts +31 -1
  12. package/dist/agent/tool-search.js +196 -15
  13. package/dist/agent/write-receipts.d.ts +27 -0
  14. package/dist/agent/write-receipts.js +181 -0
  15. package/dist/automation/index.d.ts +1 -1
  16. package/dist/collab/awareness.d.ts +2 -2
  17. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  18. package/dist/observability/routes.d.ts +6 -6
  19. package/dist/observability/trace-error.d.ts +5 -3
  20. package/dist/observability/trace-error.js +29 -4
  21. package/dist/observability/traces.js +6 -0
  22. package/dist/progress/routes.d.ts +1 -1
  23. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  24. package/dist/resources/handlers.d.ts +1 -1
  25. package/dist/secrets/routes.d.ts +11 -11
  26. package/dist/server/action-routes.js +3 -0
  27. package/dist/server/agent-chat/prompt-resources.d.ts +13 -0
  28. package/dist/server/agent-chat/prompt-resources.js +21 -12
  29. package/dist/server/agent-chat-plugin.js +25 -9
  30. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  31. package/dist/server/agent-engine-default-model-route.d.ts +2 -2
  32. package/dist/server/email-auth-links.js +98 -2
  33. package/dist/server/embed-route.js +2 -1
  34. package/dist/server/index.d.ts +3 -3
  35. package/dist/server/index.js +3 -3
  36. package/dist/server/realtime-voice.js +2 -4
  37. package/dist/shared/embed-auth.d.ts +1 -0
  38. package/dist/shared/embed-auth.js +1 -0
  39. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  40. package/dist/templates/chat/.agents/skills/actions/SKILL.md +25 -0
  41. package/dist/templates/default/.agents/skills/actions/SKILL.md +25 -0
  42. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -39
  43. package/dist/templates/headless/.agents/skills/actions/SKILL.md +25 -0
  44. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +25 -0
  45. package/package.json +3 -3
  46. package/src/templates/chat/.agents/skills/actions/SKILL.md +25 -0
  47. package/src/templates/default/.agents/skills/actions/SKILL.md +25 -0
  48. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +26 -39
  49. package/src/templates/headless/.agents/skills/actions/SKILL.md +25 -0
  50. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +25 -0
@@ -27,38 +27,38 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
27
27
  thumbsUpRate: number;
28
28
  avgEvalScore: number;
29
29
  } | {
30
+ ok?: undefined;
30
31
  error: string;
31
32
  message: string;
32
33
  summary?: undefined;
33
34
  spans?: undefined;
34
35
  id?: undefined;
35
- ok?: undefined;
36
36
  } | {
37
+ error?: undefined;
38
+ ok?: undefined;
37
39
  message?: undefined;
38
40
  summary: import("./types.js").TraceSummary;
39
41
  spans: import("./types.js").TraceSpan[];
40
42
  id?: undefined;
43
+ } | {
41
44
  error?: undefined;
42
45
  ok?: undefined;
43
- } | {
44
46
  message?: undefined;
45
47
  summary?: undefined;
46
48
  spans?: undefined;
47
49
  id: string;
48
- error?: undefined;
49
- ok?: undefined;
50
50
  } | {
51
+ ok?: undefined;
51
52
  message?: undefined;
52
53
  summary?: undefined;
53
54
  spans?: undefined;
54
55
  id?: undefined;
55
56
  error: any;
56
- ok?: undefined;
57
57
  } | {
58
+ error?: undefined;
58
59
  message?: undefined;
59
60
  summary?: undefined;
60
61
  spans?: undefined;
61
62
  id?: undefined;
62
- error?: undefined;
63
63
  ok: boolean;
64
64
  }>>;
@@ -15,8 +15,10 @@ export declare function sanitizeToolErrorMessage(value: string): string;
15
15
  * captureToolResults off: the first non-blank line, bounded. It is stored
16
16
  * without the owner opting in, so beyond credentials (a quoted secret can span
17
17
  * lines, so redact the whole text first) it also drops the emails and opaque
18
- * ids a failing tool echoes back. Never empty — an empty `error_message` reads
19
- * as "no reason was captured", which is a different fact from "the tool gave no
20
- * text".
18
+ * ids a failing tool echoes back. The `Error running <tool>: ` prefix is
19
+ * dropped so signatures group by cause, and a JSON `{ error, message }` result
20
+ * is summarized as `error: message` rather than its opening brace. Never
21
+ * empty — an empty `error_message` reads as "no reason was captured", which is
22
+ * a different fact from "the tool gave no text".
21
23
  */
22
24
  export declare function toolErrorSignature(value: unknown): string;
@@ -58,19 +58,44 @@ const EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
58
58
  // 16+ characters of an id alphabet with a digit in them: Google file ids, Gmail
59
59
  // message ids, ObjectIds, session ids. Plain identifiers have no digit.
60
60
  const OPAQUE_ID_PATTERN = /\b(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]{16,}\b/g;
61
+ const TOOL_ERROR_PREFIX_PATTERN = /^Error running [^\s:]+:\s*/;
61
62
  /**
62
63
  * The part of a tool failure that is always recorded, even with
63
64
  * captureToolResults off: the first non-blank line, bounded. It is stored
64
65
  * without the owner opting in, so beyond credentials (a quoted secret can span
65
66
  * lines, so redact the whole text first) it also drops the emails and opaque
66
- * ids a failing tool echoes back. Never empty — an empty `error_message` reads
67
- * as "no reason was captured", which is a different fact from "the tool gave no
68
- * text".
67
+ * ids a failing tool echoes back. The `Error running <tool>: ` prefix is
68
+ * dropped so signatures group by cause, and a JSON `{ error, message }` result
69
+ * is summarized as `error: message` rather than its opening brace. Never
70
+ * empty — an empty `error_message` reads as "no reason was captured", which is
71
+ * a different fact from "the tool gave no text".
69
72
  */
70
73
  export function toolErrorSignature(value) {
71
- const redacted = redactToolErrorMessage(typeof value === "string" ? value : "")
74
+ const text = (typeof value === "string" ? value : "")
75
+ .trim()
76
+ .replace(TOOL_ERROR_PREFIX_PATTERN, "");
77
+ const redacted = redactToolErrorMessage(jsonErrorSummary(text) ?? text)
72
78
  .replace(EMAIL_PATTERN, "[email]")
73
79
  .replace(OPAQUE_ID_PATTERN, "[id]");
74
80
  const firstLine = redacted.split(/\r?\n/).find((line) => line.trim());
75
81
  return boundToolErrorMessage(firstLine?.trim() || "Tool failed with no error text");
76
82
  }
83
+ /** `error: message` for a JSON `{ error, message }` result; `undefined` when the text is not one. */
84
+ function jsonErrorSummary(text) {
85
+ if (!text.startsWith("{"))
86
+ return undefined;
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(text);
90
+ // coercion-ok: unparseable text is not a JSON error result; the caller still records its first line
91
+ }
92
+ catch {
93
+ return undefined;
94
+ }
95
+ const { error, message } = (parsed ?? {});
96
+ if (typeof error !== "string" || !error.trim())
97
+ return undefined;
98
+ return typeof message === "string" && message.trim()
99
+ ? `${error}: ${message}`
100
+ : error;
101
+ }
@@ -1230,6 +1230,10 @@ export async function instrumentAgentLoop(opts) {
1230
1230
  run_id: runId,
1231
1231
  thread_id: threadId,
1232
1232
  llm_calls: llmCallCount || undefined,
1233
+ follow_up_ms: usage?.followUpMs,
1234
+ follow_up_input_tokens: usage?.followUpInputTokens,
1235
+ receipt_unverified_count: usage?.receiptUnverifiedCount,
1236
+ receipt_changed_false_count: usage?.receiptChangedFalseCount,
1233
1237
  tool_calls: toolCallCount,
1234
1238
  successful_tools: successfulTools,
1235
1239
  failed_tools: failedTools,
@@ -1413,6 +1417,8 @@ export async function instrumentAgentLoop(opts) {
1413
1417
  errorMessage: redactCapturedError(errorMessage),
1414
1418
  attributes: {
1415
1419
  "agent.llm_calls": llmCallCount,
1420
+ "agent.follow_up_ms": usage?.followUpMs,
1421
+ "agent.follow_up_input_tokens": usage?.followUpInputTokens,
1416
1422
  "agent.tool_calls": toolCallCount,
1417
1423
  "agent.successful_tools": successfulTools,
1418
1424
  "agent.failed_tools": failedTools,
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- ok: boolean;
6
5
  error?: undefined;
6
+ ok: boolean;
7
7
  }>>;
@@ -311,6 +311,7 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
311
311
  notes?: string | undefined;
312
312
  scope?: "org" | "user" | undefined;
313
313
  }, {
314
+ id?: undefined;
314
315
  deleted?: undefined;
315
316
  found?: undefined;
316
317
  providers: {
@@ -325,17 +326,16 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
325
326
  count: number;
326
327
  provider?: undefined;
327
328
  registered?: undefined;
328
- id?: undefined;
329
329
  label?: undefined;
330
330
  message?: undefined;
331
331
  } | {
332
+ id?: undefined;
332
333
  deleted?: undefined;
333
334
  count?: undefined;
334
335
  providers?: undefined;
335
336
  found: boolean;
336
337
  provider: import("../custom-registry.js").CustomProviderConfig;
337
338
  registered?: undefined;
338
- id?: undefined;
339
339
  label?: undefined;
340
340
  message?: undefined;
341
341
  } | {
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
39
39
  error: string;
40
40
  }>;
41
41
  export declare function handleDeleteResource(event: any): Promise<{
42
- error: string;
43
42
  ok?: undefined;
43
+ error: string;
44
44
  } | {
45
45
  error?: undefined;
46
46
  ok: boolean;
@@ -58,47 +58,47 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
58
58
  error: string;
59
59
  errorCode?: undefined;
60
60
  retryable?: undefined;
61
- status?: undefined;
62
61
  ok?: undefined;
62
+ status?: undefined;
63
63
  } | {
64
64
  error: string;
65
65
  errorCode: string;
66
66
  retryable?: undefined;
67
- status?: undefined;
68
67
  ok?: undefined;
68
+ status?: undefined;
69
69
  } | {
70
70
  error: string;
71
71
  errorCode: string;
72
72
  retryable: boolean;
73
- status?: undefined;
74
73
  ok?: undefined;
74
+ status?: undefined;
75
75
  } | {
76
+ error?: undefined;
76
77
  errorCode?: undefined;
77
78
  retryable?: undefined;
78
79
  ok: boolean;
79
80
  status: string;
80
- error?: undefined;
81
81
  } | {
82
+ ok?: undefined;
82
83
  error: string;
83
84
  removed?: undefined;
84
- ok?: undefined;
85
85
  } | {
86
+ error?: undefined;
86
87
  ok: boolean;
87
88
  removed: boolean;
88
- error?: undefined;
89
89
  }>>;
90
90
  export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
91
+ ok?: undefined;
91
92
  error: string;
92
93
  note?: undefined;
93
- ok?: undefined;
94
94
  } | {
95
+ error?: undefined;
95
96
  ok: boolean;
96
97
  note?: undefined;
97
- error?: undefined;
98
98
  } | {
99
+ error?: undefined;
99
100
  ok: boolean;
100
101
  note: string;
101
- error?: undefined;
102
102
  } | {
103
103
  note?: undefined;
104
104
  ok: boolean;
@@ -126,11 +126,11 @@ export declare function createAdHocSecretHandler(): import("h3").EventHandlerWit
126
126
  } | {
127
127
  error: string;
128
128
  } | {
129
+ error?: undefined;
129
130
  ok: boolean;
130
131
  key: string;
131
- error?: undefined;
132
132
  } | {
133
+ error?: undefined;
133
134
  ok: boolean;
134
135
  removed: boolean;
135
- error?: undefined;
136
136
  }>>;
@@ -750,6 +750,9 @@ function mountActionRoutesInternal(nitroApp, actions, options) {
750
750
  appId: options?.appId,
751
751
  caller,
752
752
  requestHeaders: event.headers,
753
+ ...(directoryWidgetReadAllowed
754
+ ? { mcpDirectoryWidgetReadOnly: true }
755
+ : {}),
753
756
  ...(event.req?.signal ? { signal: event.req.signal } : {}),
754
757
  actionName: name,
755
758
  ...(resolvedCaller?.delegationJti
@@ -39,6 +39,15 @@ export interface PromptResourceManifestSection {
39
39
  sourceRef?: ContextManifestSourceRef;
40
40
  }
41
41
  export declare function promptResourceManifestSections(prompt: string): PromptResourceManifestSection[];
42
+ /** The compact prompt's skills index. `skillReadTool` is the tool the request
43
+ * registry actually has for reading a skill; with none, the index is dropped
44
+ * because it would send the model to a tool it cannot call. */
45
+ export declare function buildCompactSkillsSummary(skills: ReadonlyArray<{
46
+ meta: {
47
+ name: string;
48
+ description?: string;
49
+ };
50
+ }>, skillReadTool: string | null): string | null;
42
51
  /** @internal exported for unit tests only */
43
52
  export declare function promptResourceBlock(input: {
44
53
  name: string;
@@ -104,4 +113,8 @@ export declare function preloadJevContextForPrompt(options: {
104
113
  */
105
114
  export declare function loadResourcesForPrompt(owner: string, compact?: boolean, selfAppId?: string, orgId?: string | null, opts?: {
106
115
  disabledFrameworkGroups?: ReadonlySet<FrameworkToolGroup>;
116
+ /** The tool the compact skills summary sends the model to for skill text,
117
+ * or `null` when the request registry has none. Omitted means
118
+ * `docs-search`, the framework default. */
119
+ skillReadTool?: string | null;
107
120
  }): Promise<string>;
@@ -275,6 +275,24 @@ function ensureSentence(value) {
275
275
  return "";
276
276
  return /[.!?]$/.test(trimmed) ? trimmed : `${trimmed}.`;
277
277
  }
278
+ /** The compact prompt's skills index. `skillReadTool` is the tool the request
279
+ * registry actually has for reading a skill; with none, the index is dropped
280
+ * because it would send the model to a tool it cannot call. */
281
+ export function buildCompactSkillsSummary(skills, skillReadTool) {
282
+ if (skills.length === 0 || !skillReadTool)
283
+ return null;
284
+ const listedSkills = skills.slice(0, PROMPT_SKILL_SUMMARY_LIMIT);
285
+ const lines = listedSkills.map((s) => {
286
+ const description = s.meta.description?.trim()
287
+ ? ` - ${ensureSentence(compactPromptLine(s.meta.description, PROMPT_SUMMARY_DESCRIPTION_MAX_CHARS))}`
288
+ : "";
289
+ return `- \`${s.meta.name}\`${description} Read with \`${skillReadTool} --slug "${skillDocsSlug(s.meta.name)}"\` before starting a task it applies to; reuse that page for subsequent steps in this turn.`;
290
+ });
291
+ if (skills.length > listedSkills.length) {
292
+ lines.push(`- ...${skills.length - listedSkills.length} more codebase skills. Use \`${skillReadTool} --query "<topic>"\` to discover the relevant one.`);
293
+ }
294
+ return `<skills-summary>\nCodebase skills bundled from \`.agents/skills/\` (or legacy \`.agent/skills/\`) are available as ${skillReadTool} pages. Do not use MCP resource reads for these skills. Read each relevant page once per turn and reuse it; do not repeat an equivalent ${skillReadTool} lookup unless the page or question is different.\n\n${lines.join("\n")}\n</skills-summary>`;
295
+ }
278
296
  function escapeXmlAttribute(value) {
279
297
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
280
298
  }
@@ -1119,6 +1137,7 @@ export async function preloadJevContextForPrompt(options) {
1119
1137
  */
1120
1138
  export async function loadResourcesForPrompt(owner, compact = false, selfAppId, orgId = getRequestOrgId() ?? null, opts) {
1121
1139
  await ensurePersonalDefaults(owner);
1140
+ const skillReadTool = opts?.skillReadTool === undefined ? "docs-search" : opts.skillReadTool;
1122
1141
  const sections = [];
1123
1142
  const addSection = (content, governance = "inherited") => {
1124
1143
  if (content?.trim())
@@ -1163,18 +1182,8 @@ export async function loadResourcesForPrompt(owner, compact = false, selfAppId,
1163
1182
  const skillsBlock = generateSkillsPromptBlock(bundle, runtimeSkills);
1164
1183
  addSection(skillsBlock);
1165
1184
  }
1166
- else if (runtimeSkills.length > 0) {
1167
- const listedSkills = runtimeSkills.slice(0, PROMPT_SKILL_SUMMARY_LIMIT);
1168
- const lines = listedSkills.map((s) => {
1169
- const description = s.meta.description?.trim()
1170
- ? ` - ${ensureSentence(compactPromptLine(s.meta.description, PROMPT_SUMMARY_DESCRIPTION_MAX_CHARS))}`
1171
- : "";
1172
- return `- \`${s.meta.name}\`${description} Read with \`docs-search --slug "${skillDocsSlug(s.meta.name)}"\` before starting a task it applies to; reuse that page for subsequent steps in this turn.`;
1173
- });
1174
- if (runtimeSkills.length > listedSkills.length) {
1175
- lines.push(`- ...${runtimeSkills.length - listedSkills.length} more codebase skills. Use \`docs-search --query "<topic>"\` to discover the relevant one.`);
1176
- }
1177
- addSection(`<skills-summary>\nCodebase skills bundled from \`.agents/skills/\` (or legacy \`.agent/skills/\`) are available as docs-search pages. Do not use MCP resource reads for these skills. Read each relevant page once per turn and reuse it; do not repeat an equivalent docs-search lookup unless the page or question is different.\n\n${lines.join("\n")}\n</skills-summary>`);
1185
+ else {
1186
+ addSection(buildCompactSkillsSummary(runtimeSkills, skillReadTool));
1178
1187
  }
1179
1188
  const workspaceOwner = workspaceResourceOwner(orgId);
1180
1189
  const workspaceAgents = await loadAgentsResourceForPrompt(workspaceOwner, "workspace", promptResourceMaxChars, orgId);
@@ -405,8 +405,13 @@ async function resolveResourceOrgId(event, resolveOrgId) {
405
405
  const resolved = resolveOrgId ? await resolveOrgId(event) : undefined;
406
406
  return resolved === undefined ? getRequestOrgId() : resolved;
407
407
  }
408
+ // Lean runs never receive the compact framework prompt, so the batching rule
409
+ // it carries has to ride the run policy instead.
410
+ const LEAN_PARALLEL_READS_NOTE = "\n\nWhen several independent reads are needed, emit them in the same step.";
408
411
  export function buildLeanRunPolicyPrompt(codeEditingSurfaceRestriction, prodCodeExecPromptNote) {
409
- return codeEditingSurfaceRestriction + prodCodeExecPromptNote;
412
+ return (LEAN_PARALLEL_READS_NOTE +
413
+ codeEditingSurfaceRestriction +
414
+ prodCodeExecPromptNote);
410
415
  }
411
416
  export function filterPromptActionsToSurface(actions, allowedActionNames) {
412
417
  if (!allowedActionNames)
@@ -851,6 +856,16 @@ export function createAgentChatPlugin(options) {
851
856
  const docsScripts = frameworkTools.isEnabled("docs")
852
857
  ? await createDocsScriptEntries()
853
858
  : {};
859
+ // The compact prompt sends the model to `docs-search` for skill text, so
860
+ // it is the one docs tool the lean registry keeps (`framework-search`
861
+ // stays out of the first request). Every registry carries it through
862
+ // `docsScripts` or `skillReadScripts`, which is why one resolved name
863
+ // serves every prompt site.
864
+ const docsSearchEntry = docsScripts["docs-search"];
865
+ const skillReadScripts = docsSearchEntry
866
+ ? { "docs-search": docsSearchEntry }
867
+ : {};
868
+ const skillReadTool = docsSearchEntry ? "docs-search" : null;
854
869
  const databaseToolsMode = normalizeDatabaseToolsMode(frameworkTools.database);
855
870
  const databaseToolsEnabled = databaseToolsMode !== "off";
856
871
  const databaseWriteToolsEnabled = databaseToolsMode === "write";
@@ -1843,7 +1858,7 @@ export function createAgentChatPlugin(options) {
1843
1858
  const devActive = isDevMode();
1844
1859
  // Build the same system prompt the interactive agent uses
1845
1860
  const owner = userEmail;
1846
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
1861
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
1847
1862
  const schemaBlock = lazyContext
1848
1863
  ? ""
1849
1864
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -2468,7 +2483,7 @@ export function createAgentChatPlugin(options) {
2468
2483
  // catalog on its very first request, undermining the compact MCP
2469
2484
  // catalog this surface is supposed to keep external callers on.
2470
2485
  const mcpToolSurface = createA2AEngineToolSurface(actionsToEngineTools(mcpActions), effectiveInitialToolNames);
2471
- const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
2486
+ const resources = await loadResourcesForPrompt(SHARED_OWNER, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
2472
2487
  const schemaBlock = lazyContext
2473
2488
  ? ""
2474
2489
  : await buildSchemaBlock(SHARED_OWNER, databaseToolsMode);
@@ -3109,6 +3124,7 @@ export function createAgentChatPlugin(options) {
3109
3124
  const leanActionEntries = {
3110
3125
  ...templateScripts,
3111
3126
  ...resourceScripts,
3127
+ ...skillReadScripts,
3112
3128
  ...workspaceFileActions,
3113
3129
  ...refreshScreenTool,
3114
3130
  ...urlTools,
@@ -3454,7 +3470,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3454
3470
  // so everything after it can change without a cache write.
3455
3471
  if (leanPrompt) {
3456
3472
  const leanRunPolicyPrompt = buildLeanRunPolicyPrompt(codeEditingSurfaceRestriction, `${requestProdCodeExecPromptNote}${hostedHarnessPromptNote ? `\n\n${hostedHarnessPromptNote}` : ""}`);
3457
- const resources = await loadResourcesForPrompt(owner, true, options?.appId, undefined, { disabledFrameworkGroups });
3473
+ const resources = await loadResourcesForPrompt(owner, true, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
3458
3474
  await emitContextXraySystemSections(event, {
3459
3475
  frameworkPrompt: requestLeanPrompt.slice(0, Math.max(0, requestLeanPrompt.length - requestLeanActionsPrompt.length)),
3460
3476
  actionsPrompt: requestLeanActionsPrompt,
@@ -3474,7 +3490,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3474
3490
  runtimeContext,
3475
3491
  }));
3476
3492
  }
3477
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
3493
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
3478
3494
  // In lazy context mode, skip embedding the full schema. When database
3479
3495
  // tools are enabled the agent can call `db-schema` on demand.
3480
3496
  const schemaBlock = lazyContext
@@ -3756,7 +3772,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3756
3772
  // cached prompt prefix as possible. See the prod handler above
3757
3773
  // for the same pattern.
3758
3774
  if (leanPrompt) {
3759
- const resources = await loadResourcesForPrompt(owner, true, options?.appId, undefined, { disabledFrameworkGroups });
3775
+ const resources = await loadResourcesForPrompt(owner, true, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
3760
3776
  await emitContextXraySystemSections(event, {
3761
3777
  frameworkPrompt: requestLeanPrompt.slice(0, Math.max(0, requestLeanPrompt.length - requestLeanActionsPrompt.length)),
3762
3778
  actionsPrompt: requestLeanActionsPrompt,
@@ -3773,7 +3789,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
3773
3789
  runtimeContext,
3774
3790
  }));
3775
3791
  }
3776
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
3792
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
3777
3793
  const schemaBlock = lazyContext || !databaseToolsEnabled
3778
3794
  ? ""
3779
3795
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -6261,7 +6277,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6261
6277
  const schedulerDeps = {
6262
6278
  getActions: getBackgroundActionEntries,
6263
6279
  getSystemPrompt: async (owner) => {
6264
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
6280
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
6265
6281
  const schemaBlock = lazyContext
6266
6282
  ? ""
6267
6283
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -6671,7 +6687,7 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6671
6687
  await initTriggerDispatcher({
6672
6688
  getActions: getBackgroundActionEntries,
6673
6689
  getSystemPrompt: async (owner) => {
6674
- const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups });
6690
+ const resources = await loadResourcesForPrompt(owner, lazyContext, options?.appId, undefined, { disabledFrameworkGroups, skillReadTool });
6675
6691
  const schemaBlock = lazyContext
6676
6692
  ? ""
6677
6693
  : await buildSchemaBlock(owner, databaseToolsMode);
@@ -65,6 +65,7 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
65
65
  error: any;
66
66
  code: any;
67
67
  } | {
68
+ error?: undefined;
68
69
  errorCode?: undefined;
69
70
  code?: undefined;
70
71
  ok: boolean;
@@ -72,6 +73,5 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
72
73
  baseUrlKey?: string;
73
74
  scope: AgentEngineApiKeyScope;
74
75
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
75
- error?: undefined;
76
76
  }>>;
77
77
  export {};
@@ -9,16 +9,16 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
11
  error: string;
12
- scope?: undefined;
13
12
  ok?: undefined;
13
+ scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
16
16
  error: any;
17
17
  scope?: undefined;
18
18
  } | {
19
+ error?: undefined;
19
20
  ok: boolean;
20
21
  scope: "org" | "user";
21
- error?: undefined;
22
22
  }>>;
23
23
  export interface DefaultModelSelectionRequest {
24
24
  engine: string;
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import { canonicalFrameworkPathname, publicFrameworkPath, } from "./framework-route-prefix.js";
2
3
  export const EMAIL_AUTH_LINK_LANDING_PATH = "/_agent-native/auth/email-link/landing";
3
4
  const BETTER_AUTH_MAGIC_LINK_VERIFY_PATH = "/_agent-native/auth/ba/magic-link/verify";
@@ -108,16 +109,111 @@ function escapeHtml(value) {
108
109
  }
109
110
  });
110
111
  }
112
+ const EMAIL_AUTH_LINK_LANDING_STYLES = `
113
+ *, *::before, *::after { box-sizing: border-box; }
114
+ :root {
115
+ color-scheme: dark;
116
+ --auth-canvas: #0a0a0a; /* guard:allow-raw-color - match the standalone auth palette */
117
+ --auth-card: #141414; /* guard:allow-raw-color - match the standalone auth palette */
118
+ --auth-border: rgba(255, 255, 255, 0.08); /* guard:allow-raw-color - match the standalone auth palette */
119
+ --auth-foreground: #e5e5e5; /* guard:allow-raw-color - match the standalone auth palette */
120
+ --auth-heading: #fff; /* guard:allow-raw-color - match the standalone auth palette */
121
+ --auth-muted: #888; /* guard:allow-raw-color - match the standalone auth palette */
122
+ --auth-primary: #fff; /* guard:allow-raw-color - match the standalone auth palette */
123
+ --auth-primary-foreground: #000; /* guard:allow-raw-color - match the standalone auth palette */
124
+ --auth-primary-hover: #e5e5e5; /* guard:allow-raw-color - match the standalone auth palette */
125
+ --auth-focus: #33c4ff; /* guard:allow-raw-color - match the standalone auth palette */
126
+ }
127
+ body {
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ min-height: 100vh;
132
+ min-height: 100svh;
133
+ margin: 0;
134
+ padding: 1rem;
135
+ background: var(--auth-canvas);
136
+ color: var(--auth-foreground);
137
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
138
+ }
139
+ .auth-card {
140
+ width: 100%;
141
+ max-width: 400px;
142
+ padding: 2rem;
143
+ background: var(--auth-card);
144
+ border: 1px solid var(--auth-border);
145
+ border-radius: 12px;
146
+ }
147
+ h1 {
148
+ margin: 0 0 0.25rem;
149
+ color: var(--auth-heading);
150
+ font-size: 1.25rem;
151
+ font-weight: 600;
152
+ line-height: 1.5;
153
+ }
154
+ p {
155
+ margin: 0 0 1.5rem;
156
+ color: var(--auth-muted);
157
+ font-size: 0.8125rem;
158
+ line-height: 1.5;
159
+ }
160
+ form { margin: 0; }
161
+ button {
162
+ width: 100%;
163
+ min-height: 2.75rem;
164
+ padding: 0.625rem 0.75rem;
165
+ background: var(--auth-primary);
166
+ color: var(--auth-primary-foreground);
167
+ border: 0;
168
+ border-radius: 6px;
169
+ font: inherit;
170
+ font-size: 0.875rem;
171
+ font-weight: 500;
172
+ cursor: pointer;
173
+ transition: background-color 120ms ease;
174
+ }
175
+ button:hover { background: var(--auth-primary-hover); }
176
+ button:focus-visible {
177
+ outline: 2px solid var(--auth-focus);
178
+ outline-offset: 2px;
179
+ }
180
+ @media (max-width: 420px) {
181
+ .auth-card { padding: 1.5rem; }
182
+ }
183
+ @media (prefers-reduced-motion: reduce) {
184
+ button { transition: none; }
185
+ }
186
+ `;
111
187
  export function emailAuthLinkLandingPage(actionUrl, fields, copy, locale, direction) {
112
188
  const hiddenInputs = Object.entries(fields)
113
189
  .map(([name, value]) => `<input type="hidden" name="${escapeHtml(name)}" value="${escapeHtml(value)}">`)
114
190
  .join("");
115
- const html = `<!DOCTYPE html><html lang="${escapeHtml(locale)}" dir="${direction}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(copy.title)}</title></head><body><main><h1>${escapeHtml(copy.title)}</h1><p>${escapeHtml(copy.message)}</p><form method="post" action="${escapeHtml(actionUrl)}" autocomplete="off">${hiddenInputs}<button type="submit">${escapeHtml(copy.action)}</button></form></main></body></html>`;
191
+ // A nonce keeps the standalone page styled without allowing arbitrary inline CSS.
192
+ const styleNonce = randomBytes(16).toString("base64");
193
+ const html = `<!doctype html>
194
+ <html lang="${escapeHtml(locale)}" dir="${direction}">
195
+ <head>
196
+ <meta charset="utf-8">
197
+ <meta name="viewport" content="width=device-width, initial-scale=1">
198
+ <title>${escapeHtml(copy.title)}</title>
199
+ <style nonce="${styleNonce}">${EMAIL_AUTH_LINK_LANDING_STYLES}</style>
200
+ </head>
201
+ <body>
202
+ <main class="auth-card">
203
+ <h1>${escapeHtml(copy.title)}</h1>
204
+ <p>${escapeHtml(copy.message)}</p>
205
+ <form method="post" action="${escapeHtml(actionUrl)}" autocomplete="off">
206
+ ${hiddenInputs}
207
+ <button type="submit">${escapeHtml(copy.action)}</button>
208
+ </form>
209
+ </main>
210
+ </body>
211
+ </html>`;
116
212
  return new Response(html, {
117
213
  status: 200,
118
214
  headers: {
119
215
  "cache-control": "no-store",
120
- "content-security-policy": "default-src 'none'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'",
216
+ "content-security-policy": `default-src 'none'; style-src 'nonce-${styleNonce}'; form-action 'self'; base-uri 'none'; frame-ancestors 'none'`,
121
217
  "content-type": "text/html; charset=utf-8",
122
218
  "referrer-policy": "no-referrer",
123
219
  "x-robots-tag": "noindex",
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler, getHeader, getMethod, getQuery, setResponseHeader, } from "h3";
2
2
  import { withCollapsedAgentSidebarParam } from "../shared/agent-sidebar-url.js";
3
- import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, } from "../shared/embed-auth.js";
3
+ import { EMBED_MODE_QUERY_PARAM, EMBED_START_PATH, EMBED_TOKEN_QUERY_PARAM, MCP_APP_CHAT_BRIDGE_QUERY_PARAM, MCP_DIRECTORY_WIDGET_QUERY_PARAM, } from "../shared/embed-auth.js";
4
4
  import { isMcpEmbedTransplantOrigin, MCP_EMBED_CORS_ALLOW_HEADERS, } from "../shared/mcp-embed-headers.js";
5
5
  import { getConfiguredAppBasePath } from "./app-base-path.js";
6
6
  import { readCorsAllowedOrigins } from "./cors-origins.js";
@@ -21,6 +21,7 @@ function appendEmbedParams(target, token, chatBridgeActive = false) {
21
21
  if (chatBridgeActive) {
22
22
  url.searchParams.set(MCP_APP_CHAT_BRIDGE_QUERY_PARAM, "1");
23
23
  }
24
+ url.searchParams.delete(MCP_DIRECTORY_WIDGET_QUERY_PARAM);
24
25
  return `${url.pathname}${url.search}${url.hash}`;
25
26
  }
26
27
  function redirectWithStagedCookies(event, location, status = 302) {