@agent-native/core 0.206.0 → 0.206.1-nightly-20261009183700
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/default-agent-engine.js +25 -0
- package/dist/agent/durable-background.d.ts +2 -0
- package/dist/agent/durable-background.js +2 -0
- package/dist/agent/engine/error-detail.d.ts +1 -0
- package/dist/agent/engine/error-detail.js +6 -1
- package/dist/agent/engine/translate-anthropic.d.ts +9 -0
- package/dist/agent/engine/translate-anthropic.js +18 -3
- package/dist/agent/production-agent.d.ts +4 -4
- package/dist/agent/production-agent.js +78 -74
- package/dist/agent/run-store.js +15 -3
- package/dist/agent/thread-data-builder.js +1 -2
- package/dist/automation/index.d.ts +1 -1
- package/dist/chat-threads/store.js +6 -2
- package/dist/client/chat/agentkit-agent-native.js +11 -10
- package/dist/client/chat/agentkit-protocol.js +5 -1
- package/dist/client/chat/runtime.d.ts +3 -1
- package/dist/client/chat/runtime.js +101 -43
- package/dist/client/use-action.d.ts +1 -0
- package/dist/client/use-action.js +2 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +1 -1
- package/dist/deploy/workspace-deploy.js +65 -6
- package/dist/file-upload/actions/upload-image.d.ts +3 -3
- package/dist/file-upload/registry.d.ts +7 -1
- package/dist/file-upload/registry.js +25 -7
- package/dist/mcp/build-server.js +64 -26
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/metrics.d.ts +5 -0
- package/dist/observability/metrics.js +62 -7
- package/dist/observability/routes.d.ts +4 -4
- package/dist/org/handlers.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/index.d.ts +1 -1
- package/dist/secrets/index.js +1 -1
- package/dist/secrets/storage.d.ts +1 -0
- package/dist/secrets/storage.js +8 -0
- package/dist/server/action-routes.js +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +2 -1
- package/dist/server/agent-engine-api-key-route.js +66 -2
- package/dist/server/agent-engine-default-model-route.d.ts +2 -2
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/agent-run-context.js +21 -1
- package/dist/server/agent-teams-run-queue.js +2 -1
- package/dist/server/auth.js +1 -0
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/dist/server/core-routes-plugin.js +12 -0
- package/dist/server/http-response-telemetry.js +37 -8
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/inline-bytes.d.ts +29 -0
- package/dist/shared/inline-bytes.js +170 -0
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.js +1 -0
- package/dist/tracking/registry.d.ts +3 -2
- package/dist/tracking/registry.js +12 -3
- package/dist/triggers/actions/manage-automation.d.ts +3 -3
- package/dist/vite/client.js +6 -1
- package/docs/content/observability.mdx +15 -10
- package/docs/content/tracking.mdx +1 -1
- package/package.json +3 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
|
|
2
|
+
import { isInlineDataUrl, } from "@agent-native/agentkit/protocol";
|
|
2
3
|
import { AUTO_CONTINUE_OF_RUN_METADATA_KEY, CONTINUE_OF_RUN_METADATA_KEY, } from "../../agent/auto-continue.js";
|
|
3
4
|
import { agentEngineStatusUrlForChatApi, requireAgentEngineConfiguredForDispatch, } from "../agent-engine-readiness.js";
|
|
4
5
|
import { getOrCreateAnalyticsSessionId } from "../analytics-session.js";
|
|
@@ -667,12 +668,39 @@ export function createHttpAgentChatRuntime(options) {
|
|
|
667
668
|
};
|
|
668
669
|
return runtime;
|
|
669
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* What an earlier user turn's file or image becomes in replayed history: its
|
|
673
|
+
* name, type, and durable URL, so a later turn can still refer to it. Inline
|
|
674
|
+
* bytes are never replayed.
|
|
675
|
+
*/
|
|
676
|
+
function historyAttachmentStub(message, part) {
|
|
677
|
+
if (message.role !== "user")
|
|
678
|
+
return undefined;
|
|
679
|
+
if (part.type !== "file" && part.type !== "image")
|
|
680
|
+
return undefined;
|
|
681
|
+
const name = part.type === "file" ? part.filename : part.alt;
|
|
682
|
+
const url = typeof part.url === "string" && !isInlineDataUrl(part.url)
|
|
683
|
+
? part.url
|
|
684
|
+
: undefined;
|
|
685
|
+
return `[attached: ${[name || part.type, part.mediaType, url].filter(Boolean).join(" ")}]`;
|
|
686
|
+
}
|
|
687
|
+
/** Reasoning is the model's own scratch work and is never replayed as history. */
|
|
670
688
|
function runtimeMessageText(message) {
|
|
671
689
|
return message.content
|
|
672
|
-
.map((part) => part.type === "text"
|
|
690
|
+
.map((part) => part.type === "text"
|
|
691
|
+
? part.text
|
|
692
|
+
: (historyAttachmentStub(message, part) ?? ""))
|
|
673
693
|
.filter(Boolean)
|
|
674
694
|
.join("\n");
|
|
675
695
|
}
|
|
696
|
+
function isUserAsk(message) {
|
|
697
|
+
return (message.role === "user" &&
|
|
698
|
+
message.content.some((part) => (part.type === "text" && part.text.trim()) ||
|
|
699
|
+
historyAttachmentStub(message, part) !== undefined));
|
|
700
|
+
}
|
|
701
|
+
function hasHistoryAttachment(message) {
|
|
702
|
+
return message.content.some((part) => historyAttachmentStub(message, part) !== undefined);
|
|
703
|
+
}
|
|
676
704
|
function isSyntheticToolHistoryOmissionMessage(message) {
|
|
677
705
|
return (message.role === "assistant" &&
|
|
678
706
|
(message.id === AGENTKIT_TOOL_HISTORY_OMISSION_MESSAGE_ID_PREFIX ||
|
|
@@ -719,6 +747,9 @@ const MAX_STRUCTURED_HISTORY_TEXT_SOURCE_PARTS = MAX_TOOL_HISTORY_CALLS * 2;
|
|
|
719
747
|
const MAX_STRUCTURED_HISTORY_SOURCE_PARTS = MAX_STRUCTURED_HISTORY_TOOL_SOURCE_PARTS +
|
|
720
748
|
MAX_STRUCTURED_HISTORY_TEXT_SOURCE_PARTS;
|
|
721
749
|
const MAX_STRUCTURED_HISTORY_SOURCE_MESSAGES = 1024;
|
|
750
|
+
const MAX_PINNED_ATTACHMENT_PROMPTS = 32;
|
|
751
|
+
/** Above every other candidate, so the byte budget drops pinned asks last. */
|
|
752
|
+
const PINNED_USER_PROMPT_TEXT_PRIORITY = 4;
|
|
722
753
|
const MAX_STRUCTURED_HISTORY_SOURCE_SCAN_STEPS = MAX_STRUCTURED_HISTORY_SOURCE_PARTS * 4;
|
|
723
754
|
const TOOL_INPUT_OMISSION_TEXT = "Tool input omitted from history because it could not be serialized.";
|
|
724
755
|
const TOOL_INPUT_SIZE_OMISSION_TEXT = "Tool input omitted from history because it exceeds 64 KiB.";
|
|
@@ -1177,7 +1208,7 @@ function runtimeMessageTextMatches(message, expected) {
|
|
|
1177
1208
|
return { matches: true, complete: false };
|
|
1178
1209
|
}
|
|
1179
1210
|
scannedParts++;
|
|
1180
|
-
if (part.type !== "text"
|
|
1211
|
+
if (part.type !== "text")
|
|
1181
1212
|
continue;
|
|
1182
1213
|
if (!part.text)
|
|
1183
1214
|
continue;
|
|
@@ -1229,16 +1260,19 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1229
1260
|
}
|
|
1230
1261
|
}
|
|
1231
1262
|
let firstUserPromptMessageIndex;
|
|
1263
|
+
const attachmentPromptMessageIndices = [];
|
|
1232
1264
|
const historyWindowStart = Math.max(0, historyMessages.length - MAX_STRUCTURED_HISTORY_SOURCE_MESSAGES);
|
|
1233
1265
|
for (let index = historyWindowStart; index < historyMessages.length; index++) {
|
|
1234
1266
|
if (index === currentPromptMessageIndex)
|
|
1235
1267
|
continue;
|
|
1236
1268
|
const message = historyMessages[index];
|
|
1237
|
-
if (message.role
|
|
1238
|
-
|
|
1239
|
-
|
|
1269
|
+
if (message.role !== "user")
|
|
1270
|
+
continue;
|
|
1271
|
+
if (firstUserPromptMessageIndex === undefined && isUserAsk(message)) {
|
|
1240
1272
|
firstUserPromptMessageIndex = index;
|
|
1241
|
-
|
|
1273
|
+
}
|
|
1274
|
+
if (hasHistoryAttachment(message)) {
|
|
1275
|
+
attachmentPromptMessageIndices.push(index);
|
|
1242
1276
|
}
|
|
1243
1277
|
}
|
|
1244
1278
|
let previousUserPromptMessageIndex;
|
|
@@ -1246,25 +1280,43 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1246
1280
|
for (let index = historyMessages.length - 1; index >= 0; index--) {
|
|
1247
1281
|
if (index === currentPromptMessageIndex)
|
|
1248
1282
|
continue;
|
|
1249
|
-
|
|
1250
|
-
if (message.role === "user" &&
|
|
1251
|
-
message.content.some((part) => (part.type === "text" || part.type === "reasoning") &&
|
|
1252
|
-
part.text.trim())) {
|
|
1283
|
+
if (isUserAsk(historyMessages[index])) {
|
|
1253
1284
|
previousUserPromptMessageIndex = index;
|
|
1254
1285
|
break;
|
|
1255
1286
|
}
|
|
1256
1287
|
}
|
|
1257
1288
|
}
|
|
1258
|
-
|
|
1289
|
+
// The first ask ("LinkedIn ad 1200x627 PNG") and every turn that attached a
|
|
1290
|
+
// file stay in the window however long the agentic tail after them grows.
|
|
1291
|
+
// ponytail: only the newest 32 attachment turns are pinned; older ones fall
|
|
1292
|
+
// back to the newest-first window.
|
|
1293
|
+
const pinnedUserPromptMessageIndices = new Set([
|
|
1294
|
+
firstUserPromptMessageIndex,
|
|
1295
|
+
previousUserPromptMessageIndex,
|
|
1296
|
+
...attachmentPromptMessageIndices.slice(-MAX_PINNED_ATTACHMENT_PROMPTS),
|
|
1297
|
+
].filter((index) => index !== undefined));
|
|
1259
1298
|
const regularTextPartLimit = MAX_STRUCTURED_HISTORY_TEXT_SOURCE_PARTS -
|
|
1260
|
-
|
|
1299
|
+
pinnedUserPromptMessageIndices.size;
|
|
1261
1300
|
const selectedReversed = [];
|
|
1301
|
+
const pinnedPromptsAdded = new Set();
|
|
1262
1302
|
let selectedToolPartCount = 0;
|
|
1263
1303
|
let selectedTextPartCount = 0;
|
|
1264
1304
|
let scannedPartCount = 0;
|
|
1265
1305
|
let visitedMessageCount = 0;
|
|
1266
1306
|
let toolBoundary;
|
|
1267
1307
|
let stop = false;
|
|
1308
|
+
const addPinnedPromptText = (parts, messageIndex) => {
|
|
1309
|
+
pinnedPromptsAdded.add(messageIndex);
|
|
1310
|
+
const text = runtimeMessageText(historyMessages[messageIndex]);
|
|
1311
|
+
if (!text.trim())
|
|
1312
|
+
return;
|
|
1313
|
+
if (selectedTextPartCount >= MAX_STRUCTURED_HISTORY_TEXT_SOURCE_PARTS) {
|
|
1314
|
+
omitted = true;
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
selectedTextPartCount++;
|
|
1318
|
+
parts.push({ type: "text", text });
|
|
1319
|
+
};
|
|
1268
1320
|
const visitMessage = (message, list, messageIndex) => {
|
|
1269
1321
|
if (visitedMessageCount >= MAX_STRUCTURED_HISTORY_SOURCE_MESSAGES) {
|
|
1270
1322
|
omitted = true;
|
|
@@ -1280,12 +1332,12 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1280
1332
|
visitedMessageCount++;
|
|
1281
1333
|
if (message.role !== "user" && message.role !== "assistant")
|
|
1282
1334
|
return;
|
|
1283
|
-
const
|
|
1284
|
-
const isPreviousUserPrompt = list === "messages" && messageIndex === previousUserPromptMessageIndex;
|
|
1285
|
-
const isProtectedUserPrompt = isFirstUserPrompt || isPreviousUserPrompt;
|
|
1335
|
+
const isPinnedUserPrompt = list === "messages" && pinnedUserPromptMessageIndices.has(messageIndex);
|
|
1286
1336
|
const partsReversed = [];
|
|
1287
|
-
let protectedPromptTextAdded = false;
|
|
1288
1337
|
for (let partIndex = message.content.length - 1; partIndex >= 0; partIndex--) {
|
|
1338
|
+
const part = message.content[partIndex];
|
|
1339
|
+
if (part.type === "reasoning")
|
|
1340
|
+
continue;
|
|
1289
1341
|
if (scannedPartCount >= MAX_STRUCTURED_HISTORY_SOURCE_SCAN_STEPS) {
|
|
1290
1342
|
omitted = true;
|
|
1291
1343
|
toolHistoryOmitted = true;
|
|
@@ -1294,30 +1346,15 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1294
1346
|
break;
|
|
1295
1347
|
}
|
|
1296
1348
|
scannedPartCount++;
|
|
1297
|
-
const
|
|
1349
|
+
const attachmentStub = historyAttachmentStub(message, part);
|
|
1298
1350
|
const isToolPart = (part.type === "tool-call" && message.role === "assistant") ||
|
|
1299
1351
|
part.type === "tool-result";
|
|
1300
|
-
const isTextPart = part.type === "text" ||
|
|
1352
|
+
const isTextPart = part.type === "text" || attachmentStub !== undefined;
|
|
1301
1353
|
if (!isToolPart && !isTextPart)
|
|
1302
1354
|
continue;
|
|
1303
|
-
if (
|
|
1304
|
-
if (!
|
|
1305
|
-
|
|
1306
|
-
.filter((candidate) => candidate.type === "text" || candidate.type === "reasoning")
|
|
1307
|
-
.map((candidate) => candidate.type === "text" || candidate.type === "reasoning"
|
|
1308
|
-
? candidate.text
|
|
1309
|
-
: "")
|
|
1310
|
-
.join("\n");
|
|
1311
|
-
if (text.trim()) {
|
|
1312
|
-
if (selectedTextPartCount >= MAX_STRUCTURED_HISTORY_TEXT_SOURCE_PARTS) {
|
|
1313
|
-
omitted = true;
|
|
1314
|
-
}
|
|
1315
|
-
else {
|
|
1316
|
-
selectedTextPartCount++;
|
|
1317
|
-
partsReversed.push({ type: "text", text });
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
protectedPromptTextAdded = true;
|
|
1355
|
+
if (isPinnedUserPrompt && isTextPart) {
|
|
1356
|
+
if (!pinnedPromptsAdded.has(messageIndex)) {
|
|
1357
|
+
addPinnedPromptText(partsReversed, messageIndex);
|
|
1321
1358
|
}
|
|
1322
1359
|
continue;
|
|
1323
1360
|
}
|
|
@@ -1345,15 +1382,17 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1345
1382
|
}
|
|
1346
1383
|
selectedTextPartCount++;
|
|
1347
1384
|
}
|
|
1348
|
-
partsReversed.push(
|
|
1385
|
+
partsReversed.push(attachmentStub === undefined
|
|
1386
|
+
? part
|
|
1387
|
+
: { type: "text", text: attachmentStub });
|
|
1349
1388
|
}
|
|
1350
1389
|
if (partsReversed.length) {
|
|
1351
1390
|
selectedReversed.push({
|
|
1352
1391
|
message,
|
|
1353
1392
|
parts: partsReversed.reverse(),
|
|
1354
1393
|
priority: list === "supplemental",
|
|
1355
|
-
textPriority:
|
|
1356
|
-
?
|
|
1394
|
+
textPriority: isPinnedUserPrompt
|
|
1395
|
+
? PINNED_USER_PROMPT_TEXT_PRIORITY
|
|
1357
1396
|
: list === "supplemental"
|
|
1358
1397
|
? 1
|
|
1359
1398
|
: 0,
|
|
@@ -1370,6 +1409,22 @@ function boundedStructuredHistorySources(messages, currentPrompt, supplementalMe
|
|
|
1370
1409
|
continue;
|
|
1371
1410
|
visitMessage(historyMessages[index], "messages", index);
|
|
1372
1411
|
}
|
|
1412
|
+
// The newest-first scan stops long before the first turns of a long agentic
|
|
1413
|
+
// thread; the pinned asks it never reached are replayed anyway.
|
|
1414
|
+
for (const index of [...pinnedUserPromptMessageIndices].sort((left, right) => right - left)) {
|
|
1415
|
+
if (pinnedPromptsAdded.has(index))
|
|
1416
|
+
continue;
|
|
1417
|
+
const parts = [];
|
|
1418
|
+
addPinnedPromptText(parts, index);
|
|
1419
|
+
if (parts.length) {
|
|
1420
|
+
selectedReversed.push({
|
|
1421
|
+
message: historyMessages[index],
|
|
1422
|
+
parts,
|
|
1423
|
+
priority: false,
|
|
1424
|
+
textPriority: PINNED_USER_PROMPT_TEXT_PRIORITY,
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1373
1428
|
return {
|
|
1374
1429
|
messages: selectedReversed.reverse(),
|
|
1375
1430
|
omitted,
|
|
@@ -1520,7 +1575,7 @@ function nativeStructuredHistoryFromMessages(messages, currentPrompt, supplement
|
|
|
1520
1575
|
results = [];
|
|
1521
1576
|
};
|
|
1522
1577
|
for (const part of parts) {
|
|
1523
|
-
if (part.type === "text"
|
|
1578
|
+
if (part.type === "text") {
|
|
1524
1579
|
if (jsonStringByteLengthWithinLimit(part.text, MAX_ADDED_TOOL_HISTORY_BYTES) === undefined ||
|
|
1525
1580
|
part.text.trim()) {
|
|
1526
1581
|
flushResults();
|
|
@@ -2700,6 +2755,9 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2700
2755
|
// Only the continuation that names a stopped run continues it; an approval
|
|
2701
2756
|
// or connection answered later is not another continuation of that run.
|
|
2702
2757
|
const { [AUTO_CONTINUE_OF_RUN_METADATA_KEY]: _autoContinueOf, [CONTINUE_OF_RUN_METADATA_KEY]: _continueOf, ...previousMetadata } = previousTurn?.metadata ?? {};
|
|
2758
|
+
const turnToContinue = continuation.attachments?.length
|
|
2759
|
+
? { ...previousTurn, attachments: continuation.attachments }
|
|
2760
|
+
: previousTurn;
|
|
2703
2761
|
const approval = continuation.approval;
|
|
2704
2762
|
const connection = continuation.connection;
|
|
2705
2763
|
const messageStateKey = continuation.turnId ?? session.id;
|
|
@@ -2716,7 +2774,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2716
2774
|
if (messageState)
|
|
2717
2775
|
messageState.message.connectionPending = false;
|
|
2718
2776
|
return startTurn({
|
|
2719
|
-
...
|
|
2777
|
+
...turnToContinue,
|
|
2720
2778
|
prompt: continuation.prompt ??
|
|
2721
2779
|
`The ${connection.id} connection is now available. Continue the requested work.`,
|
|
2722
2780
|
metadata: {
|
|
@@ -2736,7 +2794,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2736
2794
|
if (messageState)
|
|
2737
2795
|
messageState.message.approvalPending = false;
|
|
2738
2796
|
return startTurn({
|
|
2739
|
-
...
|
|
2797
|
+
...turnToContinue,
|
|
2740
2798
|
prompt: continuation.prompt,
|
|
2741
2799
|
metadata: {
|
|
2742
2800
|
...previousMetadata,
|
|
@@ -2759,7 +2817,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
2759
2817
|
if (messageState)
|
|
2760
2818
|
messageState.message.approvalPending = false;
|
|
2761
2819
|
return startTurn({
|
|
2762
|
-
...
|
|
2820
|
+
...turnToContinue,
|
|
2763
2821
|
prompt: continuation.prompt ??
|
|
2764
2822
|
"Approved. Go ahead and run the requested action.",
|
|
2765
2823
|
metadata: {
|
|
@@ -110,5 +110,6 @@ export declare function useActionMutation<TData = undefined, TVariables = undefi
|
|
|
110
110
|
method?: "POST" | "PUT" | "DELETE";
|
|
111
111
|
skipActionQueryInvalidation?: boolean;
|
|
112
112
|
timeoutMs?: number;
|
|
113
|
+
headers?: Record<string, string> | ((variables: TVariables extends undefined ? ActionParams<TName> : TVariables) => Record<string, string> | undefined);
|
|
113
114
|
}): import("@tanstack/react-query").UseMutationResult<TData extends undefined ? ActionResult<TName> : TData, Error, TVariables extends undefined ? ActionParams<TName> : TVariables, unknown>;
|
|
114
115
|
export {};
|
|
@@ -782,12 +782,13 @@ export function useActionQuery(actionName, params, options) {
|
|
|
782
782
|
}
|
|
783
783
|
export function useActionMutation(actionName, options) {
|
|
784
784
|
const queryClient = useQueryClient();
|
|
785
|
-
const { method: methodOpt, onSuccess, skipActionQueryInvalidation = false, timeoutMs, ...restOptions } = options ?? {};
|
|
785
|
+
const { method: methodOpt, onSuccess, skipActionQueryInvalidation = false, timeoutMs, headers, ...restOptions } = options ?? {};
|
|
786
786
|
const method = methodOpt ?? "POST";
|
|
787
787
|
return useMutation({
|
|
788
788
|
...restOptions,
|
|
789
789
|
mutationFn: (params) => actionFetch(actionName, method, params, {
|
|
790
790
|
timeoutMs,
|
|
791
|
+
headers: typeof headers === "function" ? headers(params) : headers,
|
|
791
792
|
}),
|
|
792
793
|
onSuccess: (...args) => {
|
|
793
794
|
// A write that succeeded may have fixed whatever was failing reads.
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
13
13
|
};
|
|
14
14
|
}>>;
|
|
15
15
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
package/dist/deploy/build.js
CHANGED
|
@@ -1910,7 +1910,7 @@ async function getHandler() {
|
|
|
1910
1910
|
headers: {
|
|
1911
1911
|
"Access-Control-Allow-Origin": "*",
|
|
1912
1912
|
"Access-Control-Allow-Methods": "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
|
|
1913
|
-
"Access-Control-Allow-Headers": "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Session-Id,X-Agent-Native-Client-Platform,X-Agent-Native-Desktop-Verifier,X-Agent-Native-Test-Traffic,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-Agent-Native-Embed-Target",
|
|
1913
|
+
"Access-Control-Allow-Headers": "Content-Type,Authorization,X-Requested-With,X-Request-Source,X-Content-Save-Origin,X-Agent-Native-CSRF,X-User-Timezone,X-Agent-Native-Session-Id,X-Agent-Native-Client-Platform,X-Agent-Native-Desktop-Verifier,X-Agent-Native-Test-Traffic,X-Agent-Native-Tool-Bridge,X-Agent-Native-Tool-Id,X-Agent-Native-Frontend,X-Agent-Native-Client-Compatibility,X-Agent-Native-Build-Id,X-Agent-Native-Embed-Target",
|
|
1914
1914
|
},
|
|
1915
1915
|
});
|
|
1916
1916
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { AGENT_BACKGROUND_PROCESSOR_A2A, AGENT_BACKGROUND_PROCESSOR_AGENT_TEAM, AGENT_BACKGROUND_PROCESSOR_FIELD, AGENT_BACKGROUND_PROCESSOR_INTEGRATION, AGENT_BACKGROUND_PROCESSOR_ROUTE, AGENT_BACKGROUND_PROCESSOR_ROUTE_FIELD, AGENT_CHAT_PROCESS_RUN_PATH, AGENT_TEAM_PROCESS_RUN_PATH, isDurableBackgroundFlagExplicitlyDisabled, } from "../agent/durable-background.js";
|
|
5
6
|
import { getAppConfig } from "../app-config/index.js";
|
|
@@ -210,10 +211,12 @@ export async function runWorkspaceDeploy(opts = {}) {
|
|
|
210
211
|
fs.mkdirSync(functionsDir, { recursive: true });
|
|
211
212
|
}
|
|
212
213
|
console.log(`[workspace-deploy] Building ${apps.length} app(s) for preset=${preset}`);
|
|
213
|
-
const
|
|
214
|
+
const concurrency = workspaceBuildConcurrency();
|
|
215
|
+
const runBuild = opts.execFile ?? spawnBuild;
|
|
216
|
+
await runWithConcurrency(apps, concurrency, (app) => buildOneApp(workspaceRoot, appsDir, app, preset, runBuild, workspaceApps, workspaceAuthMode, concurrency > 1 ? "pipe" : "inherit"));
|
|
217
|
+
// Copy in app order after all builds so shared outputs stay deterministic.
|
|
214
218
|
const sweepCrons = [];
|
|
215
219
|
for (const app of apps) {
|
|
216
|
-
buildOneApp(workspaceRoot, appsDir, app, preset, execFile, workspaceApps, workspaceAuthMode);
|
|
217
220
|
moveAppBuildIntoWorkspaceOutput(workspaceRoot, appsDir, app, preset, distDir, vercelOutputDir, workspaceApps, workspaceAuthMode);
|
|
218
221
|
if (preset === "vercel") {
|
|
219
222
|
sweepCrons.push(readVercelSweepCron(path.join(appsDir, app, VERCEL_OUTPUT_DIR), `/${app}`));
|
|
@@ -245,7 +248,7 @@ export async function runWorkspaceDeploy(opts = {}) {
|
|
|
245
248
|
}
|
|
246
249
|
console.log(`All apps live at https://<origin>/<app-name>/*. Log in once on any app\nand the session is shared across the workspace.`);
|
|
247
250
|
}
|
|
248
|
-
function buildOneApp(workspaceRoot, appsDir, app, preset,
|
|
251
|
+
async function buildOneApp(workspaceRoot, appsDir, app, preset, runBuild, workspaceApps, workspaceAuthMode, stdio) {
|
|
249
252
|
const appDir = path.join(appsDir, app);
|
|
250
253
|
const workspaceAppAudience = workspaceAppAudienceForApp(workspaceApps, app);
|
|
251
254
|
const workspaceAppRouteAccess = workspaceAppRouteAccessForApp(workspaceApps, app);
|
|
@@ -306,10 +309,66 @@ function buildOneApp(workspaceRoot, appsDir, app, preset, execFile, workspaceApp
|
|
|
306
309
|
}
|
|
307
310
|
console.log(`[workspace-deploy] Building ${app} (base=/${app}, preset=${preset})`);
|
|
308
311
|
cleanAppBuildOutputs(appDir);
|
|
309
|
-
|
|
312
|
+
const startedAt = Date.now();
|
|
313
|
+
await runBuild("pnpm", ["--filter", app, "build"], {
|
|
310
314
|
cwd: workspaceRoot,
|
|
311
315
|
env,
|
|
312
|
-
stdio
|
|
316
|
+
stdio,
|
|
317
|
+
});
|
|
318
|
+
console.log(`[workspace-deploy] Built ${app} in ${((Date.now() - startedAt) / 1000).toFixed(1)}s`);
|
|
319
|
+
}
|
|
320
|
+
function workspaceBuildConcurrency() {
|
|
321
|
+
const raw = process.env.AGENT_NATIVE_DEPLOY_CONCURRENCY?.trim();
|
|
322
|
+
if (raw) {
|
|
323
|
+
const parsed = Number(raw);
|
|
324
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
325
|
+
throw new Error(`AGENT_NATIVE_DEPLOY_CONCURRENCY must be a positive integer, got "${raw}"`);
|
|
326
|
+
}
|
|
327
|
+
return parsed;
|
|
328
|
+
}
|
|
329
|
+
// Each app build can use 1-2 GB, so stay well under typical CI memory limits.
|
|
330
|
+
return Math.min(3, os.availableParallelism());
|
|
331
|
+
}
|
|
332
|
+
async function runWithConcurrency(items, limit, fn) {
|
|
333
|
+
let next = 0;
|
|
334
|
+
const errors = [];
|
|
335
|
+
const worker = async () => {
|
|
336
|
+
while (errors.length === 0 && next < items.length) {
|
|
337
|
+
try {
|
|
338
|
+
await fn(items[next++]);
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
errors.push(error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
// Let in-flight builds finish so none are orphaned when the CLI exits and
|
|
346
|
+
// every failure gets reported, not just the first.
|
|
347
|
+
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
348
|
+
if (errors.length === 1)
|
|
349
|
+
throw errors[0];
|
|
350
|
+
if (errors.length > 1) {
|
|
351
|
+
throw new Error(errors
|
|
352
|
+
.map((error) => error instanceof Error ? error.message : String(error))
|
|
353
|
+
.join("\n"));
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function spawnBuild(cmd, args, options) {
|
|
357
|
+
return new Promise((resolve, reject) => {
|
|
358
|
+
const child = spawn(cmd, args, options);
|
|
359
|
+
// Buffered so parallel builds print as whole blocks instead of interleaving.
|
|
360
|
+
const chunks = [];
|
|
361
|
+
child.stdout?.on("data", (chunk) => chunks.push(chunk));
|
|
362
|
+
child.stderr?.on("data", (chunk) => chunks.push(chunk));
|
|
363
|
+
child.on("error", reject);
|
|
364
|
+
child.on("close", (code, signal) => {
|
|
365
|
+
if (chunks.length)
|
|
366
|
+
process.stdout.write(Buffer.concat(chunks));
|
|
367
|
+
if (code === 0)
|
|
368
|
+
resolve();
|
|
369
|
+
else
|
|
370
|
+
reject(new Error(`Command failed: ${cmd} ${args.join(" ")} (${signal ?? `exit code ${code}`})`));
|
|
371
|
+
});
|
|
313
372
|
});
|
|
314
373
|
}
|
|
315
374
|
function moveAppBuildIntoWorkspaceOutput(workspaceRoot, appsDir, app, preset, distDir, vercelOutputDir, workspaceApps, workspaceAuthMode) {
|
|
@@ -33,20 +33,20 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
33
33
|
idempotencyKey: string;
|
|
34
34
|
deleted: boolean;
|
|
35
35
|
} | {
|
|
36
|
-
id?: undefined;
|
|
37
36
|
error: string;
|
|
38
37
|
url?: undefined;
|
|
38
|
+
id?: undefined;
|
|
39
39
|
provider?: undefined;
|
|
40
40
|
configured?: undefined;
|
|
41
41
|
} | {
|
|
42
42
|
url: string;
|
|
43
43
|
id: string;
|
|
44
44
|
provider: string;
|
|
45
|
-
configured?: undefined;
|
|
46
45
|
error?: undefined;
|
|
46
|
+
configured?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
id?: undefined;
|
|
49
48
|
url?: undefined;
|
|
49
|
+
id?: undefined;
|
|
50
50
|
provider?: undefined;
|
|
51
51
|
error: string;
|
|
52
52
|
configured: boolean;
|
|
@@ -2,7 +2,13 @@ import type { FileUploadDeleteInput, FileUploadInput, FileUploadProvider, FileUp
|
|
|
2
2
|
export declare function registerFileUploadProvider(provider: FileUploadProvider): void;
|
|
3
3
|
export declare function unregisterFileUploadProvider(id: string): void;
|
|
4
4
|
export declare function listFileUploadProviders(): FileUploadProvider[];
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a public upload URL only through a provider that claims ownership.
|
|
7
|
+
* Each provider is checked independently: one provider whose check throws
|
|
8
|
+
* (S3 reads its config from the DB) must not hide another's positive claim, so
|
|
9
|
+
* this throws only when no provider claims the URL and at least one could not
|
|
10
|
+
* answer — "unverifiable", never reported as "not owned".
|
|
11
|
+
*/
|
|
6
12
|
export declare function findFileUploadProviderOwningUrl(url: string): Promise<FileUploadProvider | null>;
|
|
7
13
|
export declare function getActiveFileUploadProvider(): FileUploadProvider | null;
|
|
8
14
|
/**
|
|
@@ -11,17 +11,35 @@ export function unregisterFileUploadProvider(id) {
|
|
|
11
11
|
export function listFileUploadProviders() {
|
|
12
12
|
return [...providers.values()];
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Resolve a public upload URL only through a provider that claims ownership.
|
|
16
|
+
* Each provider is checked independently: one provider whose check throws
|
|
17
|
+
* (S3 reads its config from the DB) must not hide another's positive claim, so
|
|
18
|
+
* this throws only when no provider claims the URL and at least one could not
|
|
19
|
+
* answer — "unverifiable", never reported as "not owned".
|
|
20
|
+
*/
|
|
15
21
|
export async function findFileUploadProviderOwningUrl(url) {
|
|
16
|
-
const candidates = [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
const candidates = [
|
|
23
|
+
builderFileUploadProvider,
|
|
24
|
+
...[...providers.values()].filter((p) => p !== builderFileUploadProvider),
|
|
25
|
+
];
|
|
26
|
+
const failures = [];
|
|
27
|
+
const unverifiable = [];
|
|
20
28
|
for (const provider of candidates) {
|
|
21
|
-
if (provider.isOwnedUrl
|
|
22
|
-
|
|
29
|
+
if (!provider.isOwnedUrl)
|
|
30
|
+
continue;
|
|
31
|
+
try {
|
|
32
|
+
if (await provider.isOwnedUrl(url))
|
|
33
|
+
return provider;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
failures.push(error);
|
|
37
|
+
unverifiable.push(provider.id);
|
|
23
38
|
}
|
|
24
39
|
}
|
|
40
|
+
if (failures.length > 0) {
|
|
41
|
+
throw new AggregateError(failures, `Upload URL ownership is unverifiable: ${unverifiable.join(", ")} failed its check`);
|
|
42
|
+
}
|
|
25
43
|
return null;
|
|
26
44
|
}
|
|
27
45
|
export function getActiveFileUploadProvider() {
|
package/dist/mcp/build-server.js
CHANGED
|
@@ -894,16 +894,6 @@ async function withServerMintedMcpAppEmbedStart(result, meta, directoryLinkUrl,
|
|
|
894
894
|
(ctx?.orgId ?? undefined))) {
|
|
895
895
|
throw new Error("The widget write grant must match the authenticated MCP user and organization.");
|
|
896
896
|
}
|
|
897
|
-
const scope = restrictDirectoryWidgetCapability
|
|
898
|
-
? directoryWidget.capability.mode === "write"
|
|
899
|
-
? createMcpDirectoryWidgetWriteCapability(directoryWidget.capability.value)
|
|
900
|
-
: createMcpDirectoryWidgetReadCapability(directoryWidget.capability.value)
|
|
901
|
-
: typeof out.chrome === "string"
|
|
902
|
-
? out.chrome
|
|
903
|
-
: null;
|
|
904
|
-
if (restrictDirectoryWidgetCapability && !scope) {
|
|
905
|
-
throw new Error("Could not create a valid scoped capability for this MCP directory widget.");
|
|
906
|
-
}
|
|
907
897
|
const revocationAnchorCandidate = ctx?.mcpCredentialIssuedAtMs;
|
|
908
898
|
const revocationAnchorCreatedAtMs = typeof revocationAnchorCandidate === "number" &&
|
|
909
899
|
Number.isSafeInteger(revocationAnchorCandidate)
|
|
@@ -913,18 +903,50 @@ async function withServerMintedMcpAppEmbedStart(result, meta, directoryLinkUrl,
|
|
|
913
903
|
revocationAnchorCreatedAtMs === undefined) {
|
|
914
904
|
throw new Error("Directory widget sessions require a trusted MCP credential issue time.");
|
|
915
905
|
}
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
906
|
+
const mintTicket = async (capability) => {
|
|
907
|
+
const scope = capability
|
|
908
|
+
? capability.mode === "write"
|
|
909
|
+
? createMcpDirectoryWidgetWriteCapability(capability.value)
|
|
910
|
+
: createMcpDirectoryWidgetReadCapability(capability.value)
|
|
911
|
+
: typeof out.chrome === "string"
|
|
912
|
+
? out.chrome
|
|
913
|
+
: null;
|
|
914
|
+
if (capability && !scope) {
|
|
915
|
+
throw new Error("Could not create a valid scoped capability for this MCP directory widget.");
|
|
916
|
+
}
|
|
917
|
+
return createEmbedSessionTicket({
|
|
918
|
+
ownerEmail,
|
|
919
|
+
orgId: ctx?.orgId,
|
|
920
|
+
targetPath,
|
|
921
|
+
scope,
|
|
922
|
+
...(capability?.mode === "write" ? { ttlSeconds: 5 * 60 } : {}),
|
|
923
|
+
...(capability ? { revocationAnchorCreatedAtMs } : {}),
|
|
924
|
+
});
|
|
925
|
+
};
|
|
926
|
+
const writeCapability = directoryWidget?.capability.mode === "write"
|
|
927
|
+
? directoryWidget.capability
|
|
928
|
+
: undefined;
|
|
929
|
+
let ticket;
|
|
930
|
+
try {
|
|
931
|
+
ticket = await mintTicket(directoryWidget?.capability);
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
if (!writeCapability)
|
|
935
|
+
throw error;
|
|
936
|
+
// The write grant is an upgrade over the read grant. Losing it must not
|
|
937
|
+
// also lose the widget's session ticket, which the shell cannot start without.
|
|
938
|
+
console.error("[mcp:directory] Could not mint the widget write grant; issuing a read-only widget session instead.", error);
|
|
939
|
+
const { appId, resourceUri, resourceIds, readActionArguments } = writeCapability.value;
|
|
940
|
+
ticket = await mintTicket({
|
|
941
|
+
mode: "read",
|
|
942
|
+
value: {
|
|
943
|
+
appId,
|
|
944
|
+
resourceUri,
|
|
945
|
+
resourceIds,
|
|
946
|
+
actionArguments: readActionArguments,
|
|
947
|
+
},
|
|
948
|
+
});
|
|
949
|
+
}
|
|
928
950
|
const startPath = buildEmbedStartPath(ticket.ticket);
|
|
929
951
|
const embedStartUrl = meta?.origin
|
|
930
952
|
? new URL(startPath, meta.origin).toString()
|
|
@@ -2441,11 +2463,24 @@ export async function createMCPServerForRequest(config, identity, requestMeta) {
|
|
|
2441
2463
|
mcpAppResourceCandidate !== null &&
|
|
2442
2464
|
directoryWidget === undefined;
|
|
2443
2465
|
const suppressDirectoryWidget = directoryCatalog && !hasVerifiedMcpUserIdentity(effectiveIdentity);
|
|
2466
|
+
// The host mounts a widget for this tool from its descriptor, with or
|
|
2467
|
+
// without a ticket in the result, so a withheld ticket must be visible.
|
|
2468
|
+
if (missingDirectoryWidgetCapability &&
|
|
2469
|
+
!suppressDirectoryWidget &&
|
|
2470
|
+
!mcpResultIsError) {
|
|
2471
|
+
console.warn(`[mcp:directory] ${name} returned no widget session ticket: ${typeof trustedCredentialIssuedAtMs === "number"
|
|
2472
|
+
? "the result has no widget target or scoped actions"
|
|
2473
|
+
: "the credential carries no issue time"}.`);
|
|
2474
|
+
}
|
|
2444
2475
|
const rawResultForClient = missingDirectoryWidgetCapability || suppressDirectoryWidget
|
|
2445
2476
|
? withoutMcpAppEmbedTicket(projectedRawResult)
|
|
2446
2477
|
: mcpAppResourceCandidate
|
|
2447
2478
|
? await withServerMintedMcpAppEmbedStart(projectedRawResult, requestMeta, directoryLinkUrl, directoryWidget)
|
|
2448
2479
|
: projectedRawResult;
|
|
2480
|
+
if (directoryWidget &&
|
|
2481
|
+
typeof metadataObject(rawResultForClient).embedStartUrl !== "string") {
|
|
2482
|
+
console.error(`[mcp:directory] ${name} built a widget capability but issued no session ticket.`);
|
|
2483
|
+
}
|
|
2449
2484
|
const { value: actionResultForClient, images: resultImages, notes: resultImageNotes, } = extractAgentImagesFromActionResult(rawResultForClient);
|
|
2450
2485
|
const textResultForClient = mcpResult
|
|
2451
2486
|
? projectedResultForClient
|
|
@@ -2996,12 +3031,15 @@ async function admitIssuedMcpCredential(credential, requestOrigin) {
|
|
|
2996
3031
|
stored = lookup;
|
|
2997
3032
|
}
|
|
2998
3033
|
const orgId = credential.orgId !== undefined ? credential.orgId : stored?.orgId;
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3034
|
+
// Access tokens signed before grant times existed carry no claim, and the
|
|
3035
|
+
// directory widget refuses to mint a session without an issue time. Their
|
|
3036
|
+
// signed `iat` is the only anchor they have. `iat` moves on every refresh, so
|
|
3037
|
+
// it must stay the fallback and never outrank a signed grant time.
|
|
3038
|
+
const credentialIssuedAtMs = credential.grantCreatedAtMs ??
|
|
3039
|
+
(typeof credential.issuedAt === "number" &&
|
|
3002
3040
|
Number.isSafeInteger(credential.issuedAt)
|
|
3003
3041
|
? credential.issuedAt * 1000
|
|
3004
|
-
: undefined;
|
|
3042
|
+
: undefined);
|
|
3005
3043
|
const mcpCredentialIssuedAtMs = credentialIssuedAtMs !== undefined &&
|
|
3006
3044
|
Number.isSafeInteger(credentialIssuedAtMs)
|
|
3007
3045
|
? credentialIssuedAtMs
|