@agent-native/core 0.209.0-nightly-20261014153330 → 0.209.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/agent/production-agent.d.ts +8 -0
  2. package/dist/agent/production-agent.js +68 -3
  3. package/dist/agent/strict-tool-input.d.ts +4 -0
  4. package/dist/agent/strict-tool-input.js +26 -0
  5. package/dist/agent/types.d.ts +7 -0
  6. package/dist/automation/index.d.ts +1 -1
  7. package/dist/cli/skills-content/turn-into-app-attachments.d.ts +1 -1
  8. package/dist/cli/skills-content/turn-into-app-attachments.js +1 -3
  9. package/dist/cli/skills-content/turn-into-app-skill.d.ts +2 -2
  10. package/dist/cli/skills-content/turn-into-app-skill.js +15 -10
  11. package/dist/client/chat/agentkit-protocol.js +2 -0
  12. package/dist/client/chat/runtime.d.ts +1 -0
  13. package/dist/client/chat/runtime.js +35 -3
  14. package/dist/collab/routes.d.ts +1 -1
  15. package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
  16. package/dist/observability/metrics.d.ts +2 -0
  17. package/dist/observability/metrics.js +15 -1
  18. package/dist/observability/routes.d.ts +7 -7
  19. package/dist/observability/traces.js +8 -1
  20. package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
  21. package/dist/provider-api/actions/provider-api.d.ts +10 -10
  22. package/dist/resources/handlers.d.ts +1 -1
  23. package/dist/secrets/routes.d.ts +10 -10
  24. package/dist/server/agent-chat/additional-actions.d.ts +45 -0
  25. package/dist/server/agent-chat/additional-actions.js +707 -0
  26. package/dist/server/agent-chat/plugin-options.d.ts +21 -0
  27. package/dist/server/agent-chat-plugin.js +54 -13
  28. package/dist/server/better-auth-instance.js +108 -18
  29. package/dist/server/builder-browser.d.ts +4 -2
  30. package/dist/server/builder-browser.js +21 -16
  31. package/dist/server/index.d.ts +2 -1
  32. package/dist/server/index.js +1 -1
  33. package/dist/shared/data-url.d.ts +2 -0
  34. package/dist/shared/data-url.js +12 -0
  35. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
  36. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
  37. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
  38. package/dist/triggers/actions/manage-automation.d.ts +5 -5
  39. package/docs/content/agent-surfaces.mdx +61 -0
  40. package/docs/content/observability.mdx +2 -0
  41. package/package.json +5 -4
  42. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
  43. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
  44. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
@@ -241,6 +241,14 @@ export type PersistedActionSurface = {
241
241
  mode: "default";
242
242
  };
243
243
  export declare function readPersistedActionSurface(value: unknown, propertyName: string): PersistedActionSurface | undefined;
244
+ /**
245
+ * A durable continuation re-resolves the caller's additional actions, which
246
+ * may have changed since the turn started. Keep the turn's names: drop entries
247
+ * that appeared since, and keep a vanished name as an entry that fails loudly
248
+ * when called, so a persisted action allowlist that names it still resolves.
249
+ * A name that has since become a registered action is left to that action.
250
+ */
251
+ export declare function pinAdditionalActionsToTurn(resolved: Record<string, ActionEntry>, turnNames: readonly string[], registered: Record<string, ActionEntry>): Record<string, ActionEntry>;
244
252
  export declare function filterActionsByAllowedNames(actions: Record<string, ActionEntry>, allowedActionNames: readonly string[]): Record<string, ActionEntry>;
245
253
  export declare const PLAN_MODE_SYSTEM_PROMPT = "## Plan Mode Active\n\nYou are in Plan mode. This turn is for research, clarification, and a proposed approach only.\n\nHard rules:\n- Use only read-only tools. Do not edit files, write resources, run mutating bash commands, mutate SQL rows, navigate the UI, send notifications, create jobs, create tools, send messages or tasks to external agents, or change external systems.\n- If a needed detail is unclear, ask a concise clarifying question before proposing a plan.\n- When ready, present a concrete plan with the files/tools you expect to touch, the intended changes, validation steps, and notable risks.\n- Do not treat approval as implicit while Plan mode is still active. Tell the user to switch to Act mode with the mode selector or /act before implementation.";
