@agent-native/core 0.213.1 → 0.216.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action.js +54 -1
- package/dist/agent/actions/disconnect-chatgpt-subscription.js +5 -0
- package/dist/agent/actions/get-agentkit-capabilities.js +3 -3
- package/dist/agent/actions/manage-builder-connection.js +5 -0
- package/dist/agent/actions/manage-provider-key-policy.js +5 -0
- package/dist/agent/actions/manage-provider-models.js +5 -0
- package/dist/agent/actions/manage-service-providers.js +5 -0
- package/dist/agent/actions/select-chatgpt-subscription-account.js +5 -0
- package/dist/agent/context-xray/actions/context-evict.js +5 -0
- package/dist/agent/context-xray/actions/context-pin.js +5 -0
- package/dist/agent/context-xray/actions/context-report.js +5 -0
- package/dist/agent/context-xray/actions/context-restore.js +5 -0
- package/dist/agent/execution-fence.d.ts +1 -0
- package/dist/agent/execution-fence.js +5 -0
- package/dist/agent/production-agent.d.ts +35 -2
- package/dist/agent/production-agent.js +629 -32
- package/dist/agent/run-manager.d.ts +2 -0
- package/dist/agent/run-manager.js +397 -115
- package/dist/agent/run-slot-lock.d.ts +2 -0
- package/dist/agent/run-slot-lock.js +6 -0
- package/dist/agent/run-store.d.ts +5 -0
- package/dist/agent/run-store.js +423 -156
- package/dist/agent/thread-data-builder.js +27 -5
- package/dist/agent/types.d.ts +6 -12
- package/dist/agentkit/capabilities.d.ts +1 -1
- package/dist/appearance/actions/get-theme-family.d.ts +8 -0
- package/dist/appearance/actions/get-theme-family.js +38 -0
- package/dist/appearance/actions/set-theme-family.d.ts +7 -0
- package/dist/appearance/actions/set-theme-family.js +33 -0
- package/dist/appearance/theme-family.d.ts +13 -0
- package/dist/appearance/theme-family.js +38 -0
- package/dist/application-state/store.d.ts +5 -0
- package/dist/application-state/store.js +26 -0
- package/dist/chat-threads/schema-migrations.js +17 -0
- package/dist/chat-threads/store.d.ts +21 -1
- package/dist/chat-threads/store.js +223 -41
- package/dist/cli/skills-content/turn-into-app-skill.d.ts +1 -1
- package/dist/cli/skills-content/turn-into-app-skill.js +20 -18
- package/dist/cli/templates-meta.js +14 -0
- package/dist/client/analytics.js +167 -17
- package/dist/client/chat/agentkit-agent-native.js +79 -3
- package/dist/client/chat/agentkit-protocol.js +80 -42
- package/dist/client/chat/runtime.d.ts +5 -1
- package/dist/client/chat/runtime.js +46 -3
- package/dist/client/consent.d.ts +36 -0
- package/dist/client/consent.js +283 -0
- package/dist/client/review/use-review.d.ts +2 -0
- package/dist/client/review/use-review.js +28 -1
- package/dist/client/sharing/useResourceAccessGate.d.ts +3 -2
- package/dist/client/sharing/useResourceAccessGate.js +9 -1
- package/dist/client/theme-family.d.ts +35 -0
- package/dist/client/theme-family.js +223 -0
- package/dist/client/theme.d.ts +1 -1
- package/dist/client/theme.js +5 -2
- package/dist/client/ui/index.d.ts +1 -0
- package/dist/client/ui/index.js +1 -0
- package/dist/client/use-chat-thread-list.d.ts +2 -0
- package/dist/client/use-chat-thread-list.js +12 -2
- package/dist/client/use-chat-threads.d.ts +1 -0
- package/dist/client/use-chat-threads.js +2 -0
- package/dist/connections/catalog.d.ts +5 -5
- package/dist/connections/catalog.js +22 -3
- package/dist/data-widgets/index.d.ts +374 -5
- package/dist/data-widgets/index.js +242 -11
- package/dist/db/migrations.js +4 -1
- package/dist/db/schema-snapshot.generated.js +8 -1
- package/dist/feature-flags/actions/set-feature-flag.js +5 -0
- package/dist/feature-flags/index.d.ts +1 -1
- package/dist/feature-flags/index.js +1 -1
- package/dist/feature-flags/store.d.ts +12 -0
- package/dist/feature-flags/store.js +79 -7
- package/dist/file-upload/actions/manage-file-storage.js +5 -0
- package/dist/file-upload/actions/upload-image.js +5 -0
- package/dist/framework-tools.js +2 -0
- package/dist/history/actions/create-resource-version.js +5 -0
- package/dist/history/actions/restore-resource-version.js +5 -0
- package/dist/ingestion/index.d.ts +1 -1
- package/dist/ingestion/index.js +1 -1
- package/dist/ingestion/pptx.d.ts +11 -0
- package/dist/ingestion/pptx.js +81 -1
- package/dist/integrations/actions/manage-messaging-channel.js +5 -0
- package/dist/jobs/scheduler.js +17 -1
- package/dist/labs/actions/set-lab.js +5 -0
- package/dist/localization/actions/set-localization-preference.js +5 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/observability/actions/save-observability-instruction-update.js +5 -0
- package/dist/observability/actions/save-observability-review-summary.js +5 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +19 -19
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/provider-api/index.js +9 -2
- package/dist/review/actions/consume-review-feedback.js +5 -0
- package/dist/review/actions/create-review-comment.js +5 -0
- package/dist/review/actions/delete-review-comment.js +5 -0
- package/dist/review/actions/react-to-review-comment.js +5 -0
- package/dist/review/actions/reply-review-comment.js +5 -0
- package/dist/review/actions/resolve-review-thread.js +5 -0
- package/dist/review/actions/send-review-thread-to-agent.js +5 -0
- package/dist/review/actions/set-review-status.js +5 -0
- package/dist/review/actions/set-review-thread-muted.js +5 -0
- package/dist/review/actions/set-review-thread-unread.js +5 -0
- package/dist/review/actions/set-review-threads-unread.js +5 -0
- package/dist/review/actions/update-review-comment-anchor.js +5 -0
- package/dist/review/actions/update-review-comment.js +5 -0
- package/dist/review/suggestions/actions.js +60 -21
- package/dist/review/suggestions/store.d.ts +2 -0
- package/dist/review/suggestions/store.js +28 -0
- package/dist/secrets/actions/delete-api-key.js +5 -0
- package/dist/secrets/routes.d.ts +10 -10
- package/dist/server/action-discovery.js +8 -0
- package/dist/server/agent-chat/context-tools.js +105 -2
- package/dist/server/agent-chat-plugin.d.ts +2 -0
- package/dist/server/agent-chat-plugin.js +242 -54
- package/dist/server/agent-engine-default-model-route.d.ts +1 -1
- package/dist/server/agent-engine-ollama-models-route.d.ts +1 -1
- package/dist/server/application-state-maintenance.d.ts +2 -0
- package/dist/server/application-state-maintenance.js +2 -0
- package/dist/server/attribution.d.ts +14 -0
- package/dist/server/attribution.js +62 -2
- package/dist/server/auth.js +7 -0
- package/dist/server/consent-route.d.ts +41 -0
- package/dist/server/consent-route.js +102 -0
- package/dist/server/first-touch-route.d.ts +39 -0
- package/dist/server/first-touch-route.js +115 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/queued-submission-inbox-drain.d.ts +9 -0
- package/dist/server/queued-submission-inbox-drain.js +12 -0
- package/dist/server/request-origin.d.ts +6 -0
- package/dist/server/request-origin.js +12 -0
- package/dist/settings/org-settings.d.ts +2 -1
- package/dist/settings/org-settings.js +4 -1
- package/dist/settings/store.d.ts +8 -0
- package/dist/settings/store.js +14 -5
- package/dist/shared/consent.d.ts +31 -0
- package/dist/shared/consent.js +145 -0
- package/dist/shared/first-touch-persistence.d.ts +31 -0
- package/dist/shared/first-touch-persistence.js +53 -0
- package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
- package/dist/sharing/actions/set-resource-visibility.js +5 -0
- package/dist/sharing/actions/share-resource.js +5 -0
- package/dist/sharing/actions/unshare-resource.js +5 -0
- package/dist/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/dist/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/dist/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
- package/dist/usage/actions/get-usage-metrics.js +27 -4
- package/dist/usage/actions/manage-usage-alert.js +5 -0
- package/dist/vite/client.js +8 -6
- package/dist/workspace-connections/actions/bulk-update-workspace-user-groups.js +5 -0
- package/dist/workspace-connections/actions/delete-workspace-user-group.js +5 -0
- package/dist/workspace-connections/actions/upsert-workspace-user-group.js +5 -0
- package/docs/content/actions-access-control.mdx +8 -8
- package/docs/content/actions-defining.mdx +9 -8
- package/docs/content/template-design-developers.mdx +9 -9
- package/docs/content/template-design-features.mdx +1 -3
- package/docs/content/turn-into-app.mdx +4 -0
- package/package.json +4 -3
- package/src/templates/chat/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/default/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +36 -80
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +54 -16
- package/src/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +50 -73
- package/src/templates/headless/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/workspace-core/.agents/skills/actions/references/action-fields.md +1 -1
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +20 -18
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentKitRunSlotBusyError } from "@agent-native/agentkit/client";
|
|
2
|
+
import { parseAgentQueuedSubmissionReceipt, } 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 { RUN_NOT_STARTED_METADATA_KEY } from "../../shared/agent-chat-run-not-started.js";
|
|
4
5
|
import { agentEngineStatusUrlForChatApi, requireAgentEngineConfiguredForDispatch, } from "../agent-engine-readiness.js";
|
|
@@ -261,6 +262,8 @@ function defaultHttpRuntimeRequest(input) {
|
|
|
261
262
|
messages: input.turn.messages,
|
|
262
263
|
attachments: input.turn.attachments,
|
|
263
264
|
submissionPayload: input.turn.submissionPayload,
|
|
265
|
+
options: input.turn.options,
|
|
266
|
+
references: input.turn.references,
|
|
264
267
|
tools: input.turn.tools,
|
|
265
268
|
model: input.turn.model,
|
|
266
269
|
reasoningEffort: input.turn.reasoningEffort,
|
|
@@ -476,6 +479,43 @@ export function createHttpAgentChatRuntime(options) {
|
|
|
476
479
|
}
|
|
477
480
|
throw withTurnId(error, turnId);
|
|
478
481
|
}
|
|
482
|
+
if (response.status === 202) {
|
|
483
|
+
let queuedSubmission;
|
|
484
|
+
try {
|
|
485
|
+
if (!response.headers
|
|
486
|
+
.get("Content-Type")
|
|
487
|
+
?.toLowerCase()
|
|
488
|
+
.includes("application/json")) {
|
|
489
|
+
throw new TypeError("Expected a JSON queued submission receipt.");
|
|
490
|
+
}
|
|
491
|
+
queuedSubmission = parseAgentQueuedSubmissionReceipt(await response.json());
|
|
492
|
+
const submission = asRecord(turn.submissionPayload);
|
|
493
|
+
const expectedThreadId = summary.threadId ?? sessionId;
|
|
494
|
+
if (queuedSubmission.message.threadId !== expectedThreadId ||
|
|
495
|
+
(typeof submission?.id === "string" &&
|
|
496
|
+
queuedSubmission.message.id !== submission.id)) {
|
|
497
|
+
throw new TypeError("The queued submission receipt does not match this session turn.");
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (cause) {
|
|
501
|
+
cleanup();
|
|
502
|
+
throw withTurnId(Object.assign(new Error(cause instanceof Error
|
|
503
|
+
? `Invalid queued submission response: ${cause.message}`
|
|
504
|
+
: "Invalid queued submission response.", { cause }), {
|
|
505
|
+
code: "invalid_queued_submission_response",
|
|
506
|
+
status: response.status,
|
|
507
|
+
retryable: true,
|
|
508
|
+
}), turnId);
|
|
509
|
+
}
|
|
510
|
+
cleanup();
|
|
511
|
+
forgetTurnContext(turnId, turn);
|
|
512
|
+
return {
|
|
513
|
+
id: turnId,
|
|
514
|
+
sessionId,
|
|
515
|
+
queuedSubmission,
|
|
516
|
+
events: (async function* () { })(),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
479
519
|
const runId = response.headers.get("X-Run-Id") ?? undefined;
|
|
480
520
|
const events = (async function* () {
|
|
481
521
|
let retryableFailure = false;
|
|
@@ -3230,6 +3270,8 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
3230
3270
|
...(turn.submissionPayload !== undefined
|
|
3231
3271
|
? { submissionPayload: turn.submissionPayload }
|
|
3232
3272
|
: {}),
|
|
3273
|
+
...(turn.references?.length ? { references: turn.references } : {}),
|
|
3274
|
+
...(turn.options ? { options: turn.options } : {}),
|
|
3233
3275
|
...(turn.queuePromotion
|
|
3234
3276
|
? {
|
|
3235
3277
|
queuedMessageId: turn.queuePromotion.messageId,
|
|
@@ -3298,6 +3340,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
3298
3340
|
const turnToContinue = continuation.attachments?.length
|
|
3299
3341
|
? { ...previousTurn, attachments: continuation.attachments }
|
|
3300
3342
|
: previousTurn;
|
|
3343
|
+
const { submissionPayload: _submissionPayload, ...continuationTurn } = turnToContinue ?? {};
|
|
3301
3344
|
const approval = continuation.approval;
|
|
3302
3345
|
const connection = continuation.connection;
|
|
3303
3346
|
const messageStateKey = continuation.turnId ?? session.id;
|
|
@@ -3314,7 +3357,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
3314
3357
|
if (messageState)
|
|
3315
3358
|
messageState.message.connectionPending = false;
|
|
3316
3359
|
return startTurn({
|
|
3317
|
-
...
|
|
3360
|
+
...continuationTurn,
|
|
3318
3361
|
prompt: continuation.prompt ??
|
|
3319
3362
|
`The ${connection.id} connection is now available. Continue the requested work.`,
|
|
3320
3363
|
metadata: {
|
|
@@ -3334,7 +3377,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
3334
3377
|
if (messageState)
|
|
3335
3378
|
messageState.message.approvalPending = false;
|
|
3336
3379
|
return startTurn({
|
|
3337
|
-
...
|
|
3380
|
+
...continuationTurn,
|
|
3338
3381
|
prompt: continuation.prompt,
|
|
3339
3382
|
metadata: {
|
|
3340
3383
|
...previousMetadata,
|
|
@@ -3376,7 +3419,7 @@ export function createAgentNativeChatRuntime(options = {}) {
|
|
|
3376
3419
|
if (messageState)
|
|
3377
3420
|
messageState.message.approvalPending = false;
|
|
3378
3421
|
return startTurn({
|
|
3379
|
-
...
|
|
3422
|
+
...continuationTurn,
|
|
3380
3423
|
prompt: continuation.prompt ??
|
|
3381
3424
|
"Approved. Go ahead and run the requested action.",
|
|
3382
3425
|
metadata: {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type ConsentCategory, type ConsentChoice, type ConsentRegion } from "../shared/consent.js";
|
|
2
|
+
export type { ConsentCategory, ConsentChoice, ConsentRegion };
|
|
3
|
+
/** The visitor's stored choice, or `null` before they've made one. */
|
|
4
|
+
export declare function getConsentChoice(): ConsentChoice | null;
|
|
5
|
+
/**
|
|
6
|
+
* Whether this visitor gets the banner: the server's answer from the
|
|
7
|
+
* request's country, else the browser's time zone. Asked once a session.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveConsentRegion(): Promise<ConsentRegion>;
|
|
10
|
+
/**
|
|
11
|
+
* Whether `category` is allowed right now, on the time zone's guess until the
|
|
12
|
+
* server answers. Pair it with `whenConsentAllows` and a `revoke` that undoes
|
|
13
|
+
* whatever this let through.
|
|
14
|
+
*/
|
|
15
|
+
export declare function consentAllowsNow(category: ConsentCategory): boolean;
|
|
16
|
+
export interface WhenConsentAllowsOptions {
|
|
17
|
+
/**
|
|
18
|
+
* Run now on the time zone's guess instead of waiting for the server, and
|
|
19
|
+
* call `revoke` if the server or the visitor then says no. Only for storage
|
|
20
|
+
* `revoke` can fully undo; a third-party script can't be unloaded.
|
|
21
|
+
*/
|
|
22
|
+
optimistic?: boolean;
|
|
23
|
+
revoke?: () => void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run `run` once `category` is allowed: at once outside the consent region,
|
|
27
|
+
* and inside it when the visitor accepts. A later "reject" calls `revoke`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function whenConsentAllows(category: ConsentCategory, run: () => void, options?: WhenConsentAllowsOptions): void;
|
|
30
|
+
/** Store the visitor's choice for every app on this site, and apply it. */
|
|
31
|
+
export declare function setConsentChoice(choice: ConsentChoice): void;
|
|
32
|
+
/** Reopen the banner so the visitor can change their choice. */
|
|
33
|
+
export declare function openConsentSettings(): void;
|
|
34
|
+
/** Whether the banner should show now, for the banner component. */
|
|
35
|
+
export declare function shouldShowConsentBanner(): boolean;
|
|
36
|
+
export declare function subscribeConsent(listener: () => void): () => void;
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { CONSENT_COOKIE_LIFETIME_SECONDS, CONSENT_COOKIE_NAME, CONSENT_PATH, CONSENT_REGION_PATH, consentCookieDomain, isConsentRegionTimeZone, parseConsent, serializeConsent, } from "../shared/consent.js";
|
|
2
|
+
import { agentNativePath } from "./api-path.js";
|
|
3
|
+
import { agentNativeApiDisabledReason } from "./api-surface.js";
|
|
4
|
+
/** Strictly necessary for the banner: which region this session is in. */
|
|
5
|
+
const REGION_STORAGE_KEY = "an_consent_region";
|
|
6
|
+
/**
|
|
7
|
+
* Scripts that run before this module loads, like the Google tags the server
|
|
8
|
+
* injects into the page, queue `[category, run]` here.
|
|
9
|
+
*/
|
|
10
|
+
const SCRIPT_QUEUE_KEY = "__AN_CONSENT_QUEUE__";
|
|
11
|
+
// One state per page, however many copies of this module a bundle carries.
|
|
12
|
+
const STATE_KEY = Symbol.for("agent-native.client.consent");
|
|
13
|
+
function state() {
|
|
14
|
+
const holder = globalThis;
|
|
15
|
+
return (holder[STATE_KEY] ??= {
|
|
16
|
+
region: null,
|
|
17
|
+
regionPromise: null,
|
|
18
|
+
waiters: [],
|
|
19
|
+
listeners: new Set(),
|
|
20
|
+
settingsOpen: false,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function readCookie(name) {
|
|
24
|
+
if (typeof document === "undefined")
|
|
25
|
+
return null;
|
|
26
|
+
try {
|
|
27
|
+
for (const part of document.cookie ? document.cookie.split(";") : []) {
|
|
28
|
+
const eq = part.indexOf("=");
|
|
29
|
+
if (eq !== -1 && part.slice(0, eq).trim() === name) {
|
|
30
|
+
return part.slice(eq + 1).trim();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// coercion-ok: a sandboxed iframe can't read cookies, so it holds no
|
|
36
|
+
// choice and the visitor is asked.
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
/** The visitor's stored choice, or `null` before they've made one. */
|
|
41
|
+
export function getConsentChoice() {
|
|
42
|
+
return parseConsent(readCookie(CONSENT_COOKIE_NAME));
|
|
43
|
+
}
|
|
44
|
+
function cookieDomain() {
|
|
45
|
+
const host = typeof window === "undefined" ? "" : window.location.hostname;
|
|
46
|
+
const domain = consentCookieDomain(host);
|
|
47
|
+
return domain ? `; domain=${domain}` : "";
|
|
48
|
+
}
|
|
49
|
+
function writeChoiceCookie(choice) {
|
|
50
|
+
try {
|
|
51
|
+
document.cookie =
|
|
52
|
+
`${CONSENT_COOKIE_NAME}=${serializeConsent(choice)}; path=/; ` +
|
|
53
|
+
`max-age=${CONSENT_COOKIE_LIFETIME_SECONDS}; SameSite=Lax${cookieDomain()}`;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// coercion-ok: a sandboxed iframe can't store the choice; it still
|
|
57
|
+
// applies to this page.
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let serverChoiceWrites = Promise.resolve();
|
|
61
|
+
let latestChoice = null;
|
|
62
|
+
/**
|
|
63
|
+
* Safari keeps a cookie page JavaScript wrote for seven days at most, so the
|
|
64
|
+
* server writes the choice again for its full year. Writes go one at a time
|
|
65
|
+
* and only the newest is sent. A response for an older choice still sets that
|
|
66
|
+
* choice's cookie, so the newest is written back over it at once.
|
|
67
|
+
*/
|
|
68
|
+
function storeChoiceOnServer(choice) {
|
|
69
|
+
latestChoice = choice;
|
|
70
|
+
if (agentNativeApiDisabledReason() || typeof fetch !== "function")
|
|
71
|
+
return;
|
|
72
|
+
serverChoiceWrites = serverChoiceWrites.then(async () => {
|
|
73
|
+
if (latestChoice !== choice)
|
|
74
|
+
return;
|
|
75
|
+
await fetch(agentNativePath(CONSENT_PATH), {
|
|
76
|
+
method: "POST",
|
|
77
|
+
credentials: "same-origin",
|
|
78
|
+
keepalive: true,
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({ choice: serializeConsent(choice) }),
|
|
81
|
+
}).catch(() => {
|
|
82
|
+
// coercion-ok: the browser's own copy still holds the choice, for
|
|
83
|
+
// seven days in Safari; the visitor is asked again after that.
|
|
84
|
+
});
|
|
85
|
+
if (latestChoice && latestChoice !== choice) {
|
|
86
|
+
writeChoiceCookie(latestChoice);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function readCachedRegion() {
|
|
91
|
+
try {
|
|
92
|
+
const value = window.sessionStorage.getItem(REGION_STORAGE_KEY);
|
|
93
|
+
return value === "consent" || value === "none" ? value : null;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// coercion-ok: without session storage the region is asked again.
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function cacheRegion(region) {
|
|
101
|
+
try {
|
|
102
|
+
window.sessionStorage.setItem(REGION_STORAGE_KEY, region);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// coercion-ok: without session storage the region is asked again.
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function timeZoneRegion() {
|
|
109
|
+
try {
|
|
110
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
111
|
+
return isConsentRegionTimeZone(timeZone) ? "consent" : "none";
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// An unreadable zone could be anywhere, so it gets the banner.
|
|
115
|
+
return "consent";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function fetchRegion() {
|
|
119
|
+
if (agentNativeApiDisabledReason() || typeof fetch !== "function") {
|
|
120
|
+
return timeZoneRegion();
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetch(agentNativePath(CONSENT_REGION_PATH), {
|
|
124
|
+
credentials: "same-origin",
|
|
125
|
+
});
|
|
126
|
+
const body = response.ok
|
|
127
|
+
? (await response.json())
|
|
128
|
+
: {};
|
|
129
|
+
if (body.region === "consent" || body.region === "none") {
|
|
130
|
+
return body.region;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// coercion-ok: with no answer from the server, the time zone decides.
|
|
135
|
+
}
|
|
136
|
+
return timeZoneRegion();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether this visitor gets the banner: the server's answer from the
|
|
140
|
+
* request's country, else the browser's time zone. Asked once a session.
|
|
141
|
+
*/
|
|
142
|
+
export function resolveConsentRegion() {
|
|
143
|
+
const current = state();
|
|
144
|
+
if (current.region)
|
|
145
|
+
return Promise.resolve(current.region);
|
|
146
|
+
const cached = typeof window === "undefined" ? null : readCachedRegion();
|
|
147
|
+
if (cached) {
|
|
148
|
+
current.region = cached;
|
|
149
|
+
// Another app or tab may have stored a reject since this page's waiters
|
|
150
|
+
// last looked; let them clear what they kept.
|
|
151
|
+
settle();
|
|
152
|
+
notify();
|
|
153
|
+
return Promise.resolve(cached);
|
|
154
|
+
}
|
|
155
|
+
return (current.regionPromise ??= fetchRegion().then((region) => {
|
|
156
|
+
current.region = region;
|
|
157
|
+
cacheRegion(region);
|
|
158
|
+
settle();
|
|
159
|
+
notify();
|
|
160
|
+
return region;
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
/** The best answer available now, without waiting for the server. */
|
|
164
|
+
function regionGuess() {
|
|
165
|
+
const current = state();
|
|
166
|
+
return current.region ?? readCachedRegion() ?? timeZoneRegion();
|
|
167
|
+
}
|
|
168
|
+
function allows(category, region) {
|
|
169
|
+
const choice = getConsentChoice();
|
|
170
|
+
if (choice)
|
|
171
|
+
return choice[category];
|
|
172
|
+
return region === "none";
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Whether `category` is allowed right now, on the time zone's guess until the
|
|
176
|
+
* server answers. Pair it with `whenConsentAllows` and a `revoke` that undoes
|
|
177
|
+
* whatever this let through.
|
|
178
|
+
*/
|
|
179
|
+
export function consentAllowsNow(category) {
|
|
180
|
+
if (typeof window === "undefined")
|
|
181
|
+
return false;
|
|
182
|
+
if (!state().region)
|
|
183
|
+
void resolveConsentRegion();
|
|
184
|
+
return allows(category, regionGuess());
|
|
185
|
+
}
|
|
186
|
+
function settle() {
|
|
187
|
+
const region = state().region;
|
|
188
|
+
for (const waiter of state().waiters) {
|
|
189
|
+
const allowed = allows(waiter.category, region);
|
|
190
|
+
if (allowed && !waiter.ran) {
|
|
191
|
+
waiter.ran = true;
|
|
192
|
+
isolate(waiter.run);
|
|
193
|
+
}
|
|
194
|
+
else if (!allowed && region) {
|
|
195
|
+
// Also clears what an earlier page stored before the banner existed.
|
|
196
|
+
waiter.ran = false;
|
|
197
|
+
if (waiter.revoke)
|
|
198
|
+
isolate(waiter.revoke);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* One callback throwing must not skip the waiters after it, or a later revoke
|
|
204
|
+
* would never clear what a rejected category stored. Rethrow it on its own task
|
|
205
|
+
* so it still surfaces.
|
|
206
|
+
*/
|
|
207
|
+
function isolate(callback) {
|
|
208
|
+
try {
|
|
209
|
+
callback();
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
setTimeout(() => {
|
|
213
|
+
throw error;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function notify() {
|
|
218
|
+
for (const listener of state().listeners)
|
|
219
|
+
listener();
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Run `run` once `category` is allowed: at once outside the consent region,
|
|
223
|
+
* and inside it when the visitor accepts. A later "reject" calls `revoke`.
|
|
224
|
+
*/
|
|
225
|
+
export function whenConsentAllows(category, run, options = {}) {
|
|
226
|
+
if (typeof window === "undefined")
|
|
227
|
+
return;
|
|
228
|
+
const waiter = { category, run, revoke: options.revoke, ran: false };
|
|
229
|
+
state().waiters.push(waiter);
|
|
230
|
+
const region = state().region ??
|
|
231
|
+
readCachedRegion() ??
|
|
232
|
+
(options.optimistic ? regionGuess() : null);
|
|
233
|
+
if (allows(category, region)) {
|
|
234
|
+
waiter.ran = true;
|
|
235
|
+
isolate(run);
|
|
236
|
+
}
|
|
237
|
+
if (!state().region)
|
|
238
|
+
void resolveConsentRegion();
|
|
239
|
+
}
|
|
240
|
+
/** Store the visitor's choice for every app on this site, and apply it. */
|
|
241
|
+
export function setConsentChoice(choice) {
|
|
242
|
+
if (typeof document === "undefined")
|
|
243
|
+
return;
|
|
244
|
+
writeChoiceCookie(choice);
|
|
245
|
+
storeChoiceOnServer(choice);
|
|
246
|
+
state().settingsOpen = false;
|
|
247
|
+
if (!state().region)
|
|
248
|
+
state().region = "consent";
|
|
249
|
+
settle();
|
|
250
|
+
notify();
|
|
251
|
+
}
|
|
252
|
+
/** Reopen the banner so the visitor can change their choice. */
|
|
253
|
+
export function openConsentSettings() {
|
|
254
|
+
state().settingsOpen = true;
|
|
255
|
+
notify();
|
|
256
|
+
}
|
|
257
|
+
/** Whether the banner should show now, for the banner component. */
|
|
258
|
+
export function shouldShowConsentBanner() {
|
|
259
|
+
const current = state();
|
|
260
|
+
if (current.settingsOpen)
|
|
261
|
+
return true;
|
|
262
|
+
return current.region === "consent" && !getConsentChoice();
|
|
263
|
+
}
|
|
264
|
+
export function subscribeConsent(listener) {
|
|
265
|
+
state().listeners.add(listener);
|
|
266
|
+
return () => state().listeners.delete(listener);
|
|
267
|
+
}
|
|
268
|
+
/** Run the scripts the page queued before this module loaded. */
|
|
269
|
+
function drainScriptQueue() {
|
|
270
|
+
if (typeof window === "undefined")
|
|
271
|
+
return;
|
|
272
|
+
const holder = window;
|
|
273
|
+
const queued = holder[SCRIPT_QUEUE_KEY];
|
|
274
|
+
const push = (...entries) => {
|
|
275
|
+
for (const [category, run] of entries)
|
|
276
|
+
whenConsentAllows(category, run);
|
|
277
|
+
return 0;
|
|
278
|
+
};
|
|
279
|
+
holder[SCRIPT_QUEUE_KEY] = { push };
|
|
280
|
+
if (Array.isArray(queued))
|
|
281
|
+
push(...queued);
|
|
282
|
+
}
|
|
283
|
+
drainScriptQueue();
|
|
@@ -269,6 +269,8 @@ export declare function useResourceSuggestions(params: ListResourceSuggestionsPa
|
|
|
269
269
|
export declare function useCreateResourceSuggestion(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestion, Error, CreateResourceSuggestionInput, unknown>;
|
|
270
270
|
export declare function useCreateResourceSuggestionProposal(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestionProposalResult, Error, CreateResourceSuggestionProposalInput, unknown>;
|
|
271
271
|
export declare function useDecideResourceSuggestionProposal(): import("@tanstack/react-query").UseMutationResult<ResourceSuggestionProposalResult, Error, DecideResourceSuggestionProposalInput, unknown>;
|
|
272
|
+
/** Shows every listed proposal member as decided until the server answers. */
|
|
273
|
+
export declare function proposalDecisionOverlay(input: DecideResourceSuggestionProposalInput, queryClient: QueryClient): ReviewOptimisticMutation;
|
|
272
274
|
export declare function useDecideResourceSuggestion(): import("@tanstack/react-query").UseMutationResult<{
|
|
273
275
|
suggestion: ResourceSuggestion;
|
|
274
276
|
decision: unknown;
|
|
@@ -547,7 +547,34 @@ export function useCreateResourceSuggestionProposal() {
|
|
|
547
547
|
return useActionMutation("create-resource-suggestion-proposal");
|
|
548
548
|
}
|
|
549
549
|
export function useDecideResourceSuggestionProposal() {
|
|
550
|
-
return
|
|
550
|
+
return useOptimisticReviewMutation("decide-resource-suggestion-proposal", proposalDecisionOverlay);
|
|
551
|
+
}
|
|
552
|
+
/** Shows every listed proposal member as decided until the server answers. */
|
|
553
|
+
export function proposalDecisionOverlay(input, queryClient) {
|
|
554
|
+
const [first] = input.members;
|
|
555
|
+
const resource = first && resourceForSuggestion(queryClient, first.id);
|
|
556
|
+
if (!resource)
|
|
557
|
+
return noSuggestionOverlay();
|
|
558
|
+
const memberIds = new Set(input.members.map((member) => member.id));
|
|
559
|
+
return {
|
|
560
|
+
action: "list-resource-suggestions",
|
|
561
|
+
resource,
|
|
562
|
+
transform: (data, params) => updateSuggestions(data, (suggestions) => suggestions.flatMap((suggestion) => {
|
|
563
|
+
if (!memberIds.has(suggestion.id))
|
|
564
|
+
return [suggestion];
|
|
565
|
+
const next = { ...suggestion, status: input.decision };
|
|
566
|
+
return matchesSuggestionQuery(params, next) ? [next] : [];
|
|
567
|
+
})),
|
|
568
|
+
onSuccess: (result) => (data, params) => {
|
|
569
|
+
const decided = new Map(result.suggestions.map((suggestion) => [suggestion.id, suggestion]));
|
|
570
|
+
return updateSuggestions(data, (suggestions) => suggestions.flatMap((suggestion) => {
|
|
571
|
+
const next = decided.get(suggestion.id);
|
|
572
|
+
if (!next)
|
|
573
|
+
return [suggestion];
|
|
574
|
+
return matchesSuggestionQuery(params, next) ? [next] : [];
|
|
575
|
+
}));
|
|
576
|
+
},
|
|
577
|
+
};
|
|
551
578
|
}
|
|
552
579
|
export function useDecideResourceSuggestion() {
|
|
553
580
|
return useOptimisticReviewMutation("decide-resource-suggestion", (input, queryClient) => {
|
|
@@ -30,8 +30,9 @@ export interface ResourceAccessGateOptions {
|
|
|
30
30
|
* Called once when the status says the viewer can open the resource: either
|
|
31
31
|
* the read that failed should be tried again, or the owner shared it while
|
|
32
32
|
* this screen was open. The status is checked again whenever the window
|
|
33
|
-
* regains focus, including switching back from another browser window,
|
|
34
|
-
* every half minute while the viewer's request is open
|
|
33
|
+
* regains focus, including switching back from another browser window,
|
|
34
|
+
* every half minute while the viewer's request is open, and every 12 seconds
|
|
35
|
+
* while the resource is in Trash.
|
|
35
36
|
*/
|
|
36
37
|
onAccessGranted?: () => void;
|
|
37
38
|
}
|
|
@@ -4,6 +4,10 @@ import { useActionMutation, useActionQuery } from "../use-action.js";
|
|
|
4
4
|
// While a request is open, approving it should open the resource without a
|
|
5
5
|
// manual reload, even when the window never loses focus.
|
|
6
6
|
const PENDING_REQUEST_POLL_MS = 30_000;
|
|
7
|
+
// This screen replaces the resource's own view, and with it that view's sync
|
|
8
|
+
// poll, so a restore made elsewhere would otherwise wait for focus or the
|
|
9
|
+
// app-wide idle poll, which backs off to minutes. 12 s is that view's poll.
|
|
10
|
+
const TRASHED_POLL_MS = 12_000;
|
|
7
11
|
function requestFailure(error) {
|
|
8
12
|
const failure = error;
|
|
9
13
|
return {
|
|
@@ -24,7 +28,11 @@ export function useResourceAccessGate({ resourceType, resourceId, enabled = true
|
|
|
24
28
|
enabled,
|
|
25
29
|
refetchOnWindowFocus: "always",
|
|
26
30
|
staleTime: 0,
|
|
27
|
-
refetchInterval: (current) => current.state.data?.
|
|
31
|
+
refetchInterval: (current) => current.state.data?.state === "trashed"
|
|
32
|
+
? TRASHED_POLL_MS
|
|
33
|
+
: current.state.data?.request
|
|
34
|
+
? PENDING_REQUEST_POLL_MS
|
|
35
|
+
: false,
|
|
28
36
|
});
|
|
29
37
|
const request = useActionMutation("request-resource-access");
|
|
30
38
|
const state = query.data?.state;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ThemeFamily } from "../appearance/theme-family.js";
|
|
2
|
+
export { DEFAULT_THEME_FAMILY, isThemeFamily, THEME_FAMILY_APP_STATE_KEY, THEME_FAMILY_ATTRIBUTE, THEME_FAMILY_STORAGE_KEY, themeFamilyAppStateKey, themeFamilyStorageKey, type ThemeFamily, } from "../appearance/theme-family.js";
|
|
3
|
+
export declare const THEME_FAMILY_CHANGE_EVENT = "agent-native:theme-family-change";
|
|
4
|
+
export type ThemeFamilyStorageStatus = "valid" | "missing" | "invalid" | "unreadable";
|
|
5
|
+
export type StoredThemeFamily = {
|
|
6
|
+
status: "valid";
|
|
7
|
+
family: ThemeFamily;
|
|
8
|
+
} | {
|
|
9
|
+
status: "missing";
|
|
10
|
+
} | {
|
|
11
|
+
status: "invalid";
|
|
12
|
+
value: string;
|
|
13
|
+
} | {
|
|
14
|
+
status: "unreadable";
|
|
15
|
+
error: Error;
|
|
16
|
+
};
|
|
17
|
+
export interface ThemeFamilyApplyResult {
|
|
18
|
+
family: ThemeFamily;
|
|
19
|
+
storageStatus: "saved" | "unavailable";
|
|
20
|
+
}
|
|
21
|
+
export interface ThemeFamilyPreference {
|
|
22
|
+
family: ThemeFamily;
|
|
23
|
+
source: "default" | "user-setting";
|
|
24
|
+
appId: string | null;
|
|
25
|
+
}
|
|
26
|
+
export declare function readStoredThemeFamily(appId?: string | null): StoredThemeFamily;
|
|
27
|
+
export declare function applyThemeFamily(family: ThemeFamily, appId?: string | null): ThemeFamilyApplyResult;
|
|
28
|
+
export interface ThemeFamilyHook {
|
|
29
|
+
family: ThemeFamily;
|
|
30
|
+
status: "loading" | "success" | "error";
|
|
31
|
+
error?: Error;
|
|
32
|
+
storageStatus: ThemeFamilyStorageStatus;
|
|
33
|
+
setFamily: (family: ThemeFamily) => Promise<ThemeFamily>;
|
|
34
|
+
}
|
|
35
|
+
export declare function useThemeFamily(): ThemeFamilyHook;
|