@agent-native/core 0.210.2 → 0.211.0-nightly-20261020183616
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/action.d.ts +8 -0
- package/dist/action.js +17 -0
- package/dist/agent/production-agent.js +11 -20
- package/dist/client/chat/agentkit-protocol.js +48 -5
- package/dist/client/chat/runtime.d.ts +2 -0
- package/dist/client/chat/runtime.js +6 -1
- package/dist/client/hooks/index.d.ts +1 -1
- package/dist/client/hooks/index.js +1 -1
- package/dist/client/resources/mcp-integration-logos.js +4 -0
- package/dist/client/session-identity.d.ts +8 -0
- package/dist/client/session-identity.js +13 -0
- package/dist/client/use-action.d.ts +14 -0
- package/dist/client/use-action.js +117 -28
- package/dist/client/use-session.js +2 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +24 -3
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/actions/revoke-org-service-token.js +5 -1
- package/dist/mcp/execute-action-call.js +8 -17
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/actions/set-app-member-roles.js +4 -0
- package/dist/org/actions/set-app-permission-roles.js +4 -0
- package/dist/org/actions/set-workspace-app-access.js +4 -0
- package/dist/org/index.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +17 -17
- package/dist/provider-api/actions/provider-api.d.ts +11 -11
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/review/suggestions/actions.d.ts +1 -0
- package/dist/review/suggestions/actions.js +54 -42
- package/dist/secrets/actions/delete-api-key.js +4 -0
- package/dist/secrets/routes.d.ts +4 -4
- package/dist/server/action-approval.d.ts +15 -0
- package/dist/server/action-approval.js +39 -0
- package/dist/server/action-routes.js +33 -28
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +2 -2
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.js +1 -0
- package/dist/shared/action-error-codes.d.ts +5 -0
- package/dist/shared/action-error-codes.js +5 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
- package/dist/vite/client.js +2 -1
- package/docs/content/actions-access-control.mdx +3 -3
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +5 -3
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +5 -3
package/dist/action.d.ts
CHANGED
|
@@ -661,6 +661,14 @@ export declare function actionCallIntent(entry: {
|
|
|
661
661
|
* share one, so none can change what another reads.
|
|
662
662
|
*/
|
|
663
663
|
export declare function preparedCallArguments(call: PreparedActionCall): unknown;
|
|
664
|
+
/**
|
|
665
|
+
* Whether `call` needs a person's approval before it runs, by the action's
|
|
666
|
+
* `needsApproval`. Every surface that can run an action decides with this, so
|
|
667
|
+
* a predicate means the same thing to the agent loop, MCP, and HTTP.
|
|
668
|
+
*/
|
|
669
|
+
export declare function preparedCallNeedsApproval(entry: {
|
|
670
|
+
needsApproval?: boolean | ((args: any, ctx?: ActionRunContext) => boolean | Promise<boolean>);
|
|
671
|
+
}, call: PreparedActionCall, ctx: ActionRunContext): Promise<boolean>;
|
|
664
672
|
/**
|
|
665
673
|
* @internal Executes a prepared call: `entry.run` receives its own copy of the
|
|
666
674
|
* prepared arguments, which the action's input validation and classification
|
package/dist/action.js
CHANGED
|
@@ -1398,6 +1398,23 @@ export function preparedCallArguments(call) {
|
|
|
1398
1398
|
}
|
|
1399
1399
|
return copyActionArguments(prepared.snapshot);
|
|
1400
1400
|
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Whether `call` needs a person's approval before it runs, by the action's
|
|
1403
|
+
* `needsApproval`. Every surface that can run an action decides with this, so
|
|
1404
|
+
* a predicate means the same thing to the agent loop, MCP, and HTTP.
|
|
1405
|
+
*/
|
|
1406
|
+
export async function preparedCallNeedsApproval(entry, call, ctx) {
|
|
1407
|
+
const gate = entry.needsApproval;
|
|
1408
|
+
if (typeof gate !== "function")
|
|
1409
|
+
return gate === true;
|
|
1410
|
+
try {
|
|
1411
|
+
return Boolean(await gate(preparedCallArguments(call), ctx));
|
|
1412
|
+
}
|
|
1413
|
+
catch {
|
|
1414
|
+
// coercion-ok: a predicate that cannot decide fails closed and asks.
|
|
1415
|
+
return true;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1401
1418
|
/**
|
|
1402
1419
|
* @internal Executes a prepared call: `entry.run` receives its own copy of the
|
|
1403
1420
|
* prepared arguments, which the action's input validation and classification
|
|
@@ -8,7 +8,7 @@ import { parseA2AAgentActivityPart } from "../a2a/activity.js";
|
|
|
8
8
|
import { actionCallIsRetrySafe, actionChangeResource, classifyActionCall, } from "../action-call-classification.js";
|
|
9
9
|
import { runOutsideActionInvocation, vouchedOutcome } from "../action-frame.js";
|
|
10
10
|
import { ACTION_CHAT_UI_RECORD_CHANGE_RENDERER, normalizeActionChangeResult, } from "../action-ui.js";
|
|
11
|
-
import { AgentConnectionRequiredError, describeToolParameterSignature, isActionContractError, isActionHiddenFromEveryAgentSurface, isAgentActionStopError, isAgentConnectionRequiredError, actionCallIntent, invocationEffect, runPreparedActionCall, boundActionCallIntent, preparedCallArguments, recordInvalidActionCall, stripUnsupportedSchemaKeywords, } from "../action.js";
|
|
11
|
+
import { AgentConnectionRequiredError, describeToolParameterSignature, isActionContractError, isActionHiddenFromEveryAgentSurface, isAgentActionStopError, isAgentConnectionRequiredError, actionCallIntent, invocationEffect, runPreparedActionCall, boundActionCallIntent, preparedCallArguments, preparedCallNeedsApproval, recordInvalidActionCall, stripUnsupportedSchemaKeywords, } from "../action.js";
|
|
12
12
|
import { getAppConfig } from "../app-config/index.js";
|
|
13
13
|
import { MAX_BACKGROUND_RUN_CONTINUATIONS, MAX_CONSECUTIVE_NO_PROGRESS_CONTINUATIONS, MAX_TURN_WALL_CLOCK_MS, } from "../app-config/run-lifecycle-invariants.js";
|
|
14
14
|
import { readAppState } from "../application-state/script-helpers.js";
|
|
@@ -6131,25 +6131,16 @@ export async function runAgentLoop(opts) {
|
|
|
6131
6131
|
? await opts.consumeApproval(approvalBinding())
|
|
6132
6132
|
: requestedApproval;
|
|
6133
6133
|
if (actionEntry.needsApproval && !wasApproved) {
|
|
6134
|
-
let mustApprove =
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
networkProtocol: opts.networkProtocol,
|
|
6145
|
-
networkId: opts.networkId,
|
|
6146
|
-
networkPeer: opts.networkPeer,
|
|
6147
|
-
}))
|
|
6148
|
-
: actionEntry.needsApproval === true;
|
|
6149
|
-
}
|
|
6150
|
-
catch {
|
|
6151
|
-
mustApprove = true;
|
|
6152
|
-
}
|
|
6134
|
+
let mustApprove = await preparedCallNeedsApproval(actionEntry, intent, {
|
|
6135
|
+
userEmail: getRequestUserEmail(),
|
|
6136
|
+
orgId: getRequestOrgId() ?? null,
|
|
6137
|
+
appId: opts.appId,
|
|
6138
|
+
caller: opts.actionCaller ?? "tool",
|
|
6139
|
+
automation: opts.automation,
|
|
6140
|
+
networkProtocol: opts.networkProtocol,
|
|
6141
|
+
networkId: opts.networkId,
|
|
6142
|
+
networkPeer: opts.networkPeer,
|
|
6143
|
+
});
|
|
6153
6144
|
if (mustApprove &&
|
|
6154
6145
|
actionEntry.allowPersistentApproval !== false &&
|
|
6155
6146
|
opts.isToolAlwaysAllowed) {
|
|
@@ -1077,9 +1077,15 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1077
1077
|
}
|
|
1078
1078
|
return activeOwners[0] ?? owners.at(-1);
|
|
1079
1079
|
}
|
|
1080
|
-
function indexRun(run, runtimeRunId = run.turn.runId ?? run.runId) {
|
|
1080
|
+
function indexRun(run, runtimeRunId = run.turn.runId ?? run.runId, allowReplay = false) {
|
|
1081
1081
|
const existingRun = runs.get(run.runId);
|
|
1082
1082
|
if (existingRun && existingRun !== run) {
|
|
1083
|
+
if (allowReplay &&
|
|
1084
|
+
existingRun.threadId === run.threadId &&
|
|
1085
|
+
existingRun.turn.id !== undefined &&
|
|
1086
|
+
existingRun.turn.id === run.turn.id) {
|
|
1087
|
+
return existingRun;
|
|
1088
|
+
}
|
|
1083
1089
|
throw new Error(`Duplicate AgentKit run id: ${run.runId}`);
|
|
1084
1090
|
}
|
|
1085
1091
|
if (runtimeRunId && runtimeRunId !== run.runId) {
|
|
@@ -1098,6 +1104,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1098
1104
|
runtimeRunAliases.set(aliasKey, run);
|
|
1099
1105
|
}
|
|
1100
1106
|
runs.set(run.runId, run);
|
|
1107
|
+
return run;
|
|
1101
1108
|
}
|
|
1102
1109
|
async function resolveRuntimeRunOwner(threadId, runtimeRunId, sessionId, authorityRead) {
|
|
1103
1110
|
if (!runAuthority)
|
|
@@ -1468,7 +1475,11 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1468
1475
|
: {}),
|
|
1469
1476
|
listeners: new Set(),
|
|
1470
1477
|
};
|
|
1471
|
-
indexRun(run, turn.runId ?? input.runId);
|
|
1478
|
+
const indexedRun = indexRun(run, turn.runId ?? input.runId, true);
|
|
1479
|
+
if (indexedRun !== run) {
|
|
1480
|
+
await closeDiscardedTurnEvents(turn);
|
|
1481
|
+
return indexedRun;
|
|
1482
|
+
}
|
|
1472
1483
|
if (restoreRead)
|
|
1473
1484
|
run.initialAuthorityRead = restoreRead;
|
|
1474
1485
|
if (state && "pendingConnectionRequestId" in state) {
|
|
@@ -1488,7 +1499,7 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1488
1499
|
* Registers a run the runtime just started and begins reading it. A disposed
|
|
1489
1500
|
* adapter keeps no run it can no longer read.
|
|
1490
1501
|
*/
|
|
1491
|
-
async function openStartedRun(threadId, session, turn, turnMetadata, observability) {
|
|
1502
|
+
async function openStartedRun(threadId, session, turn, turnMetadata, observability, start = {}) {
|
|
1492
1503
|
const runId = turn.runId ?? turn.id ?? createId("run");
|
|
1493
1504
|
const runMetadata = mergeTrustedProtocolMetadata(options.metadata, turnMetadata, turn.metadata, {
|
|
1494
1505
|
[AGENT_NATIVE_PROTOCOL_METADATA_KEY]: {
|
|
@@ -1535,8 +1546,34 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1535
1546
|
await disposeSession(session).catch(() => undefined);
|
|
1536
1547
|
throw new Error("The AgentKit adapter was disposed during turn creation.");
|
|
1537
1548
|
}
|
|
1549
|
+
const existingRun = start.attachReplayedRun
|
|
1550
|
+
? findRun(threadId, runId)
|
|
1551
|
+
: undefined;
|
|
1552
|
+
if (existingRun) {
|
|
1553
|
+
// A duplicate queued submission is answered with the run that already
|
|
1554
|
+
// took it, replayed from its first event. This adapter reads that run
|
|
1555
|
+
// already, so the replay is drained rather than indexed a second time.
|
|
1556
|
+
// The replayed stream stays open until the run ends; closing it early
|
|
1557
|
+
// needs a transport abort, because turn.cancel would cancel the run.
|
|
1558
|
+
void (async () => {
|
|
1559
|
+
for await (const _event of turn.events) {
|
|
1560
|
+
// Discarded: the existing run delivers these events.
|
|
1561
|
+
}
|
|
1562
|
+
})().catch((error) => {
|
|
1563
|
+
console.warn(`[agentkit] replayed stream for run ${runId} failed`, error);
|
|
1564
|
+
});
|
|
1565
|
+
touchRun(existingRun);
|
|
1566
|
+
ensurePump(existingRun);
|
|
1567
|
+
// A runtime alias resolves to the run that owns it, which may use a
|
|
1568
|
+
// different protocol id; the caller must address that run.
|
|
1569
|
+
return existingRun.runId;
|
|
1570
|
+
}
|
|
1538
1571
|
pruneRetainedRuns(run.lastAccessedAtMs);
|
|
1539
|
-
indexRun(run, turn.runId);
|
|
1572
|
+
const indexedRun = indexRun(run, turn.runId, true);
|
|
1573
|
+
if (indexedRun !== run) {
|
|
1574
|
+
await closeDiscardedTurnEvents(turn);
|
|
1575
|
+
return indexedRun.runId;
|
|
1576
|
+
}
|
|
1540
1577
|
append(run, {
|
|
1541
1578
|
type: "run.started",
|
|
1542
1579
|
agentId: runtime.id,
|
|
@@ -1552,6 +1589,10 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
1552
1589
|
disposedSessions.add(session);
|
|
1553
1590
|
await session.dispose?.();
|
|
1554
1591
|
}
|
|
1592
|
+
async function closeDiscardedTurnEvents(turn) {
|
|
1593
|
+
const iterator = turn.events[Symbol.asyncIterator]();
|
|
1594
|
+
await Promise.all([turn.closeEvents?.(), iterator.return?.()]);
|
|
1595
|
+
}
|
|
1555
1596
|
function append(run, event) {
|
|
1556
1597
|
const lastType = run.events.at(-1)?.type;
|
|
1557
1598
|
const terminalStatus = event.type === "run.completed" ||
|
|
@@ -3082,7 +3123,9 @@ export function createAgentKitProtocolAdapter(runtime, options = {}) {
|
|
|
3082
3123
|
metadata: turnMetadata,
|
|
3083
3124
|
abortSignal: readers.signal,
|
|
3084
3125
|
});
|
|
3085
|
-
return openStartedRun(input.threadId, session, turn, turnMetadata
|
|
3126
|
+
return openStartedRun(input.threadId, session, turn, turnMetadata, undefined, {
|
|
3127
|
+
attachReplayedRun: true,
|
|
3128
|
+
});
|
|
3086
3129
|
};
|
|
3087
3130
|
if (!queuePromotionKey) {
|
|
3088
3131
|
return { runId: await createRun(), capabilities };
|
|
@@ -550,6 +550,8 @@ export interface AgentChatRuntimeTurn<TEvent extends AgentChatRuntimeEventBase =
|
|
|
550
550
|
readonly runId?: string;
|
|
551
551
|
readonly metadata?: AgentChatRuntimeMetadata;
|
|
552
552
|
readonly events: AsyncIterable<TEvent>;
|
|
553
|
+
/** Close the local event stream without cancelling the underlying run. */
|
|
554
|
+
closeEvents?(): Promise<void> | void;
|
|
553
555
|
cancel?(input?: AgentChatRuntimeCancelInput): Promise<AgentChatRuntimeCancelResult>;
|
|
554
556
|
}
|
|
555
557
|
export interface AgentChatRuntimeSendMessageInput extends AgentChatRuntimeTurnInput {
|
|
@@ -504,13 +504,18 @@ export function createHttpAgentChatRuntime(options) {
|
|
|
504
504
|
cleanup();
|
|
505
505
|
}
|
|
506
506
|
})();
|
|
507
|
+
const closeEvents = () => {
|
|
508
|
+
controller.abort();
|
|
509
|
+
cleanup();
|
|
510
|
+
};
|
|
507
511
|
return {
|
|
508
512
|
id: turnId,
|
|
509
513
|
sessionId,
|
|
510
514
|
runId,
|
|
511
515
|
events,
|
|
516
|
+
closeEvents,
|
|
512
517
|
cancel: async (cancelInput) => {
|
|
513
|
-
|
|
518
|
+
closeEvents();
|
|
514
519
|
if (!options.cancelEndpoint)
|
|
515
520
|
return { status: "cancelled" };
|
|
516
521
|
const endpoint = typeof options.cancelEndpoint === "function"
|
|
@@ -6,7 +6,7 @@ export { useDemoModeStatus, type DemoModeStatus, } from "../use-demo-mode-status
|
|
|
6
6
|
export { useReconciledState } from "../use-external-value.js";
|
|
7
7
|
export { beginSignOut, isSigningOut, notifySessionInvalidated, useSession, type AuthSession, } from "../use-session.js";
|
|
8
8
|
export { signOut, type SignOutOptions } from "../sign-out.js";
|
|
9
|
-
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, actionErrorMessage, callAction, callActionBlob, callActionWithRetry, tryCallActionKeepalive, useActionQuery, useActionMutation, type ActionRegistry, type ClientActionCallOptions, type ClientActionMethod, type RetriedActionCallOptions, type KeepaliveActionCallRejectionReason, type KeepaliveActionCallResult, } from "../use-action.js";
|
|
9
|
+
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, actionErrorMessage, callAction, callActionBlob, callActionWithRetry, currentActionQueryCacheBinding, recordActionQueryBrowserPersist, tryCallActionKeepalive, useActionQuery, useActionMutation, type ActionRegistry, type ClientActionCallOptions, type ClientActionMethod, type RetriedActionCallOptions, type KeepaliveActionCallRejectionReason, type KeepaliveActionCallResult, } from "../use-action.js";
|
|
10
10
|
export { actionErrorCode, isTerminalActionError, } from "../action-failure-circuit.js";
|
|
11
11
|
export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
12
12
|
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, requestPerAppChatCommand, usePerAppChatState, usePerAppChatOpen, type AppChatSidebarCommand, type AppChatSidebarState, } from "../app-chat-sidebar.js";
|
|
@@ -6,7 +6,7 @@ export { useDemoModeStatus, } from "../use-demo-mode-status.js";
|
|
|
6
6
|
export { useReconciledState } from "../use-external-value.js";
|
|
7
7
|
export { beginSignOut, isSigningOut, notifySessionInvalidated, useSession, } from "../use-session.js";
|
|
8
8
|
export { signOut } from "../sign-out.js";
|
|
9
|
-
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, actionErrorMessage, callAction, callActionBlob, callActionWithRetry, tryCallActionKeepalive, useActionQuery, useActionMutation, } from "../use-action.js";
|
|
9
|
+
export { ACTION_KEEPALIVE_BODY_BUDGET_BYTES, actionErrorMessage, callAction, callActionBlob, callActionWithRetry, currentActionQueryCacheBinding, recordActionQueryBrowserPersist, tryCallActionKeepalive, useActionQuery, useActionMutation, } from "../use-action.js";
|
|
10
10
|
export { actionErrorCode, isTerminalActionError, } from "../action-failure-circuit.js";
|
|
11
11
|
export { createAgentNativeQueryClient } from "../create-query-client.js";
|
|
12
12
|
export { APP_CHAT_SIDEBAR_STATE_EVENT, APP_CHAT_SIDEBAR_STATE_MESSAGE, APP_CHAT_SIDEBAR_STATE_REQUEST_MESSAGE, buildAppChatSidebarStateMessage, buildAppChatSidebarStateRequest, isPerAppChatStorageKey, requestPerAppChatCommand, usePerAppChatState, usePerAppChatOpen, } from "../app-chat-sidebar.js";
|
|
@@ -223,6 +223,10 @@ const LOGOS = {
|
|
|
223
223
|
mime: "image/svg+xml",
|
|
224
224
|
data: "PHN2ZyBmaWxsPSIjRTM3NDAwIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R29vZ2xlIEFuYWx5dGljczwvdGl0bGU+PHBhdGggZD0iTTIyLjg0IDIuOTk4MnYxNy45OTg3Yy4wMDg2IDEuNjQ3My0xLjMxOTcgMi45ODk3LTIuOTY3IDIuOTk4NGEyLjk4MDggMi45ODA4IDAgMDEtLjM2NzctLjAyMDhjLTEuNTI4LS4yMjYtMi42NDc3LTEuNTU1OC0yLjYxMDUtMy4xVjMuMTIwNGMtLjAzNjktMS41NDU4IDEuMDg1Ni0yLjg3NjIgMi42MTU3LTMuMSAxLjYzNjEtLjE5MTUgMy4xMTc4Ljk3OTYgMy4zMDkzIDIuNjE1OC4wMTQuMTIwMS4wMjA4LjI0MS4wMjAyLjM2MTl6TTQuMTMyNiAxOC4wNTQ4Yy0xLjY0MTcgMC0yLjk3MjYgMS4zMzEtMi45NzI2IDIuOTcyNkMxLjE2IDIyLjY2OTEgMi40OTA5IDI0IDQuMTMyNiAyNHMyLjk3MjYtMS4zMzA5IDIuOTcyNi0yLjk3MjYtMS4zMzEtMi45NzI2LTIuOTcyNi0yLjk3MjZ6bTcuODcyOC05LjAwOThjLS4wMTcxIDAtLjAzNDIgMC0uMDUxMy4wMDAzLTEuNjQ5NS4wOTA0LTIuOTI5MyAxLjQ3NC0yLjg5MSAzLjEyNTZ2Ny45ODQ2YzAgMi4xNjcuOTUzNSAzLjQ4MjUgMi4zNTA1IDMuNzYzIDEuNjExOC4zMjY2IDMuMTgzMi0uNzE1MiAzLjUwOTgtMi4zMjcuMDQtLjE5NzQuMDYtLjM5ODMuMDU5My0uNTk5OHYtOC45NTg1Yy4wMDMtMS42NDc0LTEuMzMtMi45ODUyLTIuOTc3My0yLjk4ODJ6Ii8+PC9zdmc+",
|
|
225
225
|
},
|
|
226
|
+
"google-search-console": {
|
|
227
|
+
mime: "image/svg+xml",
|
|
228
|
+
data: "PHN2ZyBmaWxsPSIjNDU4Q0Y1IiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R29vZ2xlIFNlYXJjaCBDb25zb2xlPC90aXRsZT48cGF0aCBkPSJNOC41NDggMS4xNTZMNi44MzIgMi44NzJ2MS42ODJoMS43MTZ6bTAgMy4zOTh2LjAzNUg2LjgzMnYtLjAzNUgzLjM4NkwwIDcuODQ0djMuNTc3aDIuODI2VjguOTRjMC0uNTI1LjQyOS0uOTU0Ljk1NC0uOTU0aDE2LjQ3NmMuNTI1IDAgLjk1NC40My45NTQuOTU0djIuNDhoMi43NTRWNy44NDRsLTMuMzg2LTMuMjlIMTcuM3YuMDM1aC0xLjcxN3YtLjAzNXptNy4wMzUgMEgxNy4zVjIuODcybC0xLjcxNy0xLjcxNnpNOC42NzkgMS4xODhWMi44NGg2Ljc3M1YxLjE4OHptMTEuNDcxIDcuMDdhLjgzNC44MzQgMCAwMC0uMTMyLjAxbC0uNTQzLjAwMmMtNS4yMTYuMDE0LTEwLjQzMi0uMDA4LTE1LjY0OC4wMS0uNDM1LS4wNjMtLjc5NC40MzYtLjcxNi44ODN2Mi4yNjRoMTcuODEyYy0uMDE2LS44ODguMDQ1LTEuNzgyLS4wMzQtMi42NjYtLjEwNC0uMzQyLS40MjctLjUwMi0uNzM5LS41MDJ6bS0xNS40MjIuNjM0YS42ODkuNjk4IDAgMDEuNjg5LjY5OC42ODkuNjk4IDAgMDEtLjY4OS42OTcuNjg5LjY5OCAwIDAxLS42ODgtLjY5Ny42ODkuNjk4IDAgMDEuNjg4LS42OTh6bTIuMTM0IDBhLjY4OS42OTggMCAwMS42ODkuNjk4LjY4OS42OTggMCAwMS0uNjg5LjY5Ny42ODkuNjk4IDAgMDEtLjY4OC0uNjk3LjY4OS42OTggMCAwMS42ODgtLjY5OHpNLjAzNiAxMS42NDV2OS4xNTZjMCAxLjA1Ljg1OCAxLjkwOCAxLjkwNyAxLjkwOGguODgzVjExLjY0NXptMjEuMTc0IDB2MTEuMDY0aC44ODJjMS4wNSAwIDEuOTA4LS44NTggMS45MDgtMS45MDh2LTkuMTU2ek00LjA1NyAxMy4xMzN2Ni44NWg2LjEzN3YtNi44NXptMTMuMjQzLjAyMXYzLjc3N2wtMS43MDguOTc3LTEuNzA4LS45Nzd2LTMuNzU4YTQuMDA2IDQuMDA2IDAgMDAwIDcuMjN2Mi40NDFoMy40NTd2LTIuNDQyYTQuMDA2IDQuMDA2IDAgMDAtLjA0MS03LjI0OHptLTEzLjI0MyA4LjI2djEuNDNoNy45MjV2LTEuNDN6Ii8+PC9zdmc+",
|
|
229
|
+
},
|
|
226
230
|
"google-sheets": {
|
|
227
231
|
mime: "image/svg+xml",
|
|
228
232
|
data: "PHN2ZyBmaWxsPSIjMzRBODUzIiByb2xlPSJpbWciIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+R29vZ2xlIFNoZWV0czwvdGl0bGU+PHBhdGggZD0iTTExLjMxOCAxMi41NDVINy45MXYtMS45MDloMy40MXYxLjkxek0xNC43MjggMHY2aDZsLTYtNnptMS4zNjMgMTAuNjM2aC0zLjQxdjEuOTFoMy40MXYtMS45MXptMCAzLjI3M2gtMy40MXYxLjkxaDMuNDF2LTEuOTF6TTIwLjcyNyA2LjV2MTUuODY0YzAgLjkwNC0uNzMyIDEuNjM2LTEuNjM2IDEuNjM2SDQuOTA5YTEuNjM2IDEuNjM2IDAgMCAxLTEuNjM2LTEuNjM2VjEuNjM2QzMuMjczLjczMiA0LjAwNSAwIDQuOTA5IDBoOS4zMTh2Ni41aDYuNXptLTMuMjczIDIuNzczSDYuNTQ1djcuOTA5aDEwLjkxdi03Ljkxem0tNi4xMzYgNC42MzZINy45MXYxLjkxaDMuNDF2LTEuOTF6Ii8+PC9zdmc+",
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The identity of the session this tab last read: a string when signed in,
|
|
3
|
+
* `null` when known to be signed out (or signing out), `undefined` before the
|
|
4
|
+
* first read. Kept apart from `use-session` so action fetches can read it
|
|
5
|
+
* without depending on the session hook.
|
|
6
|
+
*/
|
|
7
|
+
export declare function currentSessionIdentity(): string | null | undefined;
|
|
8
|
+
export declare function recordSessionIdentity(value: string | null | undefined): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
let identity;
|
|
2
|
+
/**
|
|
3
|
+
* The identity of the session this tab last read: a string when signed in,
|
|
4
|
+
* `null` when known to be signed out (or signing out), `undefined` before the
|
|
5
|
+
* first read. Kept apart from `use-session` so action fetches can read it
|
|
6
|
+
* without depending on the session hook.
|
|
7
|
+
*/
|
|
8
|
+
export function currentSessionIdentity() {
|
|
9
|
+
return identity;
|
|
10
|
+
}
|
|
11
|
+
export function recordSessionIdentity(value) {
|
|
12
|
+
identity = value;
|
|
13
|
+
}
|
|
@@ -44,6 +44,8 @@ export interface ClientActionCallOptions {
|
|
|
44
44
|
headers?: Record<string, string>;
|
|
45
45
|
/** Set when the action is declared `uiOnly`, so its first request carries the UI capability. */
|
|
46
46
|
uiOnly?: boolean;
|
|
47
|
+
/** Sees each HTTP response, e.g. to pass to `recordActionQueryBrowserPersist`. */
|
|
48
|
+
onResponse?: (response: Response) => void;
|
|
47
49
|
}
|
|
48
50
|
export declare function serializeActionQueryParams(params: Record<string, any>): string;
|
|
49
51
|
export interface ActionFetchOptions {
|
|
@@ -56,6 +58,8 @@ export interface ActionFetchOptions {
|
|
|
56
58
|
headers?: Record<string, string>;
|
|
57
59
|
uiOnly?: boolean;
|
|
58
60
|
}
|
|
61
|
+
/** Drop the remembered capability so the next call that needs one mints it. */
|
|
62
|
+
export declare function forgetUiActionCapability(): void;
|
|
59
63
|
export declare const ACTION_KEEPALIVE_BODY_BUDGET_BYTES = 48000;
|
|
60
64
|
/**
|
|
61
65
|
* `undefined` when there is no document to ask (SSR, a non-browser caller) —
|
|
@@ -121,6 +125,16 @@ export declare function isBrowserPersistableActionQueryForBinding(query: Query,
|
|
|
121
125
|
/** @internal used only after a persisted action result is accepted and hydrated. */
|
|
122
126
|
export declare function markRestoredBrowserPersistableActionQuery(query: Query, token?: object): void;
|
|
123
127
|
export declare function revokeBrowserPersistableActionQuery(query: Query): void;
|
|
128
|
+
/** The persisted-cache binding to capture when a fetch starts, for `recordActionQueryBrowserPersist`. */
|
|
129
|
+
export declare function currentActionQueryCacheBinding(): object | undefined;
|
|
130
|
+
/**
|
|
131
|
+
* Lets an `["action", ...]` query filled by a hand-written `queryFn` or
|
|
132
|
+
* `setQueryData` join the browser's persisted query cache the way a
|
|
133
|
+
* `useActionQuery` result does: only when its response carried the persist
|
|
134
|
+
* header, and only into the binding that was active when the fetch began, so a
|
|
135
|
+
* result fetched for one user is never written into another user's record.
|
|
136
|
+
*/
|
|
137
|
+
export declare function recordActionQueryBrowserPersist(query: Query, response: Response | undefined, bindingTokenAtFetch: object | undefined): void;
|
|
124
138
|
export declare function useActionQuery<TResult = undefined, TName extends ActionName = ActionName>(actionName: TName, params?: ActionParams<TName>, options?: Omit<UseQueryOptions<TResult extends undefined ? ActionResult<TName> : TResult>, "queryKey" | "queryFn"> & {
|
|
125
139
|
queryKeyScope?: readonly unknown[];
|
|
126
140
|
/**
|
|
@@ -3,6 +3,7 @@ import { useContext } from "react";
|
|
|
3
3
|
import { ACTION_BATCH_ACTION_NAME } from "../shared/action-batch.js";
|
|
4
4
|
import { ACTION_BROWSER_PERSIST_ALLOW, ACTION_BROWSER_PERSIST_HEADER, } from "../shared/action-browser-persist.js";
|
|
5
5
|
import { ACTION_CHANGE_MARKER_FAILED, ACTION_CHANGE_MARKER_HEADER, } from "../shared/action-change-marker-header.js";
|
|
6
|
+
import { UI_CAPABILITY_REQUIRED_ERROR_CODE } from "../shared/action-error-codes.js";
|
|
6
7
|
import { SLOW_ACTION_RESPONSE_MS } from "../shared/analytics-events.js";
|
|
7
8
|
import { ANALYTICS_CLIENT_PLATFORM_HEADER } from "../shared/analytics-platform.js";
|
|
8
9
|
import { actionCircuitRemainingMs, actionErrorCode, actionErrorRetryAfterMs, actionErrorStatus, assertActionCircuitClosed, endsActionPolling, isActionCircuitOpenError, isTerminalActionError, recordActionFailure, resetActionFailureCircuit, resetActionFailureCircuits, } from "./action-failure-circuit.js";
|
|
@@ -19,6 +20,7 @@ import { clientBuildId, clientCompatibilityVersion, reloadForClientCompatibility
|
|
|
19
20
|
import { ensureEmbedAuthFetchInterceptor, isEmbedAuthActive, } from "./embed-auth.js";
|
|
20
21
|
import { recordActionStart } from "./recorder.js";
|
|
21
22
|
import { currentRouteTemplate } from "./route-template.js";
|
|
23
|
+
import { currentSessionIdentity } from "./session-identity.js";
|
|
22
24
|
import { recheckSessionAfterUnauthorized } from "./use-session.js";
|
|
23
25
|
function actionPrefix() {
|
|
24
26
|
return agentNativePath("/_agent-native/actions");
|
|
@@ -119,20 +121,54 @@ function appendActionQueryParam(qs, key, value) {
|
|
|
119
121
|
}
|
|
120
122
|
qs.append(key, String(value));
|
|
121
123
|
}
|
|
124
|
+
// The server's capability lives 10 minutes; re-mint a minute early so a call
|
|
125
|
+
// never leaves with a capability that expires in flight.
|
|
126
|
+
const UI_CAPABILITY_REUSE_MS = 9 * 60_000;
|
|
127
|
+
// Every signed-in write waits on the shared mint, so a mint that never answers
|
|
128
|
+
// must settle on its own instead of holding writes until their own timeouts.
|
|
129
|
+
const UI_CAPABILITY_MINT_TIMEOUT_MS = 10_000;
|
|
122
130
|
let uiCapabilityRequest;
|
|
131
|
+
let uiCapability = null;
|
|
132
|
+
function hasFreshUiCapability() {
|
|
133
|
+
return (uiCapability !== null &&
|
|
134
|
+
uiCapability.identity === currentSessionIdentity() &&
|
|
135
|
+
Date.now() - uiCapability.at < UI_CAPABILITY_REUSE_MS);
|
|
136
|
+
}
|
|
137
|
+
/** Drop the remembered capability so the next call that needs one mints it. */
|
|
138
|
+
export function forgetUiActionCapability() {
|
|
139
|
+
uiCapability = null;
|
|
140
|
+
}
|
|
123
141
|
// The mint is shared by concurrent callers, so one caller's abort rejects only
|
|
124
142
|
// its own wait and never cancels the request the others are waiting on.
|
|
125
143
|
function ensureUiActionCapability(signal) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
144
|
+
if (hasFreshUiCapability())
|
|
145
|
+
return Promise.resolve();
|
|
146
|
+
const identity = currentSessionIdentity();
|
|
147
|
+
const request = uiCapabilityRequest ?? (uiCapabilityRequest = mint());
|
|
148
|
+
function mint() {
|
|
149
|
+
const controller = typeof AbortController === "undefined" ? null : new AbortController();
|
|
150
|
+
let timer;
|
|
151
|
+
const timedOut = new Promise((_resolve, reject) => {
|
|
152
|
+
timer = setTimeout(() => {
|
|
153
|
+
controller?.abort();
|
|
154
|
+
reject(new Error("Timed out establishing the browser UI capability."));
|
|
155
|
+
}, UI_CAPABILITY_MINT_TIMEOUT_MS);
|
|
156
|
+
});
|
|
157
|
+
const response = fetch(agentNativePath("/_agent-native/ui-capability"), {
|
|
158
|
+
credentials: "same-origin",
|
|
159
|
+
cache: "no-store",
|
|
160
|
+
signal: controller?.signal,
|
|
161
|
+
}).then((res) => {
|
|
162
|
+
if (!res.ok) {
|
|
130
163
|
throw new Error("Could not establish the browser UI capability.");
|
|
131
164
|
}
|
|
132
|
-
|
|
133
|
-
|
|
165
|
+
uiCapability = { identity, at: Date.now() };
|
|
166
|
+
});
|
|
167
|
+
return Promise.race([response, timedOut]).finally(() => {
|
|
168
|
+
clearTimeout(timer);
|
|
134
169
|
uiCapabilityRequest = undefined;
|
|
135
|
-
})
|
|
170
|
+
});
|
|
171
|
+
}
|
|
136
172
|
if (!signal)
|
|
137
173
|
return request;
|
|
138
174
|
return new Promise((resolve, reject) => {
|
|
@@ -145,6 +181,45 @@ function ensureUiActionCapability(signal) {
|
|
|
145
181
|
.finally(() => signal.removeEventListener("abort", onAbort));
|
|
146
182
|
});
|
|
147
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Every write from the signed-in app carries the UI capability, so an action
|
|
186
|
+
* that needs approval runs on its first request instead of failing once and
|
|
187
|
+
* retrying. The mint waits for a known signed-in session: a public page or an
|
|
188
|
+
* embed has none to mint from, and its writes go out as they are. A failed
|
|
189
|
+
* mint never holds a write back; the server decides what the write may do.
|
|
190
|
+
* A keepalive write never waits on a mint: it is sent as the page goes away,
|
|
191
|
+
* and a deferred send can be lost with it.
|
|
192
|
+
*/
|
|
193
|
+
function presentUiCapability(method, options, signal) {
|
|
194
|
+
const identity = currentSessionIdentity();
|
|
195
|
+
let pending;
|
|
196
|
+
if (options?.uiOnly) {
|
|
197
|
+
pending = ensureUiActionCapability(signal);
|
|
198
|
+
}
|
|
199
|
+
else if (method === "GET" ||
|
|
200
|
+
options?.keepalive ||
|
|
201
|
+
typeof window === "undefined" ||
|
|
202
|
+
isEmbedAuthActive() ||
|
|
203
|
+
typeof identity !== "string") {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
pending = ensureUiActionCapability(signal).catch((error) => {
|
|
208
|
+
if (signal?.aborted)
|
|
209
|
+
throw error;
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (typeof identity !== "string")
|
|
213
|
+
return pending;
|
|
214
|
+
// The write leaves only after the mint, and the browser sends whichever
|
|
215
|
+
// session's cookies it holds by then: a user who changed in between must not
|
|
216
|
+
// receive the first user's write.
|
|
217
|
+
return pending.then(() => {
|
|
218
|
+
if (currentSessionIdentity() !== identity) {
|
|
219
|
+
throw new Error("The signed-in user changed before this write was sent.");
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
148
223
|
export const ACTION_KEEPALIVE_BODY_BUDGET_BYTES = 48_000;
|
|
149
224
|
let reservedKeepaliveBodyBytes = 0;
|
|
150
225
|
function utf8ByteLength(value) {
|
|
@@ -271,9 +346,8 @@ async function performActionFetch(name, method, params, options) {
|
|
|
271
346
|
batchUrl: `${actionPrefix()}/${ACTION_BATCH_ACTION_NAME}`,
|
|
272
347
|
})
|
|
273
348
|
: fetch(url, init);
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
: send();
|
|
349
|
+
const capability = presentUiCapability(method, options, controller?.signal);
|
|
350
|
+
const request = capability ? capability.then(send) : send();
|
|
277
351
|
res = await Promise.race([request, timedOutSignal]);
|
|
278
352
|
throwIfAborted(outerSignal);
|
|
279
353
|
options?.onResponse?.(res);
|
|
@@ -340,9 +414,10 @@ async function performActionFetch(name, method, params, options) {
|
|
|
340
414
|
}
|
|
341
415
|
if (!res.ok) {
|
|
342
416
|
if (res.status === 403 &&
|
|
343
|
-
data?.errorCode ===
|
|
417
|
+
data?.errorCode === UI_CAPABILITY_REQUIRED_ERROR_CODE &&
|
|
344
418
|
!options?.uiCapabilityRetry &&
|
|
345
419
|
typeof window !== "undefined") {
|
|
420
|
+
forgetUiActionCapability();
|
|
346
421
|
return performActionFetch(name, method, params, {
|
|
347
422
|
...options,
|
|
348
423
|
uiOnly: true,
|
|
@@ -616,6 +691,7 @@ export function callAction(actionName, params, options = {}) {
|
|
|
616
691
|
includeRequestSource: false,
|
|
617
692
|
headers: options.headers,
|
|
618
693
|
uiOnly: options.uiOnly,
|
|
694
|
+
onResponse: options.onResponse,
|
|
619
695
|
});
|
|
620
696
|
}
|
|
621
697
|
export function callActionBlob(actionName, params, options = {}) {
|
|
@@ -626,6 +702,7 @@ export function callActionBlob(actionName, params, options = {}) {
|
|
|
626
702
|
headers: options.headers,
|
|
627
703
|
responseType: "blob",
|
|
628
704
|
uiOnly: options.uiOnly,
|
|
705
|
+
onResponse: options.onResponse,
|
|
629
706
|
});
|
|
630
707
|
}
|
|
631
708
|
function isAbortError(error) {
|
|
@@ -854,6 +931,32 @@ export function revokeBrowserPersistableActionQuery(query) {
|
|
|
854
931
|
browserPersistableActionQueries.delete(query);
|
|
855
932
|
browserPersistableActionQueryBindings.delete(query);
|
|
856
933
|
}
|
|
934
|
+
/** The persisted-cache binding to capture when a fetch starts, for `recordActionQueryBrowserPersist`. */
|
|
935
|
+
export function currentActionQueryCacheBinding() {
|
|
936
|
+
return actionQueryCacheBindingToken;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Lets an `["action", ...]` query filled by a hand-written `queryFn` or
|
|
940
|
+
* `setQueryData` join the browser's persisted query cache the way a
|
|
941
|
+
* `useActionQuery` result does: only when its response carried the persist
|
|
942
|
+
* header, and only into the binding that was active when the fetch began, so a
|
|
943
|
+
* result fetched for one user is never written into another user's record.
|
|
944
|
+
*/
|
|
945
|
+
export function recordActionQueryBrowserPersist(query, response, bindingTokenAtFetch) {
|
|
946
|
+
if (response?.headers.get(ACTION_BROWSER_PERSIST_HEADER) !==
|
|
947
|
+
ACTION_BROWSER_PERSIST_ALLOW) {
|
|
948
|
+
revokeBrowserPersistableActionQuery(query);
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
browserPersistableActionQueries.add(query);
|
|
952
|
+
if (bindingTokenAtFetch !== undefined &&
|
|
953
|
+
actionQueryCacheBindingToken === bindingTokenAtFetch) {
|
|
954
|
+
browserPersistableActionQueryBindings.set(query, bindingTokenAtFetch);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
browserPersistableActionQueryBindings.delete(query);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
857
960
|
const UNBATCHED_REFETCHES_META_KEY = "actionUnbatchedRefetches";
|
|
858
961
|
/**
|
|
859
962
|
* A first fetch is what the page is waiting on, so it goes alone. On
|
|
@@ -887,16 +990,14 @@ export function useActionQuery(actionName, params, options) {
|
|
|
887
990
|
const query = client.getQueryCache().find({ queryKey, exact: true });
|
|
888
991
|
const bindingTokenAtFetch = actionQueryCacheBindingToken;
|
|
889
992
|
assertActionCircuitClosed(key);
|
|
890
|
-
let
|
|
993
|
+
let lastResponse;
|
|
891
994
|
let result;
|
|
892
995
|
try {
|
|
893
996
|
result = await actionFetch(actionName, "GET", params, {
|
|
894
997
|
signal,
|
|
895
998
|
batch: canBatchActionQueryFetch(query),
|
|
896
999
|
onResponse: (response) => {
|
|
897
|
-
|
|
898
|
-
response.headers.get(ACTION_BROWSER_PERSIST_HEADER) ===
|
|
899
|
-
ACTION_BROWSER_PERSIST_ALLOW;
|
|
1000
|
+
lastResponse = response;
|
|
900
1001
|
},
|
|
901
1002
|
});
|
|
902
1003
|
}
|
|
@@ -916,19 +1017,7 @@ export function useActionQuery(actionName, params, options) {
|
|
|
916
1017
|
resetActionFailureCircuit(key);
|
|
917
1018
|
if (query && !signal.aborted) {
|
|
918
1019
|
clearAccessDeniedActionQueryBinding(query);
|
|
919
|
-
|
|
920
|
-
browserPersistableActionQueries.add(query);
|
|
921
|
-
if (bindingTokenAtFetch !== undefined &&
|
|
922
|
-
actionQueryCacheBindingToken === bindingTokenAtFetch) {
|
|
923
|
-
browserPersistableActionQueryBindings.set(query, bindingTokenAtFetch);
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
browserPersistableActionQueryBindings.delete(query);
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
else {
|
|
930
|
-
revokeBrowserPersistableActionQuery(query);
|
|
931
|
-
}
|
|
1020
|
+
recordActionQueryBrowserPersist(query, lastResponse, bindingTokenAtFetch);
|
|
932
1021
|
}
|
|
933
1022
|
return result;
|
|
934
1023
|
},
|
|
@@ -4,6 +4,7 @@ import { setSentryUser, trackEvent, trackSessionStatus } from "./analytics.js";
|
|
|
4
4
|
import { agentNativeApiDisabledReason } from "./api-surface.js";
|
|
5
5
|
import { expireClientStatusResult, fetchAuthSessionStatus, invalidateClientStatusRequest, SESSION_RESULT_LIFETIME_MS, } from "./client-status-requests.js";
|
|
6
6
|
import { getFrameOrigin, getFramePostMessageTargetOrigin } from "./frame.js";
|
|
7
|
+
import { recordSessionIdentity } from "./session-identity.js";
|
|
7
8
|
export { hasSessionHint, isSessionNavigationPending, } from "../shared/ssr-session-bootstrap.js";
|
|
8
9
|
/**
|
|
9
10
|
* True while the app tree is mounted hidden ahead of the session read (a hinted
|
|
@@ -105,6 +106,7 @@ function hasFreshSessionCache() {
|
|
|
105
106
|
function publishSessionIdentity(session) {
|
|
106
107
|
const identity = session?.userId ?? session?.email ?? null;
|
|
107
108
|
const authUserId = session?.authUserId;
|
|
109
|
+
recordSessionIdentity(identity);
|
|
108
110
|
if (trackedSessionIdentity !== identity ||
|
|
109
111
|
trackedSessionAuthUserId !== authUserId) {
|
|
110
112
|
trackedSessionIdentity = identity;
|
|
@@ -38,19 +38,19 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
38
38
|
error: string;
|
|
39
39
|
states?: undefined;
|
|
40
40
|
} | {
|
|
41
|
-
error?: undefined;
|
|
42
41
|
states: {
|
|
43
42
|
clientId: number;
|
|
44
43
|
state: string;
|
|
45
44
|
}[];
|
|
45
|
+
error?: undefined;
|
|
46
46
|
}>>;
|
|
47
47
|
export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
48
48
|
error: string;
|
|
49
49
|
users?: undefined;
|
|
50
50
|
} | {
|
|
51
|
-
error?: undefined;
|
|
52
51
|
users: {
|
|
53
52
|
clientId: number;
|
|
54
53
|
lastSeen: number;
|
|
55
54
|
}[];
|
|
55
|
+
error?: undefined;
|
|
56
56
|
}>>;
|
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;
|