246
254
  export declare function isPlanModeToolCallAllowed(name: string, input: unknown, entry: ActionEntry): boolean;
@@ -82,6 +82,7 @@ import { startRun, subscribeToRun, replayCompletedTurn, getActiveRunForThread, g
82
82
  import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, AgentTurnInitiatorMismatchError, AgentTurnInitiatorUnavailableError, insertRun, insertRunEvent, isTurnAborted, markRunAborted, updateRunHeartbeat, updateRunStatusIfRunning, setRunError, setRunTerminalReason, claimBackgroundRun, readBackgroundRunClaim, recordRunDiagnostic, countRunsForTurn, RUN_DIAG_STAGE, UNCLAIMED_BACKGROUND_RUN_GRACE_MS, turnRunLedgerExhausted, } from "./run-store.js";
83
83
  import { buildCurrentTimeUserContext } from "./runtime-context.js";
84
84
  import { claimSetupResume, setupResumeRefusedRunId, } from "./setup-resume-claim.js";
85
+ import { getStrictToolInputValidator } from "./strict-tool-input.js";
85
86
  import { consumeAgentToolApproval, createAgentToolApproval, isAgentToolAlwaysAllowed, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
86
87
  import { findCompletedJournalEntry, isUnavailableToolCallInput, } from "./tool-call-journal.js";
87
88
  import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
@@ -748,6 +749,42 @@ export function readPersistedActionSurface(value, propertyName) {
748
749
  return { orgId: null, allowedActionNames: [] };
749
750
  }
750
751
  }
