@agent-native/core 0.186.0 → 0.187.0-nightly-20260923203303
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/corpus/README.md +1 -1
- package/corpus/templates/clips/app/components/library/page-header.tsx +5 -37
- package/corpus/templates/clips/app/i18n/en-US.ts +0 -1
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/actions/get-ai-priority.ts +26 -9
- package/corpus/templates/mail/actions/get-jev-availability.ts +23 -0
- package/corpus/templates/mail/actions/navigate.ts +19 -2
- package/corpus/templates/mail/app/components/email/EmailList.tsx +13 -4
- package/corpus/templates/mail/app/pages/InboxPage.tsx +47 -6
- package/corpus/templates/mail/changelog/2026-09-22-jev-powered-mail-rules-and-priority-sort-work-with-an-enable.md +6 -0
- package/corpus/templates/mail/changelog/2026-09-23-preserve-priority-sort-on-availability-error.md +6 -0
- package/corpus/templates/mail/server/lib/automation-engine.ts +147 -132
- package/corpus/templates/mail/server/lib/automation-model.ts +32 -1
- package/dist/agent/jev-tool-prefetch.d.ts +27 -3
- package/dist/agent/jev-tool-prefetch.js +103 -23
- package/dist/agent/production-agent.d.ts +9 -0
- package/dist/agent/production-agent.js +55 -23
- package/dist/app-config/integrations.d.ts +1 -0
- package/dist/app-config/integrations.js +7 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/integrations/a2a-continuation-processor.js +35 -3
- package/dist/integrations/a2a-continuations-store.d.ts +2 -0
- package/dist/integrations/a2a-continuations-store.js +44 -2
- package/dist/integrations/integration-campaign-recovery.js +13 -2
- package/dist/integrations/integration-campaigns-store.d.ts +1 -0
- package/dist/integrations/integration-campaigns-store.js +18 -0
- package/dist/integrations/integration-durable-dispatch-config.js +2 -8
- package/dist/integrations/integration-durable-dispatch.d.ts +2 -0
- package/dist/integrations/integration-durable-dispatch.js +20 -4
- package/dist/integrations/plugin.js +11 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/prompt-resources.d.ts +1 -0
- package/dist/server/agent-chat/prompt-resources.js +5 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +4 -3
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/package.json +3 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getBuilderProxyOrigin, } from "../server/credential-provider.js";
|
|
1
2
|
import { getBuilderGatewayRequestHeaders } from "./engine/builder-gateway-headers.js";
|
|
2
3
|
import { searchToolRegistry, TOOL_SEARCH_ACTION_NAME } from "./tool-search.js";
|
|
3
4
|
const MAX_JEV_CANDIDATES = 128;
|
|
@@ -5,7 +6,6 @@ const DEFAULT_PREFETCH_LIMIT = 3;
|
|
|
5
6
|
const MAX_PREFETCH_LIMIT = 5;
|
|
6
7
|
const JEV_TIMEOUT_MS = 750;
|
|
7
8
|
const JEV_MODEL = "jev-latest";
|
|
8
|
-
export const BUILDER_JEV_PROXY_ENABLED = true;
|
|
9
9
|
/**
|
|
10
10
|
* Rank a bounded metadata-only catalog with Jev. A missing key, malformed
|
|
11
11
|
* response, timeout, or provider failure returns no ranking so callers keep
|
|
@@ -13,8 +13,8 @@ export const BUILDER_JEV_PROXY_ENABLED = true;
|
|
|
13
13
|
*/
|
|
14
14
|
export async function rankJevCandidates(options) {
|
|
15
15
|
const request = options.request.trim();
|
|
16
|
-
const apiKey = options.
|
|
17
|
-
const builderAuth =
|
|
16
|
+
const apiKey = options.personalApiKey?.trim();
|
|
17
|
+
const builderAuth = options.builderAuth;
|
|
18
18
|
if (!request ||
|
|
19
19
|
(!apiKey && !builderAuth) ||
|
|
20
20
|
options.candidates.length === 0) {
|
|
@@ -51,6 +51,7 @@ export async function rankJevCandidates(options) {
|
|
|
51
51
|
};
|
|
52
52
|
const response = await requestJev({
|
|
53
53
|
apiKey,
|
|
54
|
+
personalApiKey: options.personalApiKey,
|
|
54
55
|
builderAuth,
|
|
55
56
|
request: jevRequest,
|
|
56
57
|
});
|
|
@@ -111,8 +112,8 @@ export function shortlistJevCandidates(request, candidates) {
|
|
|
111
112
|
*/
|
|
112
113
|
export async function preloadJevTools(options) {
|
|
113
114
|
const request = options.request.trim();
|
|
114
|
-
const apiKey = options.
|
|
115
|
-
const builderAuth =
|
|
115
|
+
const apiKey = options.personalApiKey?.trim();
|
|
116
|
+
const builderAuth = options.builderAuth;
|
|
116
117
|
if (!request || (!apiKey && !builderAuth)) {
|
|
117
118
|
return options.initialTools;
|
|
118
119
|
}
|
|
@@ -142,6 +143,7 @@ export async function preloadJevTools(options) {
|
|
|
142
143
|
const selectedNames = await rankJevCandidates({
|
|
143
144
|
request,
|
|
144
145
|
apiKey,
|
|
146
|
+
personalApiKey: options.personalApiKey,
|
|
145
147
|
candidates: candidates.map((candidate) => ({
|
|
146
148
|
id: candidate.name,
|
|
147
149
|
description: candidate.description,
|
|
@@ -158,63 +160,141 @@ export async function preloadJevTools(options) {
|
|
|
158
160
|
: options.initialTools;
|
|
159
161
|
}
|
|
160
162
|
async function requestJev(options) {
|
|
161
|
-
|
|
163
|
+
const personalApiKey = options.personalApiKey?.trim();
|
|
164
|
+
if (options.builderAuth) {
|
|
162
165
|
try {
|
|
163
166
|
return await requestJevThroughBuilder(options.builderAuth, options.request);
|
|
164
167
|
}
|
|
165
168
|
catch (error) {
|
|
166
|
-
if (!
|
|
169
|
+
if (!personalApiKey)
|
|
167
170
|
throw error;
|
|
168
171
|
console.warn("[agent] Builder Jev proxy unavailable; falling back to the direct Jev API.", error instanceof Error ? error.message : "unknown error");
|
|
172
|
+
return requestJevDirect(personalApiKey, options.request);
|
|
169
173
|
}
|
|
170
174
|
}
|
|
171
|
-
if (!
|
|
175
|
+
if (!personalApiKey) {
|
|
172
176
|
throw new Error("Builder Jev proxy is unavailable.");
|
|
173
177
|
}
|
|
178
|
+
return requestJevDirect(personalApiKey, options.request);
|
|
179
|
+
}
|
|
180
|
+
async function requestJevDirect(apiKey, request) {
|
|
174
181
|
const { choice, TypeSafeClient } = await import("@typesafe-ai/sdk");
|
|
175
182
|
const client = new TypeSafeClient({
|
|
176
|
-
apiKey
|
|
183
|
+
apiKey,
|
|
177
184
|
timeout: JEV_TIMEOUT_MS,
|
|
178
185
|
retry: { maxRetries: 0 },
|
|
179
186
|
});
|
|
180
187
|
const systemOne = client.systemOne.bind(client);
|
|
181
188
|
return (await systemOne({
|
|
182
|
-
...
|
|
189
|
+
...request,
|
|
183
190
|
questions: {
|
|
184
|
-
[Object.keys(
|
|
191
|
+
[Object.keys(request.questions)[0]]: choice(Object.values(request.questions)[0].instructions, Object.values(request.questions)[0].criteria),
|
|
185
192
|
},
|
|
186
193
|
}));
|
|
187
194
|
}
|
|
188
|
-
async function requestJevThroughBuilder(auth, request) {
|
|
189
|
-
const { getBuilderProxyOrigin } = await import("../server/credential-provider.js");
|
|
195
|
+
export async function requestJevThroughBuilder(auth, request, options = {}) {
|
|
190
196
|
const controller = new AbortController();
|
|
191
|
-
const
|
|
197
|
+
const signal = options.signal
|
|
198
|
+
? AbortSignal.any([options.signal, controller.signal])
|
|
199
|
+
: controller.signal;
|
|
200
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? JEV_TIMEOUT_MS);
|
|
192
201
|
try {
|
|
193
|
-
|
|
194
|
-
|
|
202
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
203
|
+
signal.throwIfAborted();
|
|
204
|
+
const response = await fetch(`${getBuilderProxyOrigin().replace(/\/+$/, "")}/agent-native/jev/v1/system-one`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: {
|
|
207
|
+
"Content-Type": "application/json",
|
|
208
|
+
Authorization: auth.authorization,
|
|
209
|
+
...(auth.spaceId ? { "x-builder-api-key": auth.spaceId } : {}),
|
|
210
|
+
...(auth.userId ? { "x-builder-user-id": auth.userId } : {}),
|
|
211
|
+
...getBuilderGatewayRequestHeaders(),
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify(request),
|
|
214
|
+
signal,
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
if (attempt === 0 && [429, 529].includes(response.status)) {
|
|
218
|
+
await response.body?.cancel().catch(() => undefined);
|
|
219
|
+
await waitForJevRetry(signal);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(`Builder Jev proxy returned HTTP ${response.status}.`);
|
|
223
|
+
}
|
|
224
|
+
const result = (await response.json());
|
|
225
|
+
if (!isJevResponse(result)) {
|
|
226
|
+
throw new Error("Builder Jev proxy returned an invalid response.");
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
throw new Error("Builder Jev proxy request failed.");
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
clearTimeout(timeout);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function waitForJevRetry(signal) {
|
|
237
|
+
return new Promise((resolve, reject) => {
|
|
238
|
+
const onAbort = () => {
|
|
239
|
+
clearTimeout(timer);
|
|
240
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
241
|
+
};
|
|
242
|
+
const timer = setTimeout(() => {
|
|
243
|
+
signal.removeEventListener("abort", onAbort);
|
|
244
|
+
resolve();
|
|
245
|
+
}, 200);
|
|
246
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
247
|
+
if (signal.aborted)
|
|
248
|
+
onAbort();
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
export async function isBuilderJevEnabled(auth, options = {}) {
|
|
252
|
+
const controller = new AbortController();
|
|
253
|
+
const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? 3_000);
|
|
254
|
+
try {
|
|
255
|
+
const response = await fetch(`${getBuilderProxyOrigin().replace(/\/+$/, "")}/agent-native/jev/v1/status`, {
|
|
256
|
+
method: "GET",
|
|
195
257
|
headers: {
|
|
196
|
-
"Content-Type": "application/json",
|
|
197
258
|
Authorization: auth.authorization,
|
|
198
259
|
...(auth.spaceId ? { "x-builder-api-key": auth.spaceId } : {}),
|
|
199
260
|
...(auth.userId ? { "x-builder-user-id": auth.userId } : {}),
|
|
200
261
|
...getBuilderGatewayRequestHeaders(),
|
|
201
262
|
},
|
|
202
|
-
|
|
203
|
-
|
|
263
|
+
signal: options.signal
|
|
264
|
+
? AbortSignal.any([options.signal, controller.signal])
|
|
265
|
+
: controller.signal,
|
|
204
266
|
});
|
|
267
|
+
if (response.status === 403)
|
|
268
|
+
return false;
|
|
205
269
|
if (!response.ok) {
|
|
206
|
-
throw new Error(`Builder Jev
|
|
270
|
+
throw new Error(`Builder Jev status returned HTTP ${response.status}.`);
|
|
207
271
|
}
|
|
208
272
|
const result = (await response.json());
|
|
209
|
-
if (
|
|
210
|
-
|
|
273
|
+
if (typeof result !== "object" ||
|
|
274
|
+
result === null ||
|
|
275
|
+
!("enabled" in result) ||
|
|
276
|
+
typeof result.enabled !== "boolean") {
|
|
277
|
+
throw new Error("Builder Jev status returned an invalid response.");
|
|
211
278
|
}
|
|
212
|
-
return result;
|
|
279
|
+
return result.enabled;
|
|
213
280
|
}
|
|
214
281
|
finally {
|
|
215
282
|
clearTimeout(timeout);
|
|
216
283
|
}
|
|
217
284
|
}
|
|
285
|
+
export async function isJevEnabled(credentials) {
|
|
286
|
+
if (credentials.personalApiKey)
|
|
287
|
+
return true;
|
|
288
|
+
if (credentials.builderAuth) {
|
|
289
|
+
const enabled = await isBuilderJevEnabled(credentials.builderAuth);
|
|
290
|
+
if (enabled || !credentials.apiKeyLookupFailed)
|
|
291
|
+
return enabled;
|
|
292
|
+
}
|
|
293
|
+
if (credentials.apiKeyLookupFailed || credentials.builderAuthLookupFailed) {
|
|
294
|
+
throw new Error("Could not check Jev credentials or Builder entitlement.");
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
218
298
|
function isJevResponse(value) {
|
|
219
299
|
return (typeof value === "object" &&
|
|
220
300
|
value !== null &&
|
|
@@ -4,6 +4,7 @@ import type { Task } from "../a2a/types.js";
|
|
|
4
4
|
import { type ActionAutomationContext, type ActionCaller } from "../action.js";
|
|
5
5
|
import { type ArtifactReceipt } from "../artifacts/detect.js";
|
|
6
6
|
import type { AgentNativeHarnessSetting } from "../config.js";
|
|
7
|
+
import { type BuilderGatewayAuth } from "../server/credential-provider.js";
|
|
7
8
|
import { fireInternalDispatch } from "../server/self-dispatch.js";
|
|
8
9
|
import { type ReasoningEffort } from "../shared/reasoning-effort.js";
|
|
9
10
|
import { type ResolveEngineConfig } from "./engine/index.js";
|
|
@@ -115,6 +116,14 @@ export declare function getOwnerApiKey(provider: string, ownerEmail: string | nu
|
|
|
115
116
|
* staleness without adding a secret-store read to every request.
|
|
116
117
|
*/
|
|
117
118
|
export declare function getOwnerJevApiKey(ownerEmail: string | null | undefined): Promise<string | undefined>;
|
|
119
|
+
export interface JevContextCredentials {
|
|
120
|
+
apiKey: string | undefined;
|
|
121
|
+
personalApiKey: string | undefined;
|
|
122
|
+
builderAuth: BuilderGatewayAuth | null;
|
|
123
|
+
apiKeyLookupFailed?: boolean;
|
|
124
|
+
builderAuthLookupFailed?: boolean;
|
|
125
|
+
}
|
|
126
|
+
export declare function getJevContextCredentials(ownerEmail: string | null | undefined): Promise<JevContextCredentials>;
|
|
118
127
|
/**
|
|
119
128
|
* Derive the provider name from the active engine setting.
|
|
120
129
|
* "ai-sdk:openai" → "openai", "anthropic" → "anthropic"
|
|
@@ -47,7 +47,7 @@ import { loadPriorTurnToolCallJournal } from "./engine/tool-call-journal-seed.js
|
|
|
47
47
|
import { backfillEngineMessagesToolResults, stringifyToolUseInputForGateway, unmatchedToolResultReplayText, } from "./engine/translate-anthropic.js";
|
|
48
48
|
import { EngineError } from "./engine/types.js";
|
|
49
49
|
import { filterHostedHarnessToolNames, normalizeHostedHarnessRuntime, } from "./harness/hosted.js";
|
|
50
|
-
import {
|
|
50
|
+
import { preloadJevTools } from "./jev-tool-prefetch.js";
|
|
51
51
|
import { getDefaultMaxIterations, getDefaultMaxRunInputTokens, MAX_AGENT_MAX_ITERATIONS, MIN_AGENT_MAX_ITERATIONS, normalizeMaxIterations, normalizeMaxRunInputTokens, readAgentLoopSettings, } from "./loop-settings.js";
|
|
52
52
|
import { getContextWindowForModel, resolveFallbackModel, } from "./model-config.js";
|
|
53
53
|
import { maybeCompactThread, buildObservationalContext, hasObservationalMemory, serializeObservationalMemoryBlock, } from "./observational-memory/index.js";
|
|
@@ -375,17 +375,31 @@ export async function getOwnerApiKey(provider, ownerEmail, options) {
|
|
|
375
375
|
* staleness without adding a secret-store read to every request.
|
|
376
376
|
*/
|
|
377
377
|
export async function getOwnerJevApiKey(ownerEmail) {
|
|
378
|
+
return (await getOwnerJevApiKeyCredential(ownerEmail)).credential?.apiKey;
|
|
379
|
+
}
|
|
380
|
+
async function getOwnerJevApiKeyCredential(ownerEmail) {
|
|
378
381
|
if (!ownerEmail)
|
|
379
|
-
return
|
|
382
|
+
return { credential: null, lookupFailed: false };
|
|
380
383
|
const cacheKey = [
|
|
381
|
-
"jev",
|
|
384
|
+
"jev-source-v1",
|
|
382
385
|
ownerEmail,
|
|
383
386
|
getRequestOrgId() ?? `solo:${ownerEmail}`,
|
|
384
387
|
getRequestContext()?.isSyntheticTraffic === true ? "synthetic" : "normal",
|
|
385
388
|
].join("\u0000");
|
|
386
389
|
const cached = readOptionalKeyCache(cacheKey);
|
|
387
|
-
if (cached.hit)
|
|
388
|
-
|
|
390
|
+
if (cached.hit) {
|
|
391
|
+
if (!cached.value)
|
|
392
|
+
return { credential: null, lookupFailed: false };
|
|
393
|
+
const separator = cached.value.indexOf(":");
|
|
394
|
+
const source = cached.value.slice(0, separator);
|
|
395
|
+
const apiKey = cached.value.slice(separator + 1);
|
|
396
|
+
return {
|
|
397
|
+
credential: (source === "user" || source === "deployment") && apiKey
|
|
398
|
+
? { apiKey, source }
|
|
399
|
+
: null,
|
|
400
|
+
lookupFailed: false,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
389
403
|
let lookupFailed = false;
|
|
390
404
|
const value = await getOwnerApiKey("jev", ownerEmail, {
|
|
391
405
|
onLookupFailure: () => {
|
|
@@ -394,11 +408,14 @@ export async function getOwnerJevApiKey(ownerEmail) {
|
|
|
394
408
|
});
|
|
395
409
|
if (value) {
|
|
396
410
|
if (!lookupFailed)
|
|
397
|
-
writeOptionalKeyCache(cacheKey, value);
|
|
398
|
-
return
|
|
411
|
+
writeOptionalKeyCache(cacheKey, `user:${value}`);
|
|
412
|
+
return {
|
|
413
|
+
credential: { apiKey: value, source: "user" },
|
|
414
|
+
lookupFailed,
|
|
415
|
+
};
|
|
399
416
|
}
|
|
400
417
|
if (lookupFailed)
|
|
401
|
-
return
|
|
418
|
+
return { credential: null, lookupFailed: true };
|
|
402
419
|
const deployKey = canUseDeployCredentialFallbackForRequest("JEV_API_KEY")
|
|
403
420
|
? readDeployCredentialEnv("JEV_API_KEY")?.trim()
|
|
404
421
|
: undefined;
|
|
@@ -407,24 +424,37 @@ export async function getOwnerJevApiKey(ownerEmail) {
|
|
|
407
424
|
key: "JEV_API_KEY",
|
|
408
425
|
value: deployKey,
|
|
409
426
|
}))) {
|
|
410
|
-
if (!lookupFailed)
|
|
411
|
-
writeOptionalKeyCache(cacheKey, deployKey);
|
|
412
|
-
|
|
427
|
+
if (!lookupFailed) {
|
|
428
|
+
writeOptionalKeyCache(cacheKey, `deployment:${deployKey}`);
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
credential: { apiKey: deployKey, source: "deployment" },
|
|
432
|
+
lookupFailed: false,
|
|
433
|
+
};
|
|
413
434
|
}
|
|
414
435
|
if (!lookupFailed)
|
|
415
436
|
writeOptionalKeyCache(cacheKey, undefined);
|
|
416
|
-
return
|
|
437
|
+
return { credential: null, lookupFailed: false };
|
|
417
438
|
}
|
|
418
|
-
async function getJevContextCredentials(ownerEmail) {
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
439
|
+
export async function getJevContextCredentials(ownerEmail) {
|
|
440
|
+
const requestContext = getRequestContext();
|
|
441
|
+
const [lookup, builderAuthLookup] = await Promise.all([
|
|
442
|
+
getOwnerJevApiKeyCredential(ownerEmail),
|
|
443
|
+
resolveBuilderGatewayAuth({
|
|
444
|
+
userEmail: ownerEmail,
|
|
445
|
+
orgId: requestContext?.orgScope === "personal" ? null : getRequestOrgId(),
|
|
446
|
+
}).then((builderAuth) => ({ builderAuth, lookupFailed: false }), () => ({ builderAuth: null, lookupFailed: true })),
|
|
447
|
+
]);
|
|
448
|
+
const credential = lookup.credential;
|
|
449
|
+
return {
|
|
450
|
+
apiKey: credential?.apiKey,
|
|
451
|
+
personalApiKey: credential?.source === "user" ? credential.apiKey : undefined,
|
|
452
|
+
builderAuth: builderAuthLookup.builderAuth,
|
|
453
|
+
...(lookup.lookupFailed ? { apiKeyLookupFailed: true } : {}),
|
|
454
|
+
...(builderAuthLookup.lookupFailed
|
|
455
|
+
? { builderAuthLookupFailed: true }
|
|
456
|
+
: {}),
|
|
457
|
+
};
|
|
428
458
|
}
|
|
429
459
|
/**
|
|
430
460
|
* Derive the provider name from the active engine setting.
|
|
@@ -8040,7 +8070,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8040
8070
|
presendCap("files", filesContextThunk, "", 12000),
|
|
8041
8071
|
presendCap("loopSettings", loopSettingsThunk, fallbackLoopSettings, 9000),
|
|
8042
8072
|
presendCap("enrichedMessage", enrichedMessageThunk, requestMessage, 9000),
|
|
8043
|
-
presendCap("jevContextCredentials", () => getJevContextCredentials(ownerEmail ?? getRequestUserEmail()), { apiKey: undefined, builderAuth: null }, 9000),
|
|
8073
|
+
presendCap("jevContextCredentials", () => getJevContextCredentials(ownerEmail ?? getRequestUserEmail()), { apiKey: undefined, personalApiKey: undefined, builderAuth: null }, 9000),
|
|
8044
8074
|
]);
|
|
8045
8075
|
setupMark("ctxAll");
|
|
8046
8076
|
// DIAGNOSTIC-ONLY: all parallel context gathering (system prompt, screen,
|
|
@@ -8086,6 +8116,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8086
8116
|
preloadJevTools({
|
|
8087
8117
|
request: requestMessage,
|
|
8088
8118
|
apiKey: jevContextCredentials.apiKey,
|
|
8119
|
+
personalApiKey: jevContextCredentials.personalApiKey,
|
|
8089
8120
|
builderAuth: jevContextCredentials.builderAuth,
|
|
8090
8121
|
registry: requestActions,
|
|
8091
8122
|
initialTools: curatedRequestTools,
|
|
@@ -8095,6 +8126,7 @@ export function createProductionAgentHandler(options) {
|
|
|
8095
8126
|
preloadJevContextForPrompt({
|
|
8096
8127
|
request: requestMessage,
|
|
8097
8128
|
apiKey: jevContextCredentials.apiKey,
|
|
8129
|
+
personalApiKey: jevContextCredentials.personalApiKey,
|
|
8098
8130
|
builderAuth: jevContextCredentials.builderAuth,
|
|
8099
8131
|
compact: options.jevContextCompact,
|
|
8100
8132
|
maxChars: jevContextMaxChars,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** Inbound integration webhook policy. */
|
|
3
3
|
export declare const integrationsConfig: z.ZodObject<{
|
|
4
|
+
durableDispatch: z.ZodOptional<z.ZodBoolean>;
|
|
4
5
|
allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
|
|
5
6
|
webhookBaseUrl: z.ZodOptional<z.ZodString>;
|
|
6
7
|
platforms: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
/** Inbound integration webhook policy. */
|
|
3
3
|
export const integrationsConfig = z.object({
|
|
4
|
+
durableDispatch: z
|
|
5
|
+
.boolean()
|
|
6
|
+
.optional()
|
|
7
|
+
.meta({
|
|
8
|
+
env: ["AGENT_INTEGRATION_DURABLE_DISPATCH"],
|
|
9
|
+
doc: "Use a durable background function for integration work. Explicit false stops an enabled rollout; unset leaves it unavailable.",
|
|
10
|
+
}),
|
|
4
11
|
allowUnverifiedWebhooks: z
|
|
5
12
|
.boolean()
|
|
6
13
|
.default(false)
|
|
@@ -76,6 +76,7 @@ export declare const appConfigSchema: z.ZodObject<{
|
|
|
76
76
|
requireEmailVerification: z.ZodOptional<z.ZodBoolean>;
|
|
77
77
|
}, z.core.$strip>>;
|
|
78
78
|
integrations: z.ZodPrefault<z.ZodObject<{
|
|
79
|
+
durableDispatch: z.ZodOptional<z.ZodBoolean>;
|
|
79
80
|
allowUnverifiedWebhooks: z.ZodDefault<z.ZodBoolean>;
|
|
80
81
|
webhookBaseUrl: z.ZodOptional<z.ZodString>;
|
|
81
82
|
platforms: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -186,8 +186,8 @@ export declare function createAutomationCallbackHandler(runtime: AutomationRunti
|
|
|
186
186
|
duplicate?: undefined;
|
|
187
187
|
eventId?: undefined;
|
|
188
188
|
} | {
|
|
189
|
+
error?: undefined;
|
|
189
190
|
accepted: boolean;
|
|
190
191
|
duplicate: boolean;
|
|
191
192
|
eventId: string;
|
|
192
|
-
error?: undefined;
|
|
193
193
|
}>;
|
|
@@ -7,9 +7,9 @@ import { resolveArtifactBaseUrl } from "../server/agent-chat/action-filters-a2a.
|
|
|
7
7
|
import { withConfiguredAppBasePath } from "../server/app-base-path.js";
|
|
8
8
|
import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
|
|
9
9
|
import { resolveSelfDispatchBaseUrl } from "../server/self-dispatch.js";
|
|
10
|
-
import { claimA2AContinuation, claimA2AContinuationDelivery, claimDueA2AContinuations, failA2AContinuationsForIntegrationTask, failA2AContinuation, finalizeA2ATerminalHistory, getA2AContinuation, getA2AContinuationTaskOutcome, hasOnlyLegacyFailedA2AContinuationsForIntegrationTask, hasPendingConfirmedA2ADeliveryForIntegrationTask, listRecoverableA2AIntegrationTasks, recoverDueA2AContinuationIds, recordA2ATerminalDeliveryReceipt, retainA2AUnconfirmedDeliveryClaim, rescheduleA2AContinuation, saveA2AVerifiedArtifactCheckpoint, } from "./a2a-continuations-store.js";
|
|
10
|
+
import { claimA2AContinuation, claimA2AContinuationDelivery, claimDueA2AContinuations, failA2AContinuationsForIntegrationTask, failA2AContinuation, finalizeA2ATerminalHistory, getA2AContinuation, getA2AContinuationTaskOutcome, hasOnlyLegacyFailedA2AContinuationsForIntegrationTask, hasPendingConfirmedA2ADeliveryForIntegrationTask, listRecoverableA2AIntegrationTasks, deferA2AContinuationsForRuntime, recoverDueA2AContinuationIds, recordA2ATerminalDeliveryReceipt, retainA2AUnconfirmedDeliveryClaim, rescheduleA2AContinuation, pauseA2AContinuationForRuntime, saveA2AVerifiedArtifactCheckpoint, } from "./a2a-continuations-store.js";
|
|
11
11
|
import { completeIntegrationCampaignTaskAfterA2A, failDisabledIntegrationCampaignTask, failIntegrationCampaignTaskDeliveryContainment, getIntegrationCampaignForTask, } from "./integration-campaigns-store.js";
|
|
12
|
-
import { dispatchPendingIntegrationTask, isIntegrationDurableDispatchEnabledForTask, } from "./integration-durable-dispatch.js";
|
|
12
|
+
import { dispatchPendingIntegrationTask, integrationDurableDispatchRuntimeUnavailableReasons, isIntegrationDurableDispatchEnabledForTask, isIntegrationDurableDispatchExplicitlyDisabledForTask, } from "./integration-durable-dispatch.js";
|
|
13
13
|
import { signInternalToken } from "./internal-token.js";
|
|
14
14
|
import { getNextPendingTaskForThread, getPendingTask, } from "./pending-tasks-store.js";
|
|
15
15
|
import { getThreadMapping } from "./thread-mapping-store.js";
|
|
@@ -186,6 +186,7 @@ export async function recoverDueA2AContinuations(options) {
|
|
|
186
186
|
const candidateTasks = await listRecoverableA2AIntegrationTasks(200);
|
|
187
187
|
const eligibleTaskIds = [];
|
|
188
188
|
const confirmedHistoryTaskIds = [];
|
|
189
|
+
const unavailableTaskIds = [];
|
|
189
190
|
for (const task of candidateTasks) {
|
|
190
191
|
const enabled = isIntegrationDurableDispatchEnabledForTask({
|
|
191
192
|
platform: task.platform,
|
|
@@ -200,12 +201,22 @@ export async function recoverDueA2AContinuations(options) {
|
|
|
200
201
|
else if (task.hasPendingConfirmedDelivery) {
|
|
201
202
|
confirmedHistoryTaskIds.push(task.id);
|
|
202
203
|
}
|
|
203
|
-
else {
|
|
204
|
+
else if (isIntegrationDurableDispatchExplicitlyDisabledForTask({
|
|
205
|
+
platform: task.platform,
|
|
206
|
+
externalThreadId: task.externalThreadId,
|
|
207
|
+
platformContext: task.dispatchScope
|
|
208
|
+
? { channelId: task.dispatchScope }
|
|
209
|
+
: undefined,
|
|
210
|
+
})) {
|
|
204
211
|
await failDisabledDurableA2ATask(task);
|
|
205
212
|
}
|
|
213
|
+
else {
|
|
214
|
+
unavailableTaskIds.push(task.id);
|
|
215
|
+
}
|
|
206
216
|
if (eligibleTaskIds.length + confirmedHistoryTaskIds.length >= limit)
|
|
207
217
|
break;
|
|
208
218
|
}
|
|
219
|
+
await deferA2AContinuationsForRuntime(unavailableTaskIds, 2 * 60_000);
|
|
209
220
|
const ids = await recoverDueA2AContinuationIds(limit, eligibleTaskIds);
|
|
210
221
|
const remaining = Math.max(0, limit - ids.length);
|
|
211
222
|
const confirmedHistoryIds = remaining > 0 && confirmedHistoryTaskIds.length > 0
|
|
@@ -341,6 +352,18 @@ async function durableContinuationScopeStillEnabled(continuation) {
|
|
|
341
352
|
});
|
|
342
353
|
if (enabled)
|
|
343
354
|
return true;
|
|
355
|
+
if (task?.status === "processing" &&
|
|
356
|
+
!isIntegrationDurableDispatchExplicitlyDisabledForTask({
|
|
357
|
+
platform: task.platform,
|
|
358
|
+
externalThreadId: task.externalThreadId,
|
|
359
|
+
platformContext: task.dispatchScope
|
|
360
|
+
? { channelId: task.dispatchScope }
|
|
361
|
+
: undefined,
|
|
362
|
+
})) {
|
|
363
|
+
await pauseA2AContinuationForRuntime(continuation.id, continuation.attempts, RESCHEDULE_DELAY_MS);
|
|
364
|
+
console.warn(`[integrations] A2A continuation ${continuation.id} paused: durable dispatch runtime unavailable`, integrationDurableDispatchRuntimeUnavailableReasons());
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
344
367
|
if (await hasPendingConfirmedA2ADeliveryForIntegrationTask(continuation.integrationTaskId)) {
|
|
345
368
|
await failA2AContinuation(continuation.id, "Durable integration campaign was disabled before this continuation delivered");
|
|
346
369
|
await reconcileTerminalA2AParentIfDisabled(continuation.integrationTaskId);
|
|
@@ -393,6 +416,15 @@ export async function reconcileTerminalA2AParentIfDisabled(integrationTaskId) {
|
|
|
393
416
|
})) {
|
|
394
417
|
return false;
|
|
395
418
|
}
|
|
419
|
+
if (!isIntegrationDurableDispatchExplicitlyDisabledForTask({
|
|
420
|
+
platform: task.platform,
|
|
421
|
+
externalThreadId: task.externalThreadId,
|
|
422
|
+
platformContext: task.dispatchScope
|
|
423
|
+
? { channelId: task.dispatchScope }
|
|
424
|
+
: undefined,
|
|
425
|
+
})) {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
396
428
|
await failDisabledDurableA2ATask({
|
|
397
429
|
id: task.id,
|
|
398
430
|
platform: task.platform,
|
|
@@ -86,8 +86,10 @@ export interface RecoverableA2AIntegrationTask {
|
|
|
86
86
|
* can filter the canary in memory without an N+1 pending-task lookup loop.
|
|
87
87
|
*/
|
|
88
88
|
export declare function listRecoverableA2AIntegrationTasks(limit?: number): Promise<RecoverableA2AIntegrationTask[]>;
|
|
89
|
+
export declare function deferA2AContinuationsForRuntime(integrationTaskIds: string[], delayMs: number): Promise<void>;
|
|
89
90
|
export declare function claimA2AContinuationDelivery(id: string): Promise<A2AContinuation | null>;
|
|
90
91
|
export declare function rescheduleA2AContinuation(id: string, delayMs: number): Promise<void>;
|
|
92
|
+
export declare function pauseA2AContinuationForRuntime(id: string, claimedAttempts: number, delayMs: number): Promise<boolean>;
|
|
91
93
|
export declare function retainA2AUnconfirmedDeliveryClaim(id: string): Promise<void>;
|
|
92
94
|
export declare function saveA2AVerifiedArtifactCheckpoint(id: string, checkpoint: string): Promise<string | null>;
|
|
93
95
|
export declare function recordA2ATerminalDeliveryReceipt(id: string, kind: A2ATerminalDeliveryKind, historyPayload: A2ATerminalHistoryPayload, errorMessage?: string): Promise<A2AContinuation>;
|
|
@@ -544,7 +544,7 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
544
544
|
await ensureTable();
|
|
545
545
|
const now = Date.now();
|
|
546
546
|
const { rows } = await getDbExec().execute({
|
|
547
|
-
sql: `SELECT
|
|
547
|
+
sql: `SELECT c.integration_task_id, t.platform,
|
|
548
548
|
t.external_thread_id, t.dispatch_scope, t.status,
|
|
549
549
|
EXISTS (
|
|
550
550
|
SELECT 1 FROM integration_a2a_continuations receipt
|
|
@@ -562,7 +562,10 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
562
562
|
OR (c.status = 'delivering' AND
|
|
563
563
|
((c.terminal_delivery_confirmed_at IS NOT NULL AND c.next_check_at <= ?)
|
|
564
564
|
OR c.updated_at <= ?)))
|
|
565
|
-
|
|
565
|
+
GROUP BY c.integration_task_id, t.platform, t.external_thread_id,
|
|
566
|
+
t.dispatch_scope, t.status
|
|
567
|
+
ORDER BY has_pending_confirmed_delivery DESC,
|
|
568
|
+
MIN(c.next_check_at) ASC, c.integration_task_id ASC
|
|
566
569
|
LIMIT ?`,
|
|
567
570
|
args: [
|
|
568
571
|
now,
|
|
@@ -583,6 +586,32 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
583
586
|
Number(row.has_pending_confirmed_delivery ?? 0) === 1,
|
|
584
587
|
}));
|
|
585
588
|
}
|
|
589
|
+
export async function deferA2AContinuationsForRuntime(integrationTaskIds, delayMs) {
|
|
590
|
+
if (integrationTaskIds.length === 0)
|
|
591
|
+
return;
|
|
592
|
+
await ensureTable();
|
|
593
|
+
const now = Date.now();
|
|
594
|
+
const taskFilter = integrationTaskIds.map(() => "?").join(", ");
|
|
595
|
+
await getDbExec().execute({
|
|
596
|
+
sql: `UPDATE integration_a2a_continuations
|
|
597
|
+
SET next_check_at = ?, updated_at = ?
|
|
598
|
+
WHERE integration_task_id IN (${taskFilter})
|
|
599
|
+
AND terminal_delivery_confirmed_at IS NULL
|
|
600
|
+
AND ((status = 'pending' AND next_check_at <= ?)
|
|
601
|
+
OR (status = 'processing' AND
|
|
602
|
+
(updated_at <= ? OR next_check_at <= ?))
|
|
603
|
+
OR (status = 'delivering' AND updated_at <= ?))`,
|
|
604
|
+
args: [
|
|
605
|
+
now + delayMs,
|
|
606
|
+
now,
|
|
607
|
+
...integrationTaskIds,
|
|
608
|
+
now,
|
|
609
|
+
now - PROCESSING_STUCK_AFTER_MS,
|
|
610
|
+
now - PROCESSING_NEXT_CHECK_STALE_AFTER_MS,
|
|
611
|
+
now - PROCESSING_STUCK_AFTER_MS,
|
|
612
|
+
],
|
|
613
|
+
});
|
|
614
|
+
}
|
|
586
615
|
export async function claimA2AContinuationDelivery(id) {
|
|
587
616
|
await ensureTable();
|
|
588
617
|
const client = getDbExec();
|
|
@@ -608,6 +637,19 @@ export async function rescheduleA2AContinuation(id, delayMs) {
|
|
|
608
637
|
args: ["pending", now + delayMs, now, id],
|
|
609
638
|
});
|
|
610
639
|
}
|
|
640
|
+
export async function pauseA2AContinuationForRuntime(id, claimedAttempts, delayMs) {
|
|
641
|
+
await ensureTable();
|
|
642
|
+
const now = Date.now();
|
|
643
|
+
const result = await getDbExec().execute({
|
|
644
|
+
sql: `UPDATE integration_a2a_continuations
|
|
645
|
+
SET status = 'pending', attempts = attempts - 1,
|
|
646
|
+
next_check_at = ?, updated_at = ?
|
|
647
|
+
WHERE id = ? AND status = 'processing' AND attempts = ?
|
|
648
|
+
RETURNING id`,
|
|
649
|
+
args: [now + delayMs, now, id, claimedAttempts],
|
|
650
|
+
});
|
|
651
|
+
return (result.rows?.length ?? 0) > 0;
|
|
652
|
+
}
|
|
611
653
|
export async function retainA2AUnconfirmedDeliveryClaim(id) {
|
|
612
654
|
await ensureTable();
|
|
613
655
|
const now = Date.now();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getA2AContinuationTaskOutcome } from "./a2a-continuations-store.js";
|
|
2
|
-
import { getIntegrationCampaign, failDisabledIntegrationCampaignTask, listDueIntegrationCampaignIds, } from "./integration-campaigns-store.js";
|
|
3
|
-
import { dispatchPendingIntegrationTask, isIntegrationDurableDispatchEnabledForTask, } from "./integration-durable-dispatch.js";
|
|
2
|
+
import { getIntegrationCampaign, failDisabledIntegrationCampaignTask, listDueIntegrationCampaignIds, deferIntegrationCampaignForRuntime, } from "./integration-campaigns-store.js";
|
|
3
|
+
import { dispatchPendingIntegrationTask, isIntegrationDurableDispatchEnabledForTask, isIntegrationDurableDispatchExplicitlyDisabledForTask, } from "./integration-durable-dispatch.js";
|
|
4
4
|
import { getNextPendingTaskForThread, getPendingTask, } from "./pending-tasks-store.js";
|
|
5
5
|
function hasConfirmedDeliveryReceipt(payload) {
|
|
6
6
|
try {
|
|
@@ -46,6 +46,17 @@ export async function recoverDueIntegrationCampaigns(options) {
|
|
|
46
46
|
const confirmedReceipt = hasConfirmedDeliveryReceipt(task.payload) ||
|
|
47
47
|
(await getA2AContinuationTaskOutcome(task.id)) === "terminal-delivered";
|
|
48
48
|
if (!durableDispatchEnabled && !confirmedReceipt) {
|
|
49
|
+
if (!isIntegrationDurableDispatchExplicitlyDisabledForTask({
|
|
50
|
+
platform: task.platform,
|
|
51
|
+
externalThreadId: task.externalThreadId,
|
|
52
|
+
platformContext: task.dispatchScope
|
|
53
|
+
? { channelId: task.dispatchScope }
|
|
54
|
+
: undefined,
|
|
55
|
+
})) {
|
|
56
|
+
await deferIntegrationCampaignForRuntime(campaign.id, 60_000);
|
|
57
|
+
result.skipped += 1;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
49
60
|
await failDisabledIntegrationCampaignTask(task.id);
|
|
50
61
|
const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
|
|
51
62
|
if (nextTask) {
|
|
@@ -116,5 +116,6 @@ export declare function refreshIntegrationCampaignTaskA2AReceiptRetry(integratio
|
|
|
116
116
|
errorMessage: string;
|
|
117
117
|
}): Promise<boolean>;
|
|
118
118
|
export declare function completeIntegrationCampaignTaskAfterA2A(integrationTaskId: string): Promise<boolean>;
|
|
119
|
+
export declare function deferIntegrationCampaignForRuntime(id: string, delayMs: number): Promise<boolean>;
|
|
119
120
|
export declare function listDueIntegrationCampaignIds(limit?: number): Promise<string[]>;
|
|
120
121
|
export declare function hasActiveIntegrationCampaign(integrationTaskId: string): Promise<boolean>;
|
|
@@ -725,6 +725,24 @@ export async function completeIntegrationCampaignTaskAfterA2A(integrationTaskId)
|
|
|
725
725
|
}
|
|
726
726
|
throw new Error("Database does not support atomic A2A parent completion");
|
|
727
727
|
}
|
|
728
|
+
export async function deferIntegrationCampaignForRuntime(id, delayMs) {
|
|
729
|
+
await ensureTable();
|
|
730
|
+
const now = Date.now();
|
|
731
|
+
const nextRunAt = now + delayMs;
|
|
732
|
+
const result = await getDbExec().execute({
|
|
733
|
+
sql: `UPDATE integration_campaigns
|
|
734
|
+
SET next_run_at = ?,
|
|
735
|
+
lease_expires_at = CASE WHEN status = 'processing' THEN ? ELSE lease_expires_at END,
|
|
736
|
+
updated_at = ?
|
|
737
|
+
WHERE id = ? AND (
|
|
738
|
+
(status IN ('pending', 'waiting') AND next_run_at <= ?)
|
|
739
|
+
OR (status = 'processing' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?)
|
|
740
|
+
)
|
|
741
|
+
RETURNING id`,
|
|
742
|
+
args: [nextRunAt, nextRunAt, now, id, now, now],
|
|
743
|
+
});
|
|
744
|
+
return (result.rows?.length ?? 0) > 0;
|
|
745
|
+
}
|
|
728
746
|
export async function listDueIntegrationCampaignIds(limit = 25) {
|
|
729
747
|
await ensureTable();
|
|
730
748
|
const boundedLimit = Math.max(1, Math.min(Math.floor(limit), MAX_DUE_LIST_LIMIT));
|