@agent-native/core 0.209.1-nightly-20261015153252 → 0.209.1
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/a2a/index.d.ts +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/server.d.ts +10 -0
- package/dist/a2a/server.js +96 -0
- package/dist/agent/production-agent.d.ts +2 -0
- package/dist/agent/production-agent.js +43 -6
- package/dist/agent/submission-deadline.d.ts +1 -0
- package/dist/agent/submission-deadline.js +27 -0
- package/dist/agent/thread-data-builder.d.ts +2 -1
- package/dist/agent/thread-data-builder.js +37 -6
- package/dist/authorization/check-action.js +4 -3
- package/dist/chat-threads/store.d.ts +1 -0
- package/dist/chat-threads/store.js +34 -2
- package/dist/client/agent-chat.d.ts +3 -0
- package/dist/client/agent-chat.js +9 -0
- package/dist/client/chat/agentkit-agent-native.js +126 -3
- package/dist/client/error-format.d.ts +2 -0
- package/dist/client/error-format.js +10 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/org/index.d.ts +1 -1
- package/dist/org/index.js +1 -1
- package/dist/org/workspace-app-access-outcome.d.ts +45 -0
- package/dist/org/workspace-app-access-outcome.js +80 -0
- package/dist/org/workspace-app-access-request.d.ts +2 -2
- package/dist/org/workspace-app-access-request.js +2 -2
- package/dist/org/workspace-app-access.d.ts +8 -3
- package/dist/org/workspace-app-access.js +122 -30
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +6 -6
- package/dist/server/action-routes.js +10 -7
- package/dist/server/agent-chat-plugin.js +28 -2
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/auth.js +3 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/triggers/actions/manage-automation.d.ts +3 -3
- package/package.json +4 -4
package/dist/a2a/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { mountA2A, verifyA2AToken } from "./server.js";
|
|
1
|
+
export { diagnoseA2ATokenFailure, mountA2A, verifyA2AToken, type A2ATokenFailureCause, } from "./server.js";
|
|
2
2
|
export type { A2ATokenPayload } from "./server.js";
|
|
3
3
|
export { generateAgentCard } from "./agent-card.js";
|
|
4
4
|
export { A2A_AGENT_ACTIVITY_KIND, A2A_AGENT_ACTIVITY_VERSION, MAX_A2A_ACTIVITY_REASONING_CHARS, MAX_A2A_ACTIVITY_REASONING_SEGMENTS, MAX_A2A_ACTIVITY_RESPONSE_CHARS, MAX_A2A_ACTIVITY_TOOL_CALLS, MAX_A2A_ACTIVITY_TOTAL_CHARS, MAX_A2A_ACTIVITY_TOOL_ID_CHARS, MAX_A2A_ACTIVITY_TOOL_INPUT_CHARS, MAX_A2A_ACTIVITY_TOOL_NAME_CHARS, MAX_A2A_ACTIVITY_TOOL_PAYLOAD_CHARS, MAX_A2A_ACTIVITY_TOOL_RESULT_CHARS, applyA2AAgentActivityEvent, buildA2AAgentActivityPart, buildA2AAgentActivitySnapshot, createA2AAgentActivityState, parseA2AAgentActivityPart, } from "./activity.js";
|
package/dist/a2a/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { mountA2A, verifyA2AToken } from "./server.js";
|
|
1
|
+
export { diagnoseA2ATokenFailure, mountA2A, verifyA2AToken, } from "./server.js";
|
|
2
2
|
export { generateAgentCard } from "./agent-card.js";
|
|
3
3
|
export { A2A_AGENT_ACTIVITY_KIND, A2A_AGENT_ACTIVITY_VERSION, MAX_A2A_ACTIVITY_REASONING_CHARS, MAX_A2A_ACTIVITY_REASONING_SEGMENTS, MAX_A2A_ACTIVITY_RESPONSE_CHARS, MAX_A2A_ACTIVITY_TOOL_CALLS, MAX_A2A_ACTIVITY_TOTAL_CHARS, MAX_A2A_ACTIVITY_TOOL_ID_CHARS, MAX_A2A_ACTIVITY_TOOL_INPUT_CHARS, MAX_A2A_ACTIVITY_TOOL_NAME_CHARS, MAX_A2A_ACTIVITY_TOOL_PAYLOAD_CHARS, MAX_A2A_ACTIVITY_TOOL_RESULT_CHARS, applyA2AAgentActivityEvent, buildA2AAgentActivityPart, buildA2AAgentActivitySnapshot, createA2AAgentActivityState, parseA2AAgentActivityPart, } from "./activity.js";
|
|
4
4
|
export { A2AClient, A2AJsonRpcResponseError, A2AMissingJsonRpcResponseError, A2ANoJsonRpcInterfaceError, A2AProtocolError, assertCredentialedA2AUrl, callAction, callAgent, clearA2ACardCache, getGlobalA2ASecret, signA2AOrganizationToken, signA2AToken, } from "./client.js";
|
package/dist/a2a/server.d.ts
CHANGED
|
@@ -48,5 +48,15 @@ export declare function verifyA2AToken(token: string, event?: any, audienceOptio
|
|
|
48
48
|
/** Only use this explicitly configured credential; never fall back. */
|
|
49
49
|
verificationSecret?: string;
|
|
50
50
|
}): Promise<A2ATokenPayload>;
|
|
51
|
+
export type A2ATokenFailureCause = "malformed" | "secret-missing" | "audience-unresolvable" | "audience" | "issuer" | "expired" | "signature" | "invalid-claims" | "mcp-credential" | "missing-subject" | "org-binding" | "org-lookup-failed" | "undetermined";
|
|
52
|
+
/**
|
|
53
|
+
* Names why a token that `verifyA2AToken` resolved no identity for was
|
|
54
|
+
* refused. `verifyA2AToken` collapses a wrong audience, a wrong secret and an
|
|
55
|
+
* unbound organization into one empty identity; this re-checks the same inputs,
|
|
56
|
+
* against the deploy secret and then the organization's, to tell them apart.
|
|
57
|
+
* Diagnostic only: it never grants anything, and its answer must not be used as
|
|
58
|
+
* an authorization result.
|
|
59
|
+
*/
|
|
60
|
+
export declare function diagnoseA2ATokenFailure(token: string, event?: any): Promise<A2ATokenFailureCause>;
|
|
51
61
|
export declare function mountA2A(nitroApp: any, config: A2AConfig, routePrefix?: string): void;
|
|
52
62
|
export declare function filterPublicAgentCardSkills(config: A2AConfig): AgentSkill[];
|
package/dist/a2a/server.js
CHANGED
|
@@ -159,6 +159,102 @@ function isDirectReadSkill(skill) {
|
|
|
159
159
|
export async function verifyA2AToken(token, event, audienceOptions) {
|
|
160
160
|
return (await verifyA2ATokenInternal(token, event, audienceOptions)).payload;
|
|
161
161
|
}
|
|
162
|
+
async function checkAgainstSecret(token, secret, options) {
|
|
163
|
+
try {
|
|
164
|
+
const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(secret), options);
|
|
165
|
+
return { payload };
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
const { code, claim } = error;
|
|
169
|
+
if (code === "ERR_JWT_EXPIRED")
|
|
170
|
+
return { cause: "expired" };
|
|
171
|
+
if (code === "ERR_JWS_SIGNATURE_VERIFICATION_FAILED") {
|
|
172
|
+
return { cause: "signature" };
|
|
173
|
+
}
|
|
174
|
+
if (code === "ERR_JWT_CLAIM_VALIDATION_FAILED") {
|
|
175
|
+
if (claim === "aud")
|
|
176
|
+
return { cause: "audience" };
|
|
177
|
+
if (claim === "iss")
|
|
178
|
+
return { cause: "issuer" };
|
|
179
|
+
}
|
|
180
|
+
return { cause: "invalid-claims" };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function diagnoseVerifiedPayload(payload) {
|
|
184
|
+
if (isMcpCredential(payload))
|
|
185
|
+
return "mcp-credential";
|
|
186
|
+
if (typeof payload.sub !== "string" || !payload.sub.trim()) {
|
|
187
|
+
return "missing-subject";
|
|
188
|
+
}
|
|
189
|
+
try {
|
|
190
|
+
const organization = await verifyA2AOrganizationIdentity(payload);
|
|
191
|
+
return organization === null ? "org-binding" : "undetermined";
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// coercion-ok: an unreadable organization registry is the typed "org-lookup-failed" cause.
|
|
195
|
+
return "org-lookup-failed";
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Names why a token that `verifyA2AToken` resolved no identity for was
|
|
200
|
+
* refused. `verifyA2AToken` collapses a wrong audience, a wrong secret and an
|
|
201
|
+
* unbound organization into one empty identity; this re-checks the same inputs,
|
|
202
|
+
* against the deploy secret and then the organization's, to tell them apart.
|
|
203
|
+
* Diagnostic only: it never grants anything, and its answer must not be used as
|
|
204
|
+
* an authorization result.
|
|
205
|
+
*/
|
|
206
|
+
export async function diagnoseA2ATokenFailure(token, event) {
|
|
207
|
+
let claims;
|
|
208
|
+
try {
|
|
209
|
+
claims = jose.decodeJwt(token);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// coercion-ok: an undecodable token is the typed "malformed" cause.
|
|
213
|
+
return "malformed";
|
|
214
|
+
}
|
|
215
|
+
const secret = readDeployCredentialEnv("A2A_SECRET")?.trim();
|
|
216
|
+
const options = {};
|
|
217
|
+
if (typeof claims.aud !== "undefined") {
|
|
218
|
+
const audience = expectedJwtAudience(event);
|
|
219
|
+
if (!audience)
|
|
220
|
+
return "audience-unresolvable";
|
|
221
|
+
options.audience = audience;
|
|
222
|
+
}
|
|
223
|
+
if (typeof claims.iss === "string" && claims.iss.length > 0) {
|
|
224
|
+
options.issuer = claims.iss;
|
|
225
|
+
}
|
|
226
|
+
// The verifier goes on to the organization's own secret when the deploy secret
|
|
227
|
+
// is unset or does not verify the token, so neither is the final answer yet.
|
|
228
|
+
let deployCause = "secret-missing";
|
|
229
|
+
if (secret) {
|
|
230
|
+
const deploy = await checkAgainstSecret(token, secret, options);
|
|
231
|
+
if ("payload" in deploy)
|
|
232
|
+
return diagnoseVerifiedPayload(deploy.payload);
|
|
233
|
+
deployCause = deploy.cause;
|
|
234
|
+
}
|
|
235
|
+
const orgDomain = typeof claims.org_domain === "string" ? claims.org_domain.trim() : "";
|
|
236
|
+
if ((deployCause !== "signature" && deployCause !== "secret-missing") ||
|
|
237
|
+
!orgDomain) {
|
|
238
|
+
return deployCause;
|
|
239
|
+
}
|
|
240
|
+
let organization;
|
|
241
|
+
try {
|
|
242
|
+
const { resolveA2AOrganizationCredentialsByDomain } = await import("../org/context.js");
|
|
243
|
+
organization = await resolveA2AOrganizationCredentialsByDomain(orgDomain);
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// coercion-ok: an unreadable organization registry is the typed "org-lookup-failed" cause.
|
|
247
|
+
return "org-lookup-failed";
|
|
248
|
+
}
|
|
249
|
+
if (!organization)
|
|
250
|
+
return deployCause;
|
|
251
|
+
const orgCheck = await checkAgainstSecret(token, organization.secret, options);
|
|
252
|
+
if ("cause" in orgCheck)
|
|
253
|
+
return orgCheck.cause;
|
|
254
|
+
return organizationPrincipalClaims(orgCheck.payload, organization)
|
|
255
|
+
? "undetermined"
|
|
256
|
+
: "org-binding";
|
|
257
|
+
}
|
|
162
258
|
async function verifyA2ATokenInternal(token, event, audienceOptions) {
|
|
163
259
|
// Step 1: Peek at JWT claims WITHOUT verification to get org_domain.
|
|
164
260
|
// This is safe because we only use org_domain to look up the secret,
|
|
@@ -329,6 +329,7 @@ export interface ProductionAgentOptions {
|
|
|
329
329
|
attachments?: AgentChatAttachment[];
|
|
330
330
|
queuedMessageId?: string;
|
|
331
331
|
queuedMessageClaimId?: string;
|
|
332
|
+
submissionExpiresAt?: unknown;
|
|
332
333
|
}) => void | Promise<void>;
|
|
333
334
|
/**
|
|
334
335
|
* The turn was refused before a run started (no usable model credential).
|
|
@@ -342,6 +343,7 @@ export interface ProductionAgentOptions {
|
|
|
342
343
|
attachments?: AgentChatAttachment[];
|
|
343
344
|
queuedMessageId?: string;
|
|
344
345
|
agentKitMessageId?: string;
|
|
346
|
+
submissionExpiresAt?: unknown;
|
|
345
347
|
retryContext: RefusedTurnRetryContext;
|
|
346
348
|
failure: {
|
|
347
349
|
code: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS, MAX_AGENT_REQUEST_ATTACHMENTS, } from "@agent-native/agentkit/protocol";
|
|
2
|
+
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY, MAX_AGENT_REQUEST_ATTACHMENT_DATA_CHARS, MAX_AGENT_REQUEST_ATTACHMENTS, } from "@agent-native/agentkit/protocol";
|
|
3
3
|
import Ajv from "ajv";
|
|
4
4
|
import Ajv2020 from "ajv/dist/2020.js";
|
|
5
5
|
import { defineEventHandler, getHeader, setResponseHeader, setResponseStatus, getMethod, } from "h3";
|
|
@@ -83,6 +83,7 @@ import { writeLedgerEntry, readLedgerEntry, clearLedgerForThread, AgentTurnIniti
|
|
|
83
83
|
import { buildCurrentTimeUserContext } from "./runtime-context.js";
|
|
84
84
|
import { claimSetupResume, setupResumeRefusedRunId, } from "./setup-resume-claim.js";
|
|
85
85
|
import { getStrictToolInputValidator } from "./strict-tool-input.js";
|
|
86
|
+
import { normalizeSubmissionDeadlineMetadata } from "./submission-deadline.js";
|
|
86
87
|
import { consumeAgentToolApproval, createAgentToolApproval, isAgentToolAlwaysAllowed, resolveAgentToolApprovalTurnId, } from "./tool-approval-store.js";
|
|
87
88
|
import { findCompletedJournalEntry, isUnavailableToolCallInput, } from "./tool-call-journal.js";
|
|
88
89
|
import { redactSensitiveFields, sanitizeToolErrorText, sanitizeToolErrorValue, } from "./tool-error-redaction.js";
|
|
@@ -1024,6 +1025,42 @@ export function createPlanModeActionRegistry(actions) {
|
|
|
1024
1025
|
}
|
|
1025
1026
|
return filtered;
|
|
1026
1027
|
}
|
|
1028
|
+
function submissionExpiryMetadata(metadata) {
|
|
1029
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
1030
|
+
return undefined;
|
|
1031
|
+
}
|
|
1032
|
+
const custom = metadata.custom;
|
|
1033
|
+
if (!custom ||
|
|
1034
|
+
typeof custom !== "object" ||
|
|
1035
|
+
Array.isArray(custom) ||
|
|
1036
|
+
!Object.prototype.hasOwnProperty.call(custom, AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY)) {
|
|
1037
|
+
return undefined;
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
submissionExpiresAt: custom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY],
|
|
1041
|
+
};
|
|
1042
|
+
}
|
|
1043
|
+
function mergeQueuedPromotionMetadata(admittedMetadata, optionMetadata) {
|
|
1044
|
+
const admitted = queuedPromotionRecord(admittedMetadata) ?? {};
|
|
1045
|
+
const options = queuedPromotionRecord(optionMetadata) ?? {};
|
|
1046
|
+
const admittedCustom = queuedPromotionRecord(admitted.custom) ?? {};
|
|
1047
|
+
const optionCustom = queuedPromotionRecord(options.custom) ?? {};
|
|
1048
|
+
const custom = { ...admittedCustom, ...optionCustom };
|
|
1049
|
+
delete custom[AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY];
|
|
1050
|
+
delete custom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY];
|
|
1051
|
+
if (Object.prototype.hasOwnProperty.call(admittedCustom, AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY)) {
|
|
1052
|
+
custom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY] =
|
|
1053
|
+
admittedCustom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY];
|
|
1054
|
+
}
|
|
1055
|
+
const metadata = { ...admitted, ...options };
|
|
1056
|
+
if (Object.keys(custom).length > 0) {
|
|
1057
|
+
metadata.custom = custom;
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
delete metadata.custom;
|
|
1061
|
+
}
|
|
1062
|
+
return metadata;
|
|
1063
|
+
}
|
|
1027
1064
|
export async function resolveAgentOwnerEmail(options, event) {
|
|
1028
1065
|
let ownerEmail = null;
|
|
1029
1066
|
if (options.resolveOwnerEmail) {
|
|
@@ -7955,7 +7992,7 @@ export function createProductionAgentHandler(options) {
|
|
|
7955
7992
|
: typeof parentId === "string" && parentId.trim()
|
|
7956
7993
|
? parentId.trim()
|
|
7957
7994
|
: undefined;
|
|
7958
|
-
let requestMetadata = body.metadata;
|
|
7995
|
+
let requestMetadata = normalizeSubmissionDeadlineMetadata(body.metadata);
|
|
7959
7996
|
const continuedRunId = (requested) => internalContinuation === true &&
|
|
7960
7997
|
typeof requested === "string" &&
|
|
7961
7998
|
requested.trim().length <= 200
|
|
@@ -8248,10 +8285,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8248
8285
|
if (admittedQueuedMessage) {
|
|
8249
8286
|
// A claim admits the persisted queue payload, not extra fields in this request.
|
|
8250
8287
|
const promotionOptions = admittedQueuedMessage.options ?? {};
|
|
8251
|
-
const promotionMetadata =
|
|
8252
|
-
...(admittedQueuedMessage.metadata ?? {}),
|
|
8253
|
-
...(queuedPromotionRecord(promotionOptions.metadata) ?? {}),
|
|
8254
|
-
};
|
|
8288
|
+
const promotionMetadata = mergeQueuedPromotionMetadata(admittedQueuedMessage.metadata, promotionOptions.metadata);
|
|
8255
8289
|
requestMessage = admittedQueuedMessage.text;
|
|
8256
8290
|
agentKitMessageId = admittedQueuedMessage.id;
|
|
8257
8291
|
requestDisplayMessage = admittedQueuedMessage.text;
|
|
@@ -8330,6 +8364,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8330
8364
|
? requestDisplayMessage
|
|
8331
8365
|
: requestMessage,
|
|
8332
8366
|
attachments: requestAttachments,
|
|
8367
|
+
...submissionExpiryMetadata(requestMetadata),
|
|
8333
8368
|
...(typeof queuedMessageId === "string" && queuedMessageId.trim()
|
|
8334
8369
|
? { queuedMessageId: queuedMessageId.trim() }
|
|
8335
8370
|
: {}),
|
|
@@ -8715,6 +8750,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8715
8750
|
? requestDisplayMessage
|
|
8716
8751
|
: requestMessage,
|
|
8717
8752
|
attachments: requestAttachments,
|
|
8753
|
+
...submissionExpiryMetadata(requestMetadata),
|
|
8718
8754
|
...(typeof queuedMessageId === "string" && queuedMessageId.trim()
|
|
8719
8755
|
? { queuedMessageId: queuedMessageId.trim() }
|
|
8720
8756
|
: {}),
|
|
@@ -9549,6 +9585,7 @@ export function createProductionAgentHandler(options) {
|
|
|
9549
9585
|
message: messageToPersist,
|
|
9550
9586
|
...(agentKitMessageId ? { agentKitMessageId } : {}),
|
|
9551
9587
|
attachments: requestAttachments,
|
|
9588
|
+
...submissionExpiryMetadata(requestMetadata),
|
|
9552
9589
|
...(typeof queuedMessageId === "string" && queuedMessageId.trim()
|
|
9553
9590
|
? { queuedMessageId: queuedMessageId.trim() }
|
|
9554
9591
|
: {}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function normalizeSubmissionDeadlineMetadata(metadata: unknown, serverReceivedAt?: number): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY, } from "@agent-native/agentkit/protocol";
|
|
2
|
+
export function normalizeSubmissionDeadlineMetadata(metadata, serverReceivedAt = Date.now()) {
|
|
3
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
const record = metadata;
|
|
7
|
+
const custom = record.custom;
|
|
8
|
+
if (!custom || typeof custom !== "object" || Array.isArray(custom)) {
|
|
9
|
+
return record;
|
|
10
|
+
}
|
|
11
|
+
const customRecord = custom;
|
|
12
|
+
if (!Object.prototype.hasOwnProperty.call(customRecord, AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY)) {
|
|
13
|
+
return record;
|
|
14
|
+
}
|
|
15
|
+
const ttlMs = customRecord[AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY];
|
|
16
|
+
const expiresAt = typeof ttlMs === "number" &&
|
|
17
|
+
Number.isFinite(ttlMs) &&
|
|
18
|
+
ttlMs >= 0 &&
|
|
19
|
+
Number.isFinite(serverReceivedAt) &&
|
|
20
|
+
Number.isFinite(serverReceivedAt + ttlMs)
|
|
21
|
+
? serverReceivedAt + ttlMs
|
|
22
|
+
: Number.NaN;
|
|
23
|
+
const normalizedCustom = { ...customRecord };
|
|
24
|
+
delete normalizedCustom[AGENT_NATIVE_SUBMISSION_TTL_MS_METADATA_KEY];
|
|
25
|
+
normalizedCustom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY] = expiresAt;
|
|
26
|
+
return { ...record, custom: normalizedCustom };
|
|
27
|
+
}
|
|
@@ -128,7 +128,7 @@ export declare function applySubmittedUserMessage(repo: any, userMessage: UserMe
|
|
|
128
128
|
status: "submitted" | "already_submitted";
|
|
129
129
|
repo: any;
|
|
130
130
|
} | {
|
|
131
|
-
status: "already_claimed" | "claim_expired";
|
|
131
|
+
status: "already_claimed" | "claim_expired" | "submission_expired";
|
|
132
132
|
};
|
|
133
133
|
/**
|
|
134
134
|
* Whether a stored user message is the prompt this run answers, rather than
|
|
@@ -150,6 +150,7 @@ export declare function buildUserMessage(opts: {
|
|
|
150
150
|
turnId?: string;
|
|
151
151
|
agentKitMessageId?: string;
|
|
152
152
|
queuedMessageId?: string;
|
|
153
|
+
submissionExpiresAt?: unknown;
|
|
153
154
|
createdAt?: Date;
|
|
154
155
|
/** The turn was refused before a run started; its retry reads this back. */
|
|
155
156
|
refusedRetry?: RefusedTurnRetryContext;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseAgentSuggestion, } from "@agent-native/agentkit/protocol";
|
|
1
|
+
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, parseAgentSuggestion, } from "@agent-native/agentkit/protocol";
|
|
2
2
|
import { formatChatErrorText, normalizeChatError, } from "../client/error-format.js";
|
|
3
3
|
import { isCredentialGapCodeAgentEvent, normalizeCodeAgentTranscript, } from "../code-agents/transcript-normalizer.js";
|
|
4
4
|
import { stripAgentChatContextFromMessage } from "../shared/agent-chat-context.js";
|
|
@@ -1252,11 +1252,8 @@ export function claimQueuedMessage(repo, messageId) {
|
|
|
1252
1252
|
return pruneClaimedQueuedMessages(normalized);
|
|
1253
1253
|
}
|
|
1254
1254
|
export function applySubmittedUserMessage(repo, userMessage, queuedMessage) {
|
|
1255
|
-
if (!queuedMessage) {
|
|
1256
|
-
return { status: "submitted", repo: upsertUserMessage(repo, userMessage) };
|
|
1257
|
-
}
|
|
1258
1255
|
const userCustom = userMessage.metadata.custom;
|
|
1259
|
-
const wasSubmitted = Array.isArray(repo?.messages)
|
|
1256
|
+
const wasSubmitted = queuedMessage && Array.isArray(repo?.messages)
|
|
1260
1257
|
? repo.messages.some((entry) => {
|
|
1261
1258
|
const outer = entry;
|
|
1262
1259
|
const message = outer?.message ?? outer;
|
|
@@ -1274,12 +1271,41 @@ export function applySubmittedUserMessage(repo, userMessage, queuedMessage) {
|
|
|
1274
1271
|
userCustom?.submittedRunId);
|
|
1275
1272
|
})
|
|
1276
1273
|
: false;
|
|
1277
|
-
if (wasSubmitted) {
|
|
1274
|
+
if (wasSubmitted && queuedMessage) {
|
|
1278
1275
|
return {
|
|
1279
1276
|
status: "already_submitted",
|
|
1280
1277
|
repo: claimQueuedMessage(repo, queuedMessage.id),
|
|
1281
1278
|
};
|
|
1282
1279
|
}
|
|
1280
|
+
const submittedRunId = userCustom?.submittedRunId;
|
|
1281
|
+
const wasDirectlySubmitted = !queuedMessage &&
|
|
1282
|
+
typeof submittedRunId === "string" &&
|
|
1283
|
+
submittedRunId.trim().length > 0 &&
|
|
1284
|
+
Array.isArray(repo?.messages) &&
|
|
1285
|
+
repo.messages.some((entry) => {
|
|
1286
|
+
const message = getStoredMessage(entry);
|
|
1287
|
+
const custom = message?.metadata?.custom;
|
|
1288
|
+
return (message?.role === "user" &&
|
|
1289
|
+
custom &&
|
|
1290
|
+
typeof custom === "object" &&
|
|
1291
|
+
!Array.isArray(custom) &&
|
|
1292
|
+
custom.submittedRunId === submittedRunId);
|
|
1293
|
+
});
|
|
1294
|
+
if (wasDirectlySubmitted) {
|
|
1295
|
+
return { status: "already_submitted", repo };
|
|
1296
|
+
}
|
|
1297
|
+
if (userCustom &&
|
|
1298
|
+
Object.prototype.hasOwnProperty.call(userCustom, AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY)) {
|
|
1299
|
+
const submissionExpiresAt = userCustom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY];
|
|
1300
|
+
if (typeof submissionExpiresAt !== "number" ||
|
|
1301
|
+
!Number.isFinite(submissionExpiresAt) ||
|
|
1302
|
+
submissionExpiresAt <= Date.now()) {
|
|
1303
|
+
return { status: "submission_expired" };
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
if (!queuedMessage) {
|
|
1307
|
+
return { status: "submitted", repo: upsertUserMessage(repo, userMessage) };
|
|
1308
|
+
}
|
|
1283
1309
|
if (hasClaimedQueuedMessage(repo, queuedMessage.id)) {
|
|
1284
1310
|
return { status: "already_claimed" };
|
|
1285
1311
|
}
|
|
@@ -3136,6 +3162,11 @@ export function buildUserMessage(opts) {
|
|
|
3136
3162
|
...(opts.queuedMessageId
|
|
3137
3163
|
? { agentNativeQueuedMessageId: opts.queuedMessageId }
|
|
3138
3164
|
: {}),
|
|
3165
|
+
...(Object.prototype.hasOwnProperty.call(opts, "submissionExpiresAt")
|
|
3166
|
+
? {
|
|
3167
|
+
[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY]: opts.submissionExpiresAt,
|
|
3168
|
+
}
|
|
3169
|
+
: {}),
|
|
3139
3170
|
...(opts.refusedRetry ? { [RUN_NOT_STARTED_METADATA_KEY]: true } : {}),
|
|
3140
3171
|
},
|
|
3141
3172
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDbExec } from "../db/client.js";
|
|
2
2
|
import { getAppPermissionOverrides, getRegisteredAppRoles, resolveAppRole, } from "../org/app-roles.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isWorkspaceAppAccessUnavailable, WORKSPACE_APP_ACCESS_UNAVAILABLE_MESSAGE, } from "../org/workspace-app-access-outcome.js";
|
|
4
|
+
import { checkWorkspaceAppAccess } from "../org/workspace-app-access.js";
|
|
4
5
|
import { resolveWorkspaceAccessAppId } from "../org/workspace-app-identity.js";
|
|
5
6
|
import { ForbiddenError, resolveAccess } from "../sharing/access.js";
|
|
6
7
|
import { ROLE_RANK } from "../sharing/schema.js";
|
|
@@ -66,12 +67,12 @@ export async function checkAction(config, args, ctx, target) {
|
|
|
66
67
|
}
|
|
67
68
|
const workspaceAppId = resolveWorkspaceAccessAppId(appId);
|
|
68
69
|
const appAllowed = workspaceAppId
|
|
69
|
-
? await
|
|
70
|
+
? await checkWorkspaceAppAccess(workspaceAppId, {
|
|
70
71
|
email: identity.userEmail,
|
|
71
72
|
orgId: identity.orgId,
|
|
72
73
|
})
|
|
73
74
|
: true;
|
|
74
|
-
if (appAllowed
|
|
75
|
+
if (isWorkspaceAppAccessUnavailable(appAllowed)) {
|
|
75
76
|
throw Object.assign(new Error(WORKSPACE_APP_ACCESS_UNAVAILABLE_MESSAGE), {
|
|
76
77
|
statusCode: 503,
|
|
77
78
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import { parseQueueMessageInput, } from "@agent-native/agentkit/protocol";
|
|
2
|
+
import { AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY, parseQueueMessageInput, } from "@agent-native/agentkit/protocol";
|
|
3
|
+
import { normalizeSubmissionDeadlineMetadata } from "../agent/submission-deadline.js";
|
|
3
4
|
import { extractThreadMeta, mergeThreadDataForClientSave, normalizeThreadRepository, normalizeThreadTitle, } from "../agent/thread-data-builder.js";
|
|
4
5
|
import { failedRunIdsWithError, isRenderedRunNotStartedNotice, projectRootAssistantMessages, threadMessageRecord, } from "../agent/thread-message-projection.js";
|
|
5
6
|
import { getDbExec } from "../db/client.js";
|
|
@@ -1259,9 +1260,30 @@ export async function setThreadEngineMeta(threadId, meta) {
|
|
|
1259
1260
|
});
|
|
1260
1261
|
}
|
|
1261
1262
|
const QUEUED_MESSAGE_CLAIM_TTL_MS = 60_000;
|
|
1263
|
+
function queuedSubmissionExpired(message, now) {
|
|
1264
|
+
const custom = message.metadata?.custom;
|
|
1265
|
+
if (!custom ||
|
|
1266
|
+
typeof custom !== "object" ||
|
|
1267
|
+
Array.isArray(custom) ||
|
|
1268
|
+
!Object.prototype.hasOwnProperty.call(custom, AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY)) {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
const expiresAt = custom[AGENT_NATIVE_SUBMISSION_EXPIRES_AT_METADATA_KEY];
|
|
1272
|
+
return (typeof expiresAt !== "number" ||
|
|
1273
|
+
!Number.isFinite(expiresAt) ||
|
|
1274
|
+
expiresAt <= now);
|
|
1275
|
+
}
|
|
1262
1276
|
/** Applies a queue operation to the latest durable thread state on every CAS retry. */
|
|
1263
1277
|
export async function mutateThreadQueuedMessages(threadId, mutation) {
|
|
1264
1278
|
if (mutation.type === "append") {
|
|
1279
|
+
const metadata = normalizeSubmissionDeadlineMetadata(mutation.message.metadata, Date.now());
|
|
1280
|
+
mutation = {
|
|
1281
|
+
type: "append",
|
|
1282
|
+
message: {
|
|
1283
|
+
...mutation.message,
|
|
1284
|
+
...(metadata ? { metadata } : {}),
|
|
1285
|
+
},
|
|
1286
|
+
};
|
|
1265
1287
|
try {
|
|
1266
1288
|
assertNoInlineImageBytes({ type: "file", metadata: mutation.message.metadata }, "queuedMessage.metadata");
|
|
1267
1289
|
}
|
|
@@ -1304,6 +1326,10 @@ export async function mutateThreadQueuedMessages(threadId, mutation) {
|
|
|
1304
1326
|
let response = {};
|
|
1305
1327
|
switch (mutation.type) {
|
|
1306
1328
|
case "append": {
|
|
1329
|
+
if (queuedSubmissionExpired(mutation.message, Date.now())) {
|
|
1330
|
+
response = { submissionExpired: true };
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1307
1333
|
const existing = current.find((message) => message.id === mutation.message.id);
|
|
1308
1334
|
const { promotionClaim: _claim, ...existingMessage } = existing ?? {};
|
|
1309
1335
|
if (existing &&
|
|
@@ -1320,7 +1346,8 @@ export async function mutateThreadQueuedMessages(threadId, mutation) {
|
|
|
1320
1346
|
const index = current.findIndex((message) => message.id === mutation.messageId);
|
|
1321
1347
|
const message = current[index];
|
|
1322
1348
|
if (message?.promotionClaim &&
|
|
1323
|
-
message.promotionClaim.expiresAt > Date.now()
|
|
1349
|
+
message.promotionClaim.expiresAt > Date.now() &&
|
|
1350
|
+
!queuedSubmissionExpired(message, Date.now())) {
|
|
1324
1351
|
response = { promotionBusy: true };
|
|
1325
1352
|
break;
|
|
1326
1353
|
}
|
|
@@ -1351,6 +1378,11 @@ export async function mutateThreadQueuedMessages(threadId, mutation) {
|
|
|
1351
1378
|
}
|
|
1352
1379
|
const message = current[index];
|
|
1353
1380
|
const now = Date.now();
|
|
1381
|
+
if (queuedSubmissionExpired(message, now)) {
|
|
1382
|
+
queuedMessages = current.filter((candidate) => candidate.id !== mutation.messageId);
|
|
1383
|
+
response = { submissionExpired: true };
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1354
1386
|
if (message.promotionClaim &&
|
|
1355
1387
|
message.promotionClaim.id !== mutation.claimId &&
|
|
1356
1388
|
message.promotionClaim.expiresAt > now) {
|
|
@@ -42,6 +42,8 @@ export interface AgentChatMessage {
|
|
|
42
42
|
* {@link AGENT_CHAT_SUBMIT_RESULT_EVENT}. Auto-generated if omitted.
|
|
43
43
|
*/
|
|
44
44
|
submitMessageId?: string;
|
|
45
|
+
/** Drop a queued submit instead of handing it to chat after this time. */
|
|
46
|
+
submissionExpiresAt?: number;
|
|
45
47
|
usageLabel?: string;
|
|
46
48
|
approvedToolCalls?: string[];
|
|
47
49
|
}
|
|
@@ -176,6 +178,7 @@ export interface ParsedSubmitChat {
|
|
|
176
178
|
attachments?: AgentChatAttachment[];
|
|
177
179
|
requestMode?: AgentChatRequestMode;
|
|
178
180
|
submitMessageId?: string;
|
|
181
|
+
submissionExpiresAt?: number;
|
|
179
182
|
usageLabel?: string;
|
|
180
183
|
approvedToolCalls?: string[];
|
|
181
184
|
}
|
|
@@ -747,11 +747,17 @@ export function parseSubmitChatMessage(event) {
|
|
|
747
747
|
].filter((image) => typeof image === "string" && image.trim().length > 0);
|
|
748
748
|
const images = imageSources.length > 0 ? [...new Set(imageSources)] : undefined;
|
|
749
749
|
const hasActionScope = Object.prototype.hasOwnProperty.call(raw, "actionScope");
|
|
750
|
+
const hasSubmissionExpiresAt = Object.prototype.hasOwnProperty.call(raw, "submissionExpiresAt");
|
|
750
751
|
const actionScope = hasActionScope
|
|
751
752
|
? tryNormalizeAgentActionScope(raw.actionScope)
|
|
752
753
|
: undefined;
|
|
753
754
|
if (hasActionScope && !actionScope)
|
|
754
755
|
return null;
|
|
756
|
+
if (hasSubmissionExpiresAt &&
|
|
757
|
+
(typeof raw.submissionExpiresAt !== "number" ||
|
|
758
|
+
!Number.isFinite(raw.submissionExpiresAt))) {
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
755
761
|
return {
|
|
756
762
|
message,
|
|
757
763
|
context: typeof raw.context === "string" ? raw.context : undefined,
|
|
@@ -771,6 +777,9 @@ export function parseSubmitChatMessage(event) {
|
|
|
771
777
|
attachments: parseSubmitChatAttachments(raw.attachments),
|
|
772
778
|
requestMode: normalizeAgentChatRequestMode(raw.requestMode ?? raw.mode),
|
|
773
779
|
submitMessageId: typeof raw.submitMessageId === "string" ? raw.submitMessageId : undefined,
|
|
780
|
+
...(hasSubmissionExpiresAt
|
|
781
|
+
? { submissionExpiresAt: raw.submissionExpiresAt }
|
|
782
|
+
: {}),
|
|
774
783
|
usageLabel: nonEmptyString(raw.usageLabel),
|
|
775
784
|
approvedToolCalls: parseSubmitChatApprovedToolCalls(raw.approvedToolCalls),
|
|
776
785
|
};
|