752
+ function readPersistedAdditionalActionNames(body) {
753
+ const names = body.__resolvedAdditionalActionNames;
754
+ if (names === undefined)
755
+ return undefined;
756
+ if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
757
+ throw new Error("Persisted additional action names are malformed");
758
+ }
759
+ return names;
760
+ }
761
+ /**
762
+ * A durable continuation re-resolves the caller's additional actions, which
763
+ * may have changed since the turn started. Keep the turn's names: drop entries
764
+ * that appeared since, and keep a vanished name as an entry that fails loudly
765
+ * when called, so a persisted action allowlist that names it still resolves.
766
+ * A name that has since become a registered action is left to that action.
767
+ */
768
+ export function pinAdditionalActionsToTurn(resolved, turnNames, registered) {
769
+ const pinned = {};
770
+ for (const name of turnNames) {
771
+ if (Object.hasOwn(registered, name))
772
+ continue;
773
+ pinned[name] = Object.hasOwn(resolved, name)
774
+ ? resolved[name]
775
+ : {
776
+ tool: {
777
+ description: `${name} is no longer available to this caller.`,
778
+ parameters: { type: "object", properties: {} },
779
+ },
780
+ readOnly: true,
781
+ run: () => {
782
+ throw new Error(`${name} is no longer available to this caller; it was removed after this turn started.`);
783
+ },
784
+ };
785
+ }
786
+ return pinned;
787
+ }
751
788
  export function filterActionsByAllowedNames(actions, allowedActionNames) {
752
789
  const allowedNames = [...new Set(allowedActionNames)];
753
790
  const unknownNames = allowedNames.filter((name) => !Object.prototype.hasOwnProperty.call(actions, name));
@@ -4030,6 +4067,9 @@ function narrowUnionBranchErrors(errors) {
4030
4067
  return kept.length ? kept : errors;
4031
4068
  }
4032
4069
  function validateRawToolInput(entry, input) {
4070
+ const strict = getStrictToolInputValidator(entry);
4071
+ if (strict)
4072
+ return strict(input);
4033
4073
  if (!shouldValidateRawToolParameters(entry))
4034
4074
  return null;
4035
4075
  const parameters = entry.tool.parameters;
@@ -4470,6 +4510,7 @@ export async function runAgentLoop(opts) {
4470
4510
  let streamedAssistantText = "";
4471
4511
  const streamedAssistantToolCalls = [];
4472
4512
  let terminalStopReason;
4513
+ let terminalStopErrorCode;
4473
4514
  const toolCallErrors = new Map();
4474
4515
  let contextMessages = completingFollowUpSuggestions
4475
4516
  ? followUpCompletionContext
@@ -4508,6 +4549,7 @@ export async function runAgentLoop(opts) {
4508
4549
  streamedAssistantText = "";
4509
4550
  streamedAssistantToolCalls.length = 0;
4510
4551
  terminalStopReason = undefined;
4552
+ terminalStopErrorCode = undefined;
4511
4553
  toolCallErrors.clear();
4512
4554
  try {
4513
4555
  let providerOptions = opts.providerOptions;
@@ -4566,6 +4608,9 @@ export async function runAgentLoop(opts) {
4566
4608
  type: "model_stream",
4567
4609
  status: "end",
4568
4610
  ...(terminalStopReason ? { reason: terminalStopReason } : {}),
4611
+ ...(terminalStopErrorCode
4612
+ ? { errorCode: terminalStopErrorCode }
4613
+ : {}),
4569
4614
  });
4570
4615
  };
4571
4616
  let lastModelStreamProgressAt = Date.now();
@@ -4813,6 +4858,7 @@ export async function runAgentLoop(opts) {
4813
4858
  else if (event.type === "stop") {
4814
4859
  terminalStopReason = event.reason;
4815
4860
  if (event.reason === "error") {
4861
+ terminalStopErrorCode = event.errorCode;
4816
4862
  throw engineStopError(event, engineMessages);
4817
4863
  }
4818
4864
  }
@@ -4822,6 +4868,14 @@ export async function runAgentLoop(opts) {
4822
4868
  }
4823
4869
  }
4824
4870
  }
4871
+ catch (err) {
4872
+ // A thrown engine error skips the `stop` event, so its code must
4873
+ // reach the bracket end here or the failed attempt goes unlabeled.
4874
+ const thrownCode = err?.errorCode;
4875
+ if (typeof thrownCode === "string")
4876
+ terminalStopErrorCode ??= thrownCode;
4877
+ throw err;
4878
+ }
4825
4879
  finally {
4826
4880
  closeModelStreamBracket();
4827
4881
  if (!eventIteratorDone) {
@@ -5252,13 +5306,14 @@ export async function runAgentLoop(opts) {
5252
5306
  const runToolCall = async (toolCall) => {
5253
5307
  noteToolNeeded(toolCall.name);
5254
5308
  const actionEntry = actions[toolCall.name];
5255
- const placeholderNormalization = actionEntry
5309
+ const strictToolInput = getStrictToolInputValidator(actionEntry);
5310
+ const placeholderNormalization = actionEntry && !strictToolInput
5256
5311
  ? normalizeOptionalToolPlaceholders(actionEntry.tool.parameters, toolCall.input)
5257
5312
  : { input: toolCall.input, changed: false };
5258
5313
  if (placeholderNormalization.changed) {
5259
5314
  toolCall = { ...toolCall, input: placeholderNormalization.input };
5260
5315
  }
5261
- const jsonStringCoercion = actionEntry
5316
+ const jsonStringCoercion = actionEntry && !strictToolInput
5262
5317
  ? coerceStringifiedJsonToolValues(actionEntry.tool.parameters, toolCall.input)
5263
5318
  : { input: toolCall.input, changed: false };
5264
5319
  if (jsonStringCoercion.changed) {
@@ -7802,6 +7857,7 @@ export function createProductionAgentHandler(options) {
7802
7857
  delete body[AGENT_CHAT_BACKGROUND_RUN_FIELD];
7803
7858
  delete body[AGENT_CHAT_RECOVERY_OF_RUN_FIELD];
7804
7859
  delete body.__resolvedActionSurface;
7860
+ delete body.__resolvedAdditionalActionNames;
7805
7861
  }
7806
7862
  let requestedActionScope;
7807
7863
  if (hasOwn(body, "actionScope")) {
@@ -8258,11 +8314,20 @@ export function createProductionAgentHandler(options) {
8258
8314
  if (options.resolveAdditionalActions &&
8259
8315
  ownerEmail &&
8260
8316
  getRequestContext()?.agentRunAnonymous !== true) {
8261
- const additionalActions = await options.resolveAdditionalActions({
8317
+ const resolvedAdditionalActions = await options.resolveAdditionalActions({
8262
8318
  event,
8263
8319
  ownerEmail,
8264
8320
  orgId: getRequestOrgId() ?? null,
8265
8321
  });
8322
+ const turnAdditionalActionNames = isBackgroundWorker
8323
+ ? readPersistedAdditionalActionNames(body)
8324
+ : undefined;
8325
+ const additionalActions = turnAdditionalActionNames
8326
+ ? pinAdditionalActionsToTurn(resolvedAdditionalActions, turnAdditionalActionNames, resolvedActions)
8327
+ : resolvedAdditionalActions;
8328
+ if (!isBackgroundWorker) {
8329
+ body.__resolvedAdditionalActionNames = Object.keys(additionalActions);
8330
+ }
8266
8331
  const requestActions = { ...resolvedActions, ...additionalActions };
8267
8332
  if (requestActions[TOOL_SEARCH_ACTION_NAME]) {
8268
8333
  requestActions[TOOL_SEARCH_ACTION_NAME] = createToolSearchEntry(() => getRequestActions(requestActions));
@@ -0,0 +1,4 @@
1
+ type StrictToolInputValidator = (input: unknown) => string | null;
2
+ export declare function registerStrictToolInput(entry: object, validate: StrictToolInputValidator): void;
3
+ export declare function getStrictToolInputValidator(entry: object | undefined): StrictToolInputValidator | undefined;
4
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Validators for action entries that must receive their arguments exactly as
3
+ * the model sent them. The agent loop normally repairs model input (filling
4
+ * placeholders, parsing stringified JSON, coercing types) before validating;
5
+ * an entry registered here skips that repair and is validated with its own
6
+ * validator instead, which returns an error message or null.
7
+ *
8
+ * The validator lives on the entry under a symbol rather than in a side table:
9
+ * registries copy entries with object spread as they filter them, and spread
10
+ * keeps own symbol properties, so the copy reaching the loop stays strict.
11
+ */
12
+ const STRICT_TOOL_INPUT = Symbol("agent-native.strictToolInput");
13
+ export function registerStrictToolInput(entry, validate) {
14
+ Object.defineProperty(entry, STRICT_TOOL_INPUT, {
15
+ value: validate,
16
+ enumerable: true,
17
+ });
18
+ }
19
+ export function getStrictToolInputValidator(entry) {
20
+ const validate = entry
21
+ ? entry[STRICT_TOOL_INPUT]
22
+ : undefined;
23
+ return typeof validate === "function"
24
+ ? validate
25
+ : undefined;
26
+ }
@@ -206,6 +206,12 @@ export interface AgentChatRequest {
206
206
  orgId: string | null;
207
207
  mode: "default";
208
208
  };
209
+ /**
210
+ * Names of the request-scoped additional actions the turn started with,
211
+ * carried across authenticated durable background dispatches so a
212
+ * continuation keeps the same tool set. Deleted from client requests.
213
+ */
214
+ __resolvedAdditionalActionNames?: string[];
209
215
  turnId?: string;
210
216
  /** Turn metadata the client forwards; read only for the keys it names. */
211
217
  metadata?: Record<string, unknown>;
@@ -274,6 +280,7 @@ export type AgentChatEvent = {
274
280
  type: "model_stream";
275
281
  status: "start" | "end";
276
282
  reason?: "end_turn" | "tool_use" | "max_tokens" | "stop_sequence" | "error";
283
+ errorCode?: string;
277
284
  } | {
278
285
  type: "tool_start";
279
286
  tool: string;
@@ -139,8 +139,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
139
139
  duplicate?: undefined;
140
140
  eventId?: undefined;
141
141
  } | {
142
- error?: undefined;
143
142
  accepted: boolean;
144
143
  duplicate: boolean;
145
144
  eventId: string;
145
+ error?: undefined;
146
146
  }>;
@@ -1 +1 @@
1
- export declare const TURN_INTO_APP_ATTACHMENTS_REFERENCE_MD = "# Dispatch Handoff Attachments\n\nWhen calling `start-workspace-app-creation`, pass source files in the `attachments` argument rather than embedding their bytes in `prompt`.\n\n## Uploaded files\n\nImages and PDFs use an upload attachment with a matching base64 data URL:\n\n```ts\n{\n type: \"upload\",\n contentType: \"image/webp\" | \"image/png\" | \"image/jpeg\" | \"image/gif\" | \"application/pdf\",\n name: string,\n dataUrl: \"data:<contentType>;base64,<BASE64_CONTENT>\",\n size: number,\n id: string\n}\n```\n\nText and JSON files provide decoded contents instead of a data URL:\n\n```ts\n{\n type: \"upload\",\n contentType: \"text/plain\" | \"application/json\",\n name: string,\n dataUrl: \"\",\n text: string,\n size: number,\n id: string\n}\n```\n\nSet `size` to the UTF-8 byte length of `text`. Use a stable or unique `id` for each attachment.\n\n## Public URLs\n\nFor an existing public file that Builder can fetch, use:\n\n```ts\n{\n type: \"url\",\n value: \"https://...\"\n}\n```\n\nDo not pass private, localhost, or internal-network URLs.\n\n## Handoff behavior\n\nKeep the original task in `prompt` and tell the Builder agent how each attachment should be used. Put multiple attachments in the same `attachments` array. Attachments are model context only and are not copied into the generated app's workspace.\n";
1
+ export declare const TURN_INTO_APP_ATTACHMENTS_REFERENCE_MD = "# Dispatch Handoff Attachments\n\nWhen calling `start-workspace-app-creation`, pass source files in the `attachments` argument rather than embedding their bytes in `prompt`.\n\n## Uploaded files\n\nImages and PDFs use an upload attachment with a matching base64 data URL:\n\n```ts\n{\n type: \"upload\",\n contentType: \"image/webp\" | \"image/png\" | \"image/jpeg\" | \"image/gif\" | \"application/pdf\",\n name: string,\n dataUrl: \"data:<contentType>;base64,<BASE64_CONTENT>\",\n id: string\n}\n```\n\nText and JSON files provide decoded contents instead of a data URL:\n\n```ts\n{\n type: \"upload\",\n contentType: \"text/plain\" | \"application/json\",\n name: string,\n dataUrl: \"\",\n text: string,\n id: string\n}\n```\n\nSend any other text-based file (Markdown, CSV, source code, plain text) as `text/plain` with its real file name, for example `notes.md`. Use a stable or unique `id` for each attachment. A malformed attachment is rejected with `invalid_attachment` and a message naming the problem; fix that attachment and retry, because an unchanged retry fails the same way.\n\n## Public URLs\n\nFor an existing public file that Builder can fetch, use:\n\n```ts\n{\n type: \"url\",\n value: \"https://...\"\n}\n```\n\nDo not pass private, localhost, or internal-network URLs.\n\n## Handoff behavior\n\nKeep the original task in `prompt` and tell the Builder agent how each attachment should be used. Put multiple attachments in the same `attachments` array. Attachments are model context only and are not copied into the generated app's workspace.\n";
@@ -12,7 +12,6 @@ Images and PDFs use an upload attachment with a matching base64 data URL:
12
12
  contentType: "image/webp" | "image/png" | "image/jpeg" | "image/gif" | "application/pdf",
13
13
  name: string,
14
14
  dataUrl: "data:<contentType>;base64,<BASE64_CONTENT>",
15
- size: number,
16
15
  id: string
17
16
  }
18
17
  \`\`\`
@@ -26,12 +25,11 @@ Text and JSON files provide decoded contents instead of a data URL:
26
25
  name: string,
27
26
  dataUrl: "",
28
27
  text: string,
29
- size: number,
30
28
  id: string
31
29
  }
32
30
  \`\`\`
33
31
 
34
- Set \`size\` to the UTF-8 byte length of \`text\`. Use a stable or unique \`id\` for each attachment.
32
+ Send any other text-based file (Markdown, CSV, source code, plain text) as \`text/plain\` with its real file name, for example \`notes.md\`. Use a stable or unique \`id\` for each attachment. A malformed attachment is rejected with \`invalid_attachment\` and a message naming the problem; fix that attachment and retry, because an unchanged retry fails the same way.
35
33
 
36
34
  ## Public URLs
37
35