@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.
- package/dist/agent/production-agent.d.ts +8 -0
- package/dist/agent/production-agent.js +68 -3
- package/dist/agent/strict-tool-input.d.ts +4 -0
- package/dist/agent/strict-tool-input.js +26 -0
- package/dist/agent/types.d.ts +7 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-attachments.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-attachments.js +1 -3
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +2 -2
- package/dist/cli/skills-content/turn-into-app-skill.js +15 -10
- package/dist/client/chat/agentkit-protocol.js +2 -0
- package/dist/client/chat/runtime.d.ts +1 -0
- package/dist/client/chat/runtime.js +35 -3
- package/dist/collab/routes.d.ts +1 -1
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/observability/metrics.d.ts +2 -0
- package/dist/observability/metrics.js +15 -1
- package/dist/observability/routes.d.ts +7 -7
- package/dist/observability/traces.js +8 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/agent-chat/additional-actions.d.ts +45 -0
- package/dist/server/agent-chat/additional-actions.js +707 -0
- package/dist/server/agent-chat/plugin-options.d.ts +21 -0
- package/dist/server/agent-chat-plugin.js +54 -13
- package/dist/server/better-auth-instance.js +108 -18
- package/dist/server/builder-browser.d.ts +4 -2
- package/dist/server/builder-browser.js +21 -16
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/data-url.d.ts +2 -0
- package/dist/shared/data-url.js +12 -0
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/source-brief.md +2 -2
- package/dist/triggers/actions/manage-automation.d.ts +5 -5
- package/docs/content/agent-surfaces.mdx +61 -0
- package/docs/content/observability.mdx +2 -0
- package/package.json +5 -4
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/attachments.md +1 -3
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -8
- 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
|
|
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
|
|
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
|
+
}
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|