@agent-native/core 0.211.0 → 0.212.0-nightly-20261021010049
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/README.md +1 -1
- package/dist/app-config/analytics.d.ts +1 -0
- package/dist/app-config/analytics.js +7 -0
- package/dist/app-config/schema.d.ts +1 -0
- package/dist/chat-threads/store.d.ts +28 -2
- package/dist/chat-threads/store.js +75 -13
- package/dist/cli/template-baseline.js +18 -5
- package/dist/cli/template-sync.js +100 -19
- package/dist/client/agent-chat/index.d.ts +2 -1
- package/dist/client/agent-chat/index.js +2 -1
- package/dist/client/chat/agentkit-protocol.js +21 -1
- package/dist/client/chat/run-outcome.d.ts +1 -0
- package/dist/client/chat/runtime.js +6 -0
- package/dist/client/sharing/index.d.ts +2 -1
- package/dist/client/sharing/index.js +2 -1
- package/dist/client/sharing/share-controller-helpers.d.ts +14 -1
- package/dist/client/sharing/share-controller-helpers.js +46 -1
- package/dist/client/sharing/useShareButtonController.d.ts +12 -0
- package/dist/client/sharing/useShareButtonController.js +76 -57
- package/dist/client/sharing/useShareDialogController.d.ts +3 -0
- package/dist/client/sharing/useShareDialogController.js +77 -49
- package/dist/client/sharing/useShareVisibilityTransition.d.ts +68 -0
- package/dist/client/sharing/useShareVisibilityTransition.js +224 -0
- package/dist/client/use-chat-thread-list.d.ts +40 -0
- package/dist/client/use-chat-thread-list.js +210 -0
- package/dist/client/use-chat-threads.d.ts +67 -0
- package/dist/client/use-chat-threads.js +141 -23
- package/dist/client/use-db-sync.d.ts +1 -0
- package/dist/client/use-db-sync.js +13 -1
- package/dist/collab/__fixtures__/live-copy-tasks.d.ts +1 -0
- package/dist/collab/__fixtures__/live-copy-tasks.js +92 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/bounded-task-bundle.d.ts +17 -0
- package/dist/collab/bounded-task-bundle.js +39 -0
- package/dist/collab/bounded-task-source.d.ts +14 -0
- package/dist/collab/bounded-task-source.js +8 -0
- package/dist/collab/bounded-tasks.d.ts +67 -0
- package/dist/collab/bounded-tasks.js +439 -0
- package/dist/collab/client.js +2 -1
- package/dist/collab/index.d.ts +5 -2
- package/dist/collab/index.js +5 -2
- package/dist/collab/live-copy-tasks.d.ts +80 -0
- package/dist/collab/live-copy-tasks.js +330 -0
- package/dist/collab/live-copy.d.ts +188 -0
- package/dist/collab/live-copy.js +415 -0
- package/dist/collab/routes.d.ts +5 -0
- package/dist/collab/routes.js +24 -8
- package/dist/collab/storage.d.ts +53 -1
- package/dist/collab/storage.js +93 -5
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/collab/ydoc-manager.d.ts +92 -3
- package/dist/collab/ydoc-manager.js +447 -38
- package/dist/db/client.d.ts +6 -0
- package/dist/db/client.js +25 -5
- package/dist/deploy/build.js +2 -0
- package/dist/extensions/actions.js +1 -0
- package/dist/jobs/actions/manage-recurring-job.d.ts +1 -1
- package/dist/mcp/agent-connected.js +5 -1
- package/dist/mcp/build-server.js +39 -10
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- 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 +16 -16
- package/dist/provider-api/actions/provider-api.d.ts +10 -10
- package/dist/server/agent-chat-plugin.js +80 -17
- 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/analytics.d.ts +1 -0
- package/dist/server/analytics.js +18 -1
- package/dist/server/collab-plugin.d.ts +10 -1
- package/dist/server/collab-plugin.js +89 -20
- package/dist/server/index.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/shared/optional-node-builtins.d.ts +1 -0
- package/dist/shared/optional-node-builtins.js +3 -0
- package/dist/sharing/access.d.ts +1 -0
- package/dist/sharing/access.js +113 -95
- package/dist/tracking/providers.d.ts +1 -0
- package/dist/tracking/providers.js +2 -1
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- package/dist/vite/bounded-task-plugin.d.ts +22 -0
- package/dist/vite/bounded-task-plugin.js +44 -0
- package/dist/vite/client.js +2 -0
- package/dist/vitest-config.js +3 -0
- package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +1 -1
- package/docs/content/deployment-environment-variables.mdx +1 -0
- package/docs/content/environment-variables.mdx +1 -0
- package/docs/content/sharing.mdx +36 -0
- package/docs/content/toolkit-sharing.mdx +98 -12
- package/package.json +5 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LiveCopyError, type LiveCopyBinding } from "../collab/live-copy.js";
|
|
1
2
|
import { type DbExec } from "../db/client.js";
|
|
2
3
|
type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
|
|
3
4
|
export type CollabResourceIdResolver = (docId: string) => string | null | Promise<string | null>;
|
|
@@ -22,7 +23,14 @@ export interface CollabPluginOptions {
|
|
|
22
23
|
idColumn?: string;
|
|
23
24
|
autoSeed?: boolean;
|
|
24
25
|
resolveSourceIdFromCollabDocumentId?: (docId: string) => string;
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Documents this binding covers are live copies: each update commits with
|
|
28
|
+
* its derived text, and the text, JSON and search-replace routes refuse
|
|
29
|
+
* them. Requires `autoSeed: false`; seed with `seedLiveCopy`. Passing the
|
|
30
|
+
* binding here also imports it at server start, so the refusal holds from
|
|
31
|
+
* the first request rather than from whenever an action first loads it.
|
|
32
|
+
*/
|
|
33
|
+
liveCopy?: LiveCopyBinding;
|
|
26
34
|
contentType?: "text" | "json";
|
|
27
35
|
jsonColumn?: string;
|
|
28
36
|
access?: CollabAccess;
|
|
@@ -48,5 +56,6 @@ export declare function createCollabSourceSeeder(options: {
|
|
|
48
56
|
seed: (docId: string, source: string, client?: DbExec) => Promise<void>;
|
|
49
57
|
withLock?: (docId: string, run: (client?: DbExec) => Promise<void>) => Promise<void>;
|
|
50
58
|
}): (docId: string) => Promise<void>;
|
|
59
|
+
export declare function liveCopyErrorStatus(error: LiveCopyError): number;
|
|
51
60
|
export declare function createCollabPlugin(options?: CollabPluginOptions): NitroPluginDef;
|
|
52
61
|
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineEventHandler, getMethod, setResponseStatus, } from "h3";
|
|
2
2
|
import { postAwareness, getActiveUsers } from "../collab/awareness.js";
|
|
3
3
|
import { getCollabEmitter } from "../collab/emitter.js";
|
|
4
|
+
import { LiveCopyError } from "../collab/live-copy.js";
|
|
4
5
|
import { getCollabState, postCollabUpdate, postCollabText, postCollabSearchReplace, } from "../collab/routes.js";
|
|
5
6
|
import { hasCollabState } from "../collab/storage.js";
|
|
6
7
|
import { postCollabJson, getCollabJson, postCollabPatch, } from "../collab/struct-routes.js";
|
|
@@ -98,7 +99,67 @@ export function createCollabSourceSeeder(options) {
|
|
|
98
99
|
}
|
|
99
100
|
};
|
|
100
101
|
}
|
|
102
|
+
// Worker and capacity failures are the server's, so a client may retry them;
|
|
103
|
+
// a task that broke its contract is a server bug, not a bad edit.
|
|
104
|
+
const RETRYABLE_TASK_FAILURES = new Set([
|
|
105
|
+
"unavailable",
|
|
106
|
+
"closed",
|
|
107
|
+
"queue_full",
|
|
108
|
+
"queue_timeout",
|
|
109
|
+
"deadline_exceeded",
|
|
110
|
+
"memory_exceeded",
|
|
111
|
+
"worker_crashed",
|
|
112
|
+
"startup_failed",
|
|
113
|
+
]);
|
|
114
|
+
export function liveCopyErrorStatus(error) {
|
|
115
|
+
switch (error.code) {
|
|
116
|
+
case "write_refused":
|
|
117
|
+
case "read_refused":
|
|
118
|
+
case "not_live_copy":
|
|
119
|
+
case "not_seeded":
|
|
120
|
+
case "version_conflict":
|
|
121
|
+
return 409;
|
|
122
|
+
case "state_too_large":
|
|
123
|
+
return 413;
|
|
124
|
+
case "malformed_state":
|
|
125
|
+
case "unsupported_content":
|
|
126
|
+
case "derive_refused":
|
|
127
|
+
case "build_refused":
|
|
128
|
+
return 422;
|
|
129
|
+
case "derive_failed":
|
|
130
|
+
case "build_failed":
|
|
131
|
+
if (RETRYABLE_TASK_FAILURES.has(error.reason ?? ""))
|
|
132
|
+
return 503;
|
|
133
|
+
if (error.reason === "input_too_large" ||
|
|
134
|
+
error.reason === "output_too_large") {
|
|
135
|
+
return 413;
|
|
136
|
+
}
|
|
137
|
+
return 500;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function collabErrorResponse(event, error) {
|
|
141
|
+
if (error instanceof LiveCopyError) {
|
|
142
|
+
setResponseStatus(event, liveCopyErrorStatus(error));
|
|
143
|
+
return {
|
|
144
|
+
error: error.message,
|
|
145
|
+
code: `live_copy_${error.code}`,
|
|
146
|
+
...(error.reason !== undefined ? { reason: error.reason } : {}),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
101
151
|
export function createCollabPlugin(options = {}) {
|
|
152
|
+
if ("onContentSync" in options) {
|
|
153
|
+
throw new Error("createCollabPlugin no longer accepts onContentSync; it was never called. Use liveCopy (defineLiveCopy) to derive a document's text when its live copy changes.");
|
|
154
|
+
}
|
|
155
|
+
if (options.liveCopy) {
|
|
156
|
+
if (options.autoSeed !== false) {
|
|
157
|
+
throw new Error("createCollabPlugin liveCopy requires autoSeed: false; seed live copies with seedLiveCopy.");
|
|
158
|
+
}
|
|
159
|
+
if (options.contentType === "json") {
|
|
160
|
+
throw new Error('createCollabPlugin liveCopy derives text from an XML fragment and cannot be combined with contentType: "json".');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
102
163
|
const normalizedAccess = normalizeCollabAccess(options);
|
|
103
164
|
const { table = "documents", contentColumn = "content", idColumn = "id", autoSeed = true, maxPayloadBytes = DEFAULT_MAX_PAYLOAD_BYTES, } = options;
|
|
104
165
|
const resolveSourceIdFromCollabDocumentId = options.resolveSourceIdFromCollabDocumentId ?? ((docId) => docId);
|
|
@@ -355,26 +416,34 @@ export function createCollabPlugin(options = {}) {
|
|
|
355
416
|
(action === "search-replace" && method === "POST") ||
|
|
356
417
|
(action === "json" && (method === "GET" || method === "POST")) ||
|
|
357
418
|
(action === "patch" && method === "POST");
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
419
|
+
try {
|
|
420
|
+
if (needsSeed)
|
|
421
|
+
await ensureDocumentSeeded(docId);
|
|
422
|
+
if (action === "state" && method === "GET")
|
|
423
|
+
return await getCollabState(event);
|
|
424
|
+
if (action === "update" && method === "POST")
|
|
425
|
+
return await postCollabUpdate(event);
|
|
426
|
+
if (action === "text" && method === "POST")
|
|
427
|
+
return await postCollabText(event);
|
|
428
|
+
if (action === "search-replace" && method === "POST")
|
|
429
|
+
return await postCollabSearchReplace(event);
|
|
430
|
+
if (action === "json" && method === "POST")
|
|
431
|
+
return await postCollabJson(event);
|
|
432
|
+
if (action === "json" && method === "GET")
|
|
433
|
+
return await getCollabJson(event);
|
|
434
|
+
if (action === "patch" && method === "POST")
|
|
435
|
+
return await postCollabPatch(event);
|
|
436
|
+
if (action === "awareness" && method === "POST")
|
|
437
|
+
return await postAwareness(event);
|
|
438
|
+
if (action === "users" && method === "GET")
|
|
439
|
+
return await getActiveUsers(event);
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
const response = collabErrorResponse(event, error);
|
|
443
|
+
if (response)
|
|
444
|
+
return response;
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
378
447
|
setResponseStatus(event, 404);
|
|
379
448
|
return { error: "Not found" };
|
|
380
449
|
});
|
package/dist/server/index.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export { refreshMcpManagerForPrincipal } from "./agent-chat/mcp-glue.js";
|
|
|
58
58
|
export { AGENT_CHAT_STREAM_PATH, AGENT_CHAT_STREAM_TOKEN_SUFFIX, AGENT_CHAT_STREAM_TOKEN_TTL_SECONDS, createAgentChatStreamToken, isAgentChatStreamingRuntime, readAgentChatStreamBearerToken, verifyAgentChatStreamToken, type AgentChatStreamPrincipal, } from "./agent-chat-stream.js";
|
|
59
59
|
export type { AgentChatMcpIcon, AgentChatMcpOptions, } from "./agent-chat/mcp-options.js";
|
|
60
60
|
export { configureAgentNativeEmbeddedEnvironment, createAgentNativeEmbeddedAuthOptions, createAgentNativeEmbeddedPlugin, mountAgentNativeEmbedded, normalizeAgentNativeEmbeddedSession, type AgentNativeEmbeddedAuthOptions, type AgentNativeEmbeddedGetSession, type AgentNativeEmbeddedHostSession, type AgentNativeEmbeddedPluginOptions, } from "./embedded.js";
|
|
61
|
-
export { createThread, getThread, listThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, type ChatThread, type ChatThreadScope, type ChatThreadSummary, type ListThreadsOptions, } from "../chat-threads/store.js";
|
|
61
|
+
export { createThread, getThread, listThreads, updateThreadData, deleteThread, setThreadArchived, setThreadPinned, setThreadScope, type ChatThread, type ChatThreadAccessBasis, type ChatThreadOwnership, type ChatThreadScope, type ChatThreadSummary, type ChatThreadListRow, type ListThreadsOptions, } from "../chat-threads/store.js";
|
|
62
62
|
export { createResourcesPlugin, defaultResourcesPlugin, } from "./resources-plugin.js";
|
|
63
63
|
export { createCoreRoutesPlugin, defaultCoreRoutesPlugin, FRAMEWORK_ROUTE_PREFIX, type CoreRoutesPluginOptions, } from "./core-routes-plugin.js";
|
|
64
64
|
export type { CoreRoutesMcpOptions } from "./core-routes/mcp-connect-options.js";
|
|
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
54
54
|
expiresAt?: undefined;
|
|
55
55
|
ttlSeconds?: undefined;
|
|
56
56
|
} | {
|
|
57
|
+
error?: undefined;
|
|
57
58
|
token: string;
|
|
58
59
|
expiresAt: string;
|
|
59
60
|
ttlSeconds: number;
|
|
60
|
-
error?: undefined;
|
|
61
61
|
}>>;
|
|
@@ -2,4 +2,5 @@ import type { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import type { EventEmitter } from "node:events";
|
|
3
3
|
export type AsyncLocalStorageCtor = new <T>() => AsyncLocalStorage<T>;
|
|
4
4
|
export declare function getAsyncLocalStorageCtor(): AsyncLocalStorageCtor | undefined;
|
|
5
|
+
export declare function getWorkerThreads(): typeof import("node:worker_threads") | undefined;
|
|
5
6
|
export declare function createEventEmitter(): EventEmitter;
|
|
@@ -11,6 +11,9 @@ function nodeBuiltin(name) {
|
|
|
11
11
|
export function getAsyncLocalStorageCtor() {
|
|
12
12
|
return nodeBuiltin("node:async_hooks")?.AsyncLocalStorage;
|
|
13
13
|
}
|
|
14
|
+
export function getWorkerThreads() {
|
|
15
|
+
return nodeBuiltin("node:worker_threads");
|
|
16
|
+
}
|
|
14
17
|
export function createEventEmitter() {
|
|
15
18
|
const Ctor = nodeBuiltin("node:events")?.EventEmitter;
|
|
16
19
|
if (Ctor)
|
package/dist/sharing/access.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface AccessContext {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function currentAccess(): AccessContext;
|
|
17
17
|
export declare function resolveRegisteredAccessContext(reg: ShareableResourceRegistration | undefined, ctx: AccessContext): AccessContext;
|
|
18
|
+
export declare function hasCurrentOrgMembership(reg: Pick<ShareableResourceRegistration, "getDb">, memberOrgId: string, email: string, ctx: AccessContext): Promise<boolean>;
|
|
18
19
|
export declare function accessFilter(resourceTable: any, sharesTable: any, rawCtx?: AccessContext, minRole?: ShareRole, options?: {
|
|
19
20
|
includePublic?: boolean;
|
|
20
21
|
}): SQL;
|
package/dist/sharing/access.js
CHANGED
|
@@ -3,7 +3,6 @@ import { drizzle as drizzleProxy } from "drizzle-orm/pg-proxy";
|
|
|
3
3
|
import { getScopedDbExec, withDbExec } from "../db/client.js";
|
|
4
4
|
import { evaluateFeatureFlagStrict } from "../feature-flags/store.js";
|
|
5
5
|
import { CROSS_APP_ORG_FEDERATION_FLAG } from "../org/feature-flags.js";
|
|
6
|
-
import { isMissingOrganizationTableError } from "../org/membership.js";
|
|
7
6
|
import { orgMembers, organizations } from "../org/schema.js";
|
|
8
7
|
import { implicitServiceOrgRole } from "../org/service-identity.js";
|
|
9
8
|
import { getRequestAuthCapability, getRequestContext, getRequestUserEmail, getRequestOrgId, } from "../server/request-context.js";
|
|
@@ -111,19 +110,34 @@ function orgMembershipScope(ctx, orgId) {
|
|
|
111
110
|
}
|
|
112
111
|
return sql `1=1`;
|
|
113
112
|
}
|
|
114
|
-
async function
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
113
|
+
export async function hasCurrentOrgMembership(reg, memberOrgId, email, ctx) {
|
|
114
|
+
const normalizedEmail = normalizeEmailForAccess(email);
|
|
115
|
+
if (!memberOrgId || !normalizedEmail)
|
|
116
|
+
return false;
|
|
117
|
+
const transaction = ctx.transaction ?? getScopedDbExec();
|
|
118
|
+
const membershipContext = transaction ? { ...ctx, transaction } : ctx;
|
|
119
|
+
return inAccessTransaction(membershipContext, async () => {
|
|
120
|
+
const requestContext = getRequestContext();
|
|
121
|
+
const verifiedServiceIdentity = requestContext?.verifiedServiceIdentity;
|
|
122
|
+
const db = transaction
|
|
123
|
+
? drizzleProxy(async (query, params) => {
|
|
124
|
+
const result = await transaction.execute({
|
|
125
|
+
sql: query,
|
|
126
|
+
args: params,
|
|
127
|
+
});
|
|
128
|
+
return { rows: result.rows.map((row) => Object.values(row)) };
|
|
129
|
+
})
|
|
130
|
+
: reg.getDb();
|
|
131
|
+
const verifiedLocalService = normalizeEmailForAccess(requestContext?.userEmail) === normalizedEmail &&
|
|
132
|
+
normalizeEmailForAccess(verifiedServiceIdentity?.userEmail) ===
|
|
133
|
+
normalizedEmail &&
|
|
134
|
+
implicitServiceOrgRole({
|
|
135
|
+
email: verifiedServiceIdentity?.userEmail,
|
|
136
|
+
orgId: memberOrgId,
|
|
137
|
+
requestOrgId: requestContext?.orgId,
|
|
138
|
+
}) &&
|
|
139
|
+
verifiedServiceIdentity?.orgId === memberOrgId;
|
|
140
|
+
const loadOrganization = async () => {
|
|
127
141
|
const [organization] = await db
|
|
128
142
|
.select({
|
|
129
143
|
identityAuthority: organizations.identityAuthority,
|
|
@@ -132,66 +146,46 @@ async function isOrgMember(reg, memberOrgId, email, ctx) {
|
|
|
132
146
|
.from(organizations)
|
|
133
147
|
.where(eq(organizations.id, memberOrgId))
|
|
134
148
|
.limit(1);
|
|
135
|
-
if (organization
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
if (!organization) {
|
|
150
|
+
throw new Error("Organization metadata is unavailable for access validation");
|
|
151
|
+
}
|
|
152
|
+
return organization;
|
|
153
|
+
};
|
|
154
|
+
let organization;
|
|
155
|
+
if (verifiedLocalService) {
|
|
156
|
+
organization = await loadOrganization();
|
|
157
|
+
if (!organization.identityAuthority?.trim() &&
|
|
158
|
+
!organization.identityId?.trim()) {
|
|
138
159
|
return true;
|
|
139
160
|
}
|
|
140
161
|
}
|
|
141
|
-
|
|
142
|
-
if (!isMissingOrganizationTableError(error))
|
|
143
|
-
throw error;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
let rows;
|
|
147
|
-
try {
|
|
148
|
-
rows = await db
|
|
162
|
+
const rows = await db
|
|
149
163
|
.select({ id: orgMembers.id })
|
|
150
164
|
.from(orgMembers)
|
|
151
|
-
.where(and(eq(orgMembers.orgId, memberOrgId), emailColumnMatches(orgMembers.email,
|
|
165
|
+
.where(and(eq(orgMembers.orgId, memberOrgId), emailColumnMatches(orgMembers.email, normalizedEmail), isNull(orgMembers.federationRemovalPendingAt)))
|
|
152
166
|
.limit(1);
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
throw error;
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
const linked = String(organization?.identityAuthority ?? "").trim() ||
|
|
178
|
-
String(organization?.identityId ?? "").trim();
|
|
179
|
-
if (!linked)
|
|
180
|
-
return true;
|
|
181
|
-
if (!(await evaluateFeatureFlagStrict(CROSS_APP_ORG_FEDERATION_FLAG.key, {
|
|
182
|
-
userEmail: email,
|
|
183
|
-
userKey: email,
|
|
184
|
-
orgId: memberOrgId,
|
|
185
|
-
transaction: ctx.transaction,
|
|
186
|
-
}))) {
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
const { validateFederatedOrganizationMembershipForCurrentRequest } = await import("../org/federation.js");
|
|
190
|
-
const validation = await validateFederatedOrganizationMembershipForCurrentRequest({
|
|
191
|
-
orgId: memberOrgId,
|
|
192
|
-
email,
|
|
167
|
+
if (rows.length === 0)
|
|
168
|
+
return false;
|
|
169
|
+
organization ??= await loadOrganization();
|
|
170
|
+
if (!organization.identityAuthority?.trim() &&
|
|
171
|
+
!organization.identityId?.trim()) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
if (!(await evaluateFeatureFlagStrict(CROSS_APP_ORG_FEDERATION_FLAG.key, {
|
|
175
|
+
userEmail: normalizedEmail,
|
|
176
|
+
userKey: normalizedEmail,
|
|
177
|
+
orgId: memberOrgId,
|
|
178
|
+
transaction,
|
|
179
|
+
}))) {
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const { validateFederatedOrganizationMembershipForCurrentRequest } = await import("../org/federation.js");
|
|
183
|
+
const validation = await validateFederatedOrganizationMembershipForCurrentRequest({
|
|
184
|
+
orgId: memberOrgId,
|
|
185
|
+
email: normalizedEmail,
|
|
186
|
+
});
|
|
187
|
+
return validation.active;
|
|
193
188
|
});
|
|
194
|
-
return validation.active;
|
|
195
189
|
}
|
|
196
190
|
export function accessFilter(resourceTable, sharesTable, rawCtx = currentAccess(), minRole = "viewer", options = {}) {
|
|
197
191
|
const reg = findRegistrationByTable(resourceTable);
|
|
@@ -469,6 +463,17 @@ async function ownerOrAdminRole(reg, resource, ctx, normalizedUserEmail) {
|
|
|
469
463
|
async function accessToResource(reg, resourceId, resource, ctx) {
|
|
470
464
|
const { userEmail } = ctx;
|
|
471
465
|
const normalizedUserEmail = normalizeEmailForAccess(userEmail);
|
|
466
|
+
const memberships = new Map();
|
|
467
|
+
const isOrgMember = (orgId) => {
|
|
468
|
+
let membership = memberships.get(orgId);
|
|
469
|
+
if (!membership) {
|
|
470
|
+
membership = normalizedUserEmail
|
|
471
|
+
? hasCurrentOrgMembership(reg, orgId, normalizedUserEmail, ctx)
|
|
472
|
+
: Promise.resolve(false);
|
|
473
|
+
memberships.set(orgId, membership);
|
|
474
|
+
}
|
|
475
|
+
return membership;
|
|
476
|
+
};
|
|
472
477
|
// A host can deny outright. When it grants access, the resource's owner
|
|
473
478
|
// and admins keep their own roles; the host's role covers everyone else.
|
|
474
479
|
const hostResolver = hostAccessResolvers.get(reg.type);
|
|
@@ -482,21 +487,19 @@ async function accessToResource(reg, resourceId, resource, ctx) {
|
|
|
482
487
|
return { role: managingRole, resource };
|
|
483
488
|
if (hosted !== undefined)
|
|
484
489
|
return { role: hosted, resource };
|
|
490
|
+
const role = await highestShareRole(reg, resourceId, ctx, resource, isOrgMember);
|
|
485
491
|
if (resource.visibility === "public" && reg.allowPublic !== false) {
|
|
486
492
|
const publicRole = await publicAccessRoleForResource(reg, resource, ctx);
|
|
487
|
-
const role = await highestShareRole(reg, resourceId, ctx, resource);
|
|
488
493
|
return { role: higherShareRole(publicRole, role), resource };
|
|
489
494
|
}
|
|
495
|
+
if (role)
|
|
496
|
+
return { role, resource };
|
|
490
497
|
if (resource.visibility === "org" &&
|
|
491
498
|
resource.orgId &&
|
|
492
499
|
normalizedUserEmail &&
|
|
493
|
-
(await isOrgMember(
|
|
494
|
-
|
|
495
|
-
return { role: role ?? "viewer", resource };
|
|
500
|
+
(await isOrgMember(resource.orgId))) {
|
|
501
|
+
return { role: "viewer", resource };
|
|
496
502
|
}
|
|
497
|
-
const role = await highestShareRole(reg, resourceId, ctx, resource);
|
|
498
|
-
if (role)
|
|
499
|
-
return { role, resource };
|
|
500
503
|
return null;
|
|
501
504
|
}
|
|
502
505
|
/**
|
|
@@ -530,7 +533,7 @@ export async function resolveAccessStatus(resourceType, resourceId, ctx = curren
|
|
|
530
533
|
return { state: available ? "allowed" : "trashed", role: access.role };
|
|
531
534
|
});
|
|
532
535
|
}
|
|
533
|
-
async function highestShareRole(reg, resourceId, ctx, resource) {
|
|
536
|
+
async function highestShareRole(reg, resourceId, ctx, resource, isOrgMember) {
|
|
534
537
|
const { userEmail, orgId } = ctx;
|
|
535
538
|
const normalizedUserEmail = normalizeEmailForAccess(userEmail);
|
|
536
539
|
if (!normalizedUserEmail && !orgId)
|
|
@@ -545,34 +548,49 @@ async function highestShareRole(reg, resourceId, ctx, resource) {
|
|
|
545
548
|
if (orgId) {
|
|
546
549
|
principalClauses.push(and(eq(reg.sharesTable.principalType, "org"), eq(reg.sharesTable.principalId, orgId)));
|
|
547
550
|
}
|
|
551
|
+
if (principalClauses.length === 0)
|
|
552
|
+
return null;
|
|
553
|
+
const rows = await db
|
|
554
|
+
.select({
|
|
555
|
+
role: reg.sharesTable.role,
|
|
556
|
+
principalType: reg.sharesTable.principalType,
|
|
557
|
+
})
|
|
558
|
+
.from(reg.sharesTable)
|
|
559
|
+
.where(and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)))
|
|
560
|
+
.limit(10);
|
|
548
561
|
let best = null;
|
|
562
|
+
const grantRows = rows;
|
|
563
|
+
for (const row of grantRows) {
|
|
564
|
+
if (row.principalType === "user")
|
|
565
|
+
best = higherShareRole(row.role, best);
|
|
566
|
+
}
|
|
567
|
+
// Direct admin grants are already maximal and do not depend on organization health.
|
|
568
|
+
if (best === "admin")
|
|
569
|
+
return best;
|
|
570
|
+
if (orgId &&
|
|
571
|
+
grantRows.some((row) => row.principalType === "org") &&
|
|
572
|
+
(await isOrgMember(orgId))) {
|
|
573
|
+
for (const row of grantRows) {
|
|
574
|
+
if (row.principalType === "org")
|
|
575
|
+
best = higherShareRole(row.role, best);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
549
578
|
if (reg.supportsGroupShares && normalizedUserEmail && resource.orgId) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
579
|
+
const groupRows = await db
|
|
580
|
+
.select({
|
|
581
|
+
principalId: reg.sharesTable.principalId,
|
|
582
|
+
role: reg.sharesTable.role,
|
|
583
|
+
})
|
|
584
|
+
.from(reg.sharesTable)
|
|
585
|
+
.where(and(eq(reg.sharesTable.resourceId, resourceId), eq(reg.sharesTable.principalType, "group")));
|
|
586
|
+
if (groupRows.length > 0 && (await isOrgMember(resource.orgId))) {
|
|
558
587
|
for (const row of groupRows) {
|
|
559
588
|
if (await workspaceUserGroupsIncludeUser(resource.orgId, [row.principalId], normalizedUserEmail)) {
|
|
560
|
-
|
|
561
|
-
best = row.role;
|
|
562
|
-
}
|
|
589
|
+
best = higherShareRole(row.role, best);
|
|
563
590
|
}
|
|
564
591
|
}
|
|
565
592
|
}
|
|
566
593
|
}
|
|
567
|
-
const rows = await db
|
|
568
|
-
.select({ role: reg.sharesTable.role })
|
|
569
|
-
.from(reg.sharesTable)
|
|
570
|
-
.where(and(eq(reg.sharesTable.resourceId, resourceId), or(...principalClauses)))
|
|
571
|
-
.limit(10);
|
|
572
|
-
for (const r of rows) {
|
|
573
|
-
if (!best || ROLE_RANK[r.role] > ROLE_RANK[best])
|
|
574
|
-
best = r.role;
|
|
575
|
-
}
|
|
576
594
|
return best;
|
|
577
595
|
}
|
|
578
596
|
export async function assertAccess(resourceType, resourceId, minRole = "viewer", ctx = currentAccess(), options = {}) {
|
|
@@ -6,6 +6,7 @@ import { reshapeTrackedExceptionProperties } from "./posthog-exception.js";
|
|
|
6
6
|
import { registerTrackingProvider } from "./registry.js";
|
|
7
7
|
const POSTHOG_DEFAULT_HOST = "https://us.i.posthog.com";
|
|
8
8
|
const AGENT_NATIVE_ANALYTICS_DEFAULT_ENDPOINT = "https://analytics.agent-native.com/track";
|
|
9
|
+
export const AGENT_NATIVE_ANALYTICS_PROVIDER = "agent-native-analytics";
|
|
9
10
|
const BATCH_INTERVAL_MS = 10_000;
|
|
10
11
|
const MAX_BATCH_SIZE = 50;
|
|
11
12
|
const QUEUE_KEY = Symbol.for("@agent-native/core/tracking.queue");
|
|
@@ -323,7 +324,7 @@ function createWebhookProvider(url, authHeader) {
|
|
|
323
324
|
function createAgentNativeAnalyticsProvider(publicKey, endpoint) {
|
|
324
325
|
const flushImmediately = agentNativeAnalyticsFlushesImmediately();
|
|
325
326
|
return {
|
|
326
|
-
name:
|
|
327
|
+
name: AGENT_NATIVE_ANALYTICS_PROVIDER,
|
|
327
328
|
acceptsTestIdentityExceptions: true,
|
|
328
329
|
track(event) {
|
|
329
330
|
enqueue(endpoint, JSON.stringify({
|
|
@@ -13,7 +13,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
13
13
|
executionEngine?: string;
|
|
14
14
|
executionCwd?: string;
|
|
15
15
|
}, {
|
|
16
|
-
deleted?: undefined;
|
|
17
16
|
created: boolean;
|
|
18
17
|
name: string;
|
|
19
18
|
scope: "organization" | "personal";
|
|
@@ -24,6 +23,7 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
24
23
|
reasoningEffort: "auto" | "high" | "low" | "max" | "medium" | "minimal" | "none" | "xhigh";
|
|
25
24
|
webhookPath: string;
|
|
26
25
|
nextRun: string;
|
|
26
|
+
deleted?: undefined;
|
|
27
27
|
enabled?: undefined;
|
|
28
28
|
executionHostId?: undefined;
|
|
29
29
|
executionEngine?: undefined;
|
|
@@ -45,11 +45,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
45
45
|
nextRun?: undefined;
|
|
46
46
|
webhookPath?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
deleted?: undefined;
|
|
49
48
|
created?: undefined;
|
|
50
49
|
scope?: undefined;
|
|
51
50
|
triggerType?: undefined;
|
|
52
51
|
event?: undefined;
|
|
52
|
+
deleted?: undefined;
|
|
53
53
|
name: string;
|
|
54
54
|
enabled: boolean;
|
|
55
55
|
schedule: string;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface PluginContext {
|
|
2
|
+
resolve(source: string, importer?: string, options?: {
|
|
3
|
+
skipSelf?: boolean;
|
|
4
|
+
}): Promise<{
|
|
5
|
+
id: string;
|
|
6
|
+
external?: boolean | string;
|
|
7
|
+
} | null>;
|
|
8
|
+
addWatchFile(file: string): void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* `import source from "./tasks.ts?bounded-task"` yields the module bundled
|
|
12
|
+
* into a `BoundedTaskSource` at build time, so deployed servers never run
|
|
13
|
+
* esbuild. Registered for Vite (dev and client builds) and the production
|
|
14
|
+
* Nitro build.
|
|
15
|
+
*/
|
|
16
|
+
export declare function boundedTaskSourcePlugin(): {
|
|
17
|
+
name: string;
|
|
18
|
+
enforce: "pre";
|
|
19
|
+
resolveId(this: PluginContext, source: string, importer?: string): Promise<string>;
|
|
20
|
+
load(this: PluginContext, id: string): Promise<string>;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { bundleBoundedTaskSource } from "../collab/bounded-task-bundle.js";
|
|
3
|
+
const QUERY = "?bounded-task";
|
|
4
|
+
const RESOLVED_PREFIX = "\0agent-native-bounded-task:";
|
|
5
|
+
/**
|
|
6
|
+
* `import source from "./tasks.ts?bounded-task"` yields the module bundled
|
|
7
|
+
* into a `BoundedTaskSource` at build time, so deployed servers never run
|
|
8
|
+
* esbuild. Registered for Vite (dev and client builds) and the production
|
|
9
|
+
* Nitro build.
|
|
10
|
+
*/
|
|
11
|
+
export function boundedTaskSourcePlugin() {
|
|
12
|
+
return {
|
|
13
|
+
name: "agent-native-bounded-task-source",
|
|
14
|
+
enforce: "pre",
|
|
15
|
+
async resolveId(source, importer) {
|
|
16
|
+
if (!source.endsWith(QUERY))
|
|
17
|
+
return null;
|
|
18
|
+
const request = source.slice(0, -QUERY.length);
|
|
19
|
+
const resolved = await this.resolve(request, importer, {
|
|
20
|
+
skipSelf: true,
|
|
21
|
+
});
|
|
22
|
+
if (!resolved || resolved.external) {
|
|
23
|
+
throw new Error(`Cannot resolve bounded task module ${request}${importer ? ` from ${importer}` : ""}`);
|
|
24
|
+
}
|
|
25
|
+
return RESOLVED_PREFIX + resolved.id.split("?")[0];
|
|
26
|
+
},
|
|
27
|
+
async load(id) {
|
|
28
|
+
if (!id.startsWith(RESOLVED_PREFIX))
|
|
29
|
+
return null;
|
|
30
|
+
const entry = id.slice(RESOLVED_PREFIX.length);
|
|
31
|
+
const bundled = await bundleBoundedTaskSource(entry, {
|
|
32
|
+
name: path.basename(entry).replace(/\.[^.]+$/, ""),
|
|
33
|
+
});
|
|
34
|
+
for (const input of bundled.inputs)
|
|
35
|
+
this.addWatchFile(input);
|
|
36
|
+
const source = {
|
|
37
|
+
kind: bundled.kind,
|
|
38
|
+
name: bundled.name,
|
|
39
|
+
code: bundled.code,
|
|
40
|
+
};
|
|
41
|
+
return `export default Object.freeze(${JSON.stringify(source)});\n`;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/vite/client.js
CHANGED
|
@@ -29,6 +29,7 @@ import { AGENT_NATIVE_TYPEGEN_ENV, formatRuntimeConfigReport, getRuntimeConfigRe
|
|
|
29
29
|
import { actionTypesPlugin } from "./action-types-plugin.js";
|
|
30
30
|
import { createAgentNativeConfigContext, loadAgentNativeConfigFile, loadWorkspaceAgentNativeConfigFile, readAgentNativeJsonConfig, resolveFirstRunOnboardingBuildReplacement, resolveHarnessBuildReplacement, writeAgentNativeBuildConfigMarker, } from "./agent-native-config-loader.js";
|
|
31
31
|
import { agentsBundlePlugin } from "./agents-bundle-plugin.js";
|
|
32
|
+
import { boundedTaskSourcePlugin } from "./bounded-task-plugin.js";
|
|
32
33
|
import { migrationDiagnosticPlugin } from "./migration-diagnostic-plugin.js";
|
|
33
34
|
import { motionSourceProvenancePlugin } from "./motion-source-provenance-plugin.js";
|
|
34
35
|
import { resolveAgentNativePackageVersions } from "./package-versions.js";
|
|
@@ -3657,6 +3658,7 @@ function createAgentNativePlugins(options, { command, includeReactTransform, use
|
|
|
3657
3658
|
appChangelogRawPlugin(),
|
|
3658
3659
|
actionTypesPlugin(),
|
|
3659
3660
|
agentsBundlePlugin({ agentNativeConfig: options.agentNativeConfig }),
|
|
3661
|
+
boundedTaskSourcePlugin(),
|
|
3660
3662
|
authClientAssetPlugin(cwd),
|
|
3661
3663
|
autoReloadOnOptimizeDep(),
|
|
3662
3664
|
fullReloadOnOptimizeDep504(),
|
package/dist/vitest-config.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { boundedTaskSourcePlugin } from "./vite/bounded-task-plugin.js";
|
|
1
2
|
const DEFAULT_MAX_WORKERS = "25%";
|
|
2
3
|
const ENV_KEYS = ["VITEST_CONCURRENCY", "AGENT_NATIVE_VITEST_CONCURRENCY"];
|
|
3
4
|
export function resolveMaxWorkers(env = process.env, fallback = DEFAULT_MAX_WORKERS) {
|
|
@@ -25,6 +26,8 @@ export function resolveMaxWorkers(env = process.env, fallback = DEFAULT_MAX_WORK
|
|
|
25
26
|
return count;
|
|
26
27
|
}
|
|
27
28
|
const vitestBaseConfig = {
|
|
29
|
+
// App tests import server modules that load `?bounded-task` sources.
|
|
30
|
+
plugins: [boundedTaskSourcePlugin()],
|
|
28
31
|
test: {
|
|
29
32
|
maxWorkers: resolveMaxWorkers(),
|
|
30
33
|
// Vitest's 5 s default only holds while a worker has a core to itself.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
declare const _default: import("../../action.js").ActionDefinition<Record<string, never>, {
|
|
2
|
+
memberEmails: any[];
|
|
2
3
|
id: string;
|
|
3
4
|
orgId: string;
|
|
4
5
|
name: string;
|
|
5
6
|
createdByEmail: string;
|
|
6
7
|
createdAt: string;
|
|
7
8
|
updatedAt: string;
|
|
8
|
-
memberEmails: any[];
|
|
9
9
|
}[]>;
|
|
10
10
|
export default _default;
|