@axiom-lattice/protocols 4.1.4 → 4.2.1
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +22 -0
- package/dist/index.d.mts +273 -25
- package/dist/index.d.ts +273 -25
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/A2AApiKeyStoreProtocol.ts +18 -0
- package/src/A2AProtocol.ts +3 -11
- package/src/McpLatticeProtocol.ts +7 -1
- package/src/McpServerConfigStoreProtocol.ts +2 -1
- package/src/OpenProtocol.ts +125 -0
- package/src/PluginProtocol.ts +19 -0
- package/src/ProjectRoomMessageStoreProtocol.ts +12 -1
- package/src/ProjectRoomReadStateProtocol.ts +20 -0
- package/src/ProjectRoomRealtimeProtocol.ts +28 -3
- package/src/SandboxPluginProtocol.ts +62 -0
- package/src/SandboxResourceProtocol.ts +5 -0
- package/src/__tests__/ProjectRoomReadStateProtocol.test.ts +16 -0
- package/src/__tests__/ProjectRoomStores.test.ts +4 -0
- package/src/__tests__/a2a-types.test.ts +0 -6
- package/src/__tests__/open-grants.test.ts +29 -0
- package/src/index.ts +4 -0
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { A2AApiKeyEntry } from "./A2AProtocol";
|
|
9
|
+
import type { OpenGrant } from "./OpenProtocol";
|
|
9
10
|
|
|
10
11
|
export interface A2AApiKeyRecord {
|
|
11
12
|
id: string;
|
|
@@ -15,6 +16,11 @@ export interface A2AApiKeyRecord {
|
|
|
15
16
|
projectId: string;
|
|
16
17
|
/** Assistant whitelist; empty/undefined = all exposed agents in the tenant */
|
|
17
18
|
assistantIds?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Open-door authorization grants (A2A door keeps reading assistantIds).
|
|
21
|
+
* Absent/empty → derived via effectiveGrants() legacy semantics.
|
|
22
|
+
*/
|
|
23
|
+
grants?: OpenGrant[];
|
|
18
24
|
label?: string;
|
|
19
25
|
enabled: boolean;
|
|
20
26
|
createdAt: Date;
|
|
@@ -27,9 +33,18 @@ export interface CreateA2AApiKeyInput {
|
|
|
27
33
|
projectId: string;
|
|
28
34
|
/** Assistant whitelist; empty/undefined = all exposed agents in the tenant */
|
|
29
35
|
assistantIds?: string[];
|
|
36
|
+
/** Open-door grants (see OpenProtocol); absent → legacy derivation */
|
|
37
|
+
grants?: OpenGrant[];
|
|
30
38
|
label?: string;
|
|
31
39
|
}
|
|
32
40
|
|
|
41
|
+
export interface UpdateA2AApiKeyInput {
|
|
42
|
+
label?: string;
|
|
43
|
+
projectId?: string;
|
|
44
|
+
assistantIds?: string[];
|
|
45
|
+
grants?: OpenGrant[];
|
|
46
|
+
}
|
|
47
|
+
|
|
33
48
|
export interface A2AApiKeyStore {
|
|
34
49
|
/** Look up a key record by its bearer token value (for auth). */
|
|
35
50
|
findByKey(key: string): Promise<A2AApiKeyRecord | null>;
|
|
@@ -55,6 +70,9 @@ export interface A2AApiKeyStore {
|
|
|
55
70
|
/** Delete a key permanently. */
|
|
56
71
|
delete(id: string): Promise<void>;
|
|
57
72
|
|
|
73
|
+
/** Update mutable fields of an existing key (label, grants, projectId, assistantIds). */
|
|
74
|
+
update(id: string, input: UpdateA2AApiKeyInput): Promise<A2AApiKeyRecord>;
|
|
75
|
+
|
|
58
76
|
/** Bulk load all active keys into a lookup Map (used at startup). */
|
|
59
77
|
loadIntoMap(): Promise<Map<string, A2AApiKeyEntry>>;
|
|
60
78
|
}
|
package/src/A2AProtocol.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OpenGrant } from "./OpenProtocol";
|
|
1
2
|
/**
|
|
2
3
|
* A2AProtocol - re-exports standard A2A 0.3 types from @a2a-js/sdk
|
|
3
4
|
* plus Axiom-specific auth/exposure types.
|
|
@@ -15,17 +16,6 @@ export type {
|
|
|
15
16
|
* Per-agent A2A exposure configuration — controls whether an agent is
|
|
16
17
|
* reachable over A2A and which skills are advertised on its AgentCard.
|
|
17
18
|
*/
|
|
18
|
-
export interface A2AExposure {
|
|
19
|
-
/** Whether this agent is exposed over the A2A protocol */
|
|
20
|
-
enabled: boolean;
|
|
21
|
-
/** Skills advertised on the AgentCard; defaults to a single generic skill when omitted */
|
|
22
|
-
skills?: Array<{ id: string; name: string; description: string; tags?: string[]; examples?: string[] }>;
|
|
23
|
-
/** Supported input modes (MIME types); defaults to text modes when omitted */
|
|
24
|
-
inputModes?: string[];
|
|
25
|
-
/** Supported output modes (MIME types); defaults to text modes when omitted */
|
|
26
|
-
outputModes?: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
19
|
/**
|
|
30
20
|
* In-memory API key entry used for request authentication.
|
|
31
21
|
* Empty/undefined assistantIds means all exposed agents in the tenant.
|
|
@@ -35,6 +25,8 @@ export interface A2AApiKeyEntry {
|
|
|
35
25
|
tenantId: string;
|
|
36
26
|
projectId: string;
|
|
37
27
|
assistantIds?: string[];
|
|
28
|
+
/** Open-door grants; the agent whitelist derives from the agent-domain items. */
|
|
29
|
+
grants?: OpenGrant[];
|
|
38
30
|
}
|
|
39
31
|
|
|
40
32
|
/**
|
|
@@ -29,8 +29,14 @@ export interface McpServerConfig {
|
|
|
29
29
|
args?: string[];
|
|
30
30
|
/** URL for HTTP/SSE transport */
|
|
31
31
|
url?: string;
|
|
32
|
-
/** Environment variables */
|
|
32
|
+
/** Environment variables (stdio transport; credentials passed to the server process) */
|
|
33
33
|
env?: Record<string, string>;
|
|
34
|
+
/**
|
|
35
|
+
* Custom HTTP headers sent with every request (streamable_http / sse only).
|
|
36
|
+
* Commonly used for authentication, e.g. `{ Authorization: "Bearer <token>" }`.
|
|
37
|
+
* Ignored for stdio transport. Values are encrypted at rest by config stores.
|
|
38
|
+
*/
|
|
39
|
+
headers?: Record<string, string>;
|
|
34
40
|
/** Connection timeout in milliseconds */
|
|
35
41
|
timeout?: number;
|
|
36
42
|
/** Retry attempts on connection failure */
|
|
@@ -38,7 +38,8 @@ export interface McpServerConfigEntry {
|
|
|
38
38
|
selectedTools: string[];
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Whether
|
|
41
|
+
* Whether secret values (env vars and headers) are encrypted at rest.
|
|
42
|
+
* Field name kept for backward compatibility; it covers config.headers too.
|
|
42
43
|
*/
|
|
43
44
|
isEnvEncrypted: boolean;
|
|
44
45
|
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenProtocol — contracts for the Open API surface (unified external
|
|
3
|
+
* capability invocation). See docs/superpowers/specs/2026-09-07-open-platform-api-design.md.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Authorization grant: default-deny; a grant covers one capability domain. */
|
|
7
|
+
export interface OpenGrant {
|
|
8
|
+
domain: string;
|
|
9
|
+
/**
|
|
10
|
+
* Action names (kb: ["search"]) or instance ids (agent: [assistantId]).
|
|
11
|
+
* Absent/empty = every action in the domain.
|
|
12
|
+
*/
|
|
13
|
+
items?: string[];
|
|
14
|
+
selector?: { sandbox?: "self" };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Execution context synthesized from a verified credential — never from the caller. */
|
|
18
|
+
export interface OpenExecutionContext {
|
|
19
|
+
tenantId: string;
|
|
20
|
+
projectId: string;
|
|
21
|
+
/** Addressing only, not an authorization dimension (see design §6.2). */
|
|
22
|
+
workspaceId?: string;
|
|
23
|
+
/** Synthesized runConfig (mirrors the Agent path, incl. _resolvedConnections). */
|
|
24
|
+
runConfig: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OpenExecutionResult {
|
|
28
|
+
content: Array<{ type: "text"; text: string }>;
|
|
29
|
+
isError?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Effective grants for the Open door, derived from a key record.
|
|
34
|
+
*
|
|
35
|
+
* Backward compatibility with pre-grants A2A keys:
|
|
36
|
+
* - explicit grants win;
|
|
37
|
+
* - otherwise assistantIds become an agent-domain grant;
|
|
38
|
+
* - legacy "empty assistantIds = all exposed agents" maps to a bare agent grant.
|
|
39
|
+
*
|
|
40
|
+
* The A2A door keeps reading assistantIds directly and never consults this —
|
|
41
|
+
* existing A2A behavior is unchanged by construction.
|
|
42
|
+
*/
|
|
43
|
+
export function effectiveGrants(record: {
|
|
44
|
+
grants?: OpenGrant[];
|
|
45
|
+
assistantIds?: string[];
|
|
46
|
+
}): OpenGrant[] {
|
|
47
|
+
if (record.grants && record.grants.length > 0) return record.grants;
|
|
48
|
+
if (record.assistantIds && record.assistantIds.length > 0) {
|
|
49
|
+
return [{ domain: "agent", items: record.assistantIds }];
|
|
50
|
+
}
|
|
51
|
+
return [{ domain: "agent" }];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ─── Capability catalog (for the key-management picker) ───────────────────
|
|
55
|
+
|
|
56
|
+
/** Where an exposed capability comes from. */
|
|
57
|
+
export type OpenCapabilitySource = "builtin" | "plugin" | "agent";
|
|
58
|
+
|
|
59
|
+
/** A selectable grant item: an action name or an instance id. */
|
|
60
|
+
export interface OpenCatalogItem {
|
|
61
|
+
/** Grant item value: action name (kb → "search") or instance id (agent → assistantId). */
|
|
62
|
+
id: string;
|
|
63
|
+
/** Full MCP tool name when this item is exclusively exposed (informational). */
|
|
64
|
+
toolName: string;
|
|
65
|
+
label: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
annotations?: { readOnlyHint: boolean; destructiveHint: boolean };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface OpenCatalogDomain {
|
|
71
|
+
domain: string;
|
|
72
|
+
label: string;
|
|
73
|
+
source: OpenCapabilitySource;
|
|
74
|
+
/** Display-only scope hint (defaults to tenant-wide). */
|
|
75
|
+
scopeKind?: "tenant" | "workspace" | "project";
|
|
76
|
+
/** How grant items are chosen: per-action or per-instance. */
|
|
77
|
+
itemKind: "action" | "instance";
|
|
78
|
+
items: OpenCatalogItem[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Full grantable capability catalog for a tenant (drives the key picker). */
|
|
82
|
+
export interface OpenCatalog {
|
|
83
|
+
domains: OpenCatalogDomain[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ─── Audit ────────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
export type OpenCredentialKind = "api_key" | "sandbox_token" | "browser_token";
|
|
89
|
+
|
|
90
|
+
/** Append-only audit input; the writer is responsible for redacting args. */
|
|
91
|
+
export interface OpenAuditAppendInput {
|
|
92
|
+
tenantId: string;
|
|
93
|
+
credentialId: string;
|
|
94
|
+
credentialKind: OpenCredentialKind;
|
|
95
|
+
projectId: string;
|
|
96
|
+
domain: string;
|
|
97
|
+
action: string;
|
|
98
|
+
args: unknown;
|
|
99
|
+
status: "ok" | "error";
|
|
100
|
+
errorCode?: string;
|
|
101
|
+
durationMs: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface OpenAuditRecord extends OpenAuditAppendInput {
|
|
105
|
+
id: string;
|
|
106
|
+
createdAt: Date;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface OpenAuditQuery {
|
|
110
|
+
tenantId: string;
|
|
111
|
+
credentialId?: string;
|
|
112
|
+
domain?: string;
|
|
113
|
+
status?: "ok" | "error";
|
|
114
|
+
/** ISO timestamps; inclusive from / exclusive to. */
|
|
115
|
+
from?: string;
|
|
116
|
+
to?: string;
|
|
117
|
+
limit?: number;
|
|
118
|
+
offset?: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface OpenAuditStore {
|
|
122
|
+
append(record: OpenAuditAppendInput): Promise<void>;
|
|
123
|
+
/** Tenant-scoped, newest-first. */
|
|
124
|
+
query(params: OpenAuditQuery): Promise<OpenAuditRecord[]>;
|
|
125
|
+
}
|
package/src/PluginProtocol.ts
CHANGED
|
@@ -73,6 +73,17 @@ export interface PluginConnection {
|
|
|
73
73
|
export interface PluginToolMeta {
|
|
74
74
|
name: string;
|
|
75
75
|
description: string;
|
|
76
|
+
/** When true, this tool surfaces on the Open API (MCP) surface. */
|
|
77
|
+
expose?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Open-surface exposure entry with MCP annotation hints. */
|
|
81
|
+
export interface PluginOpenExposeTool {
|
|
82
|
+
name: string;
|
|
83
|
+
/** MCP readOnlyHint — pure query, no side effects. */
|
|
84
|
+
readOnly?: boolean;
|
|
85
|
+
/** MCP destructiveHint — may cause irreversible changes. */
|
|
86
|
+
destructive?: boolean;
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
/**
|
|
@@ -135,6 +146,14 @@ export interface PluginMeta {
|
|
|
135
146
|
icon?: string;
|
|
136
147
|
/** 工具清单(可选,middleware 能自动提取时不需要写) */
|
|
137
148
|
tools?: PluginToolMeta[];
|
|
149
|
+
/**
|
|
150
|
+
* Open 面(MCP)暴露的工具名清单——独立于 tools 声明,一行即生长。
|
|
151
|
+
* 声明后 OpenCredentialService 的 grants 匹配 domain=<meta.type>。
|
|
152
|
+
*
|
|
153
|
+
* 条目可为字符串(默认 readOnly=false, destructive=false)或带注解对象,
|
|
154
|
+
* 注解映射到 MCP annotations(readOnlyHint / destructiveHint)。
|
|
155
|
+
*/
|
|
156
|
+
openExpose?: Array<string | PluginOpenExposeTool>;
|
|
138
157
|
/**
|
|
139
158
|
* 中间件配置 schema(用于 agent 配置面板)。新的 connection-backed
|
|
140
159
|
* plugins use `connections: string[]` and optional `connectAll?: boolean`.
|
|
@@ -13,7 +13,10 @@ export interface ProjectRoomMessageStore {
|
|
|
13
13
|
input: Omit<ProjectRoomMessage, "createdAt"> & { idempotencyKey: string },
|
|
14
14
|
): Promise<ProjectRoomMessage>;
|
|
15
15
|
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* Lists messages strictly before an optional cursor, up to the requested limit.
|
|
18
|
+
* Individually invalid persisted rows are skipped (and logged) instead of failing the whole read.
|
|
19
|
+
*/
|
|
17
20
|
list(input: {
|
|
18
21
|
tenantId: string;
|
|
19
22
|
roomId: string;
|
|
@@ -23,4 +26,12 @@ export interface ProjectRoomMessageStore {
|
|
|
23
26
|
|
|
24
27
|
/** Finds a room message by identifier within a tenant. */
|
|
25
28
|
findById(tenantId: string, id: string): Promise<ProjectRoomMessage | null>;
|
|
29
|
+
|
|
30
|
+
/** Counts messages created strictly after a horizon, optionally excluding one human author. */
|
|
31
|
+
countAfter(input: {
|
|
32
|
+
tenantId: string;
|
|
33
|
+
roomId: string;
|
|
34
|
+
after: Date;
|
|
35
|
+
excludeAuthorUserId?: string;
|
|
36
|
+
}): Promise<number>;
|
|
26
37
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A user's per-room read marker used to compute unread counts. */
|
|
2
|
+
export interface ProjectRoomReadState {
|
|
3
|
+
tenantId: string;
|
|
4
|
+
roomId: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
/** Read horizon: messages created strictly after this instant are unread. */
|
|
7
|
+
lastReadAt: Date;
|
|
8
|
+
updatedAt: Date;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Persistence operations for per-user, per-room read markers. */
|
|
12
|
+
export interface ProjectRoomReadStateStore {
|
|
13
|
+
/** Returns the user's read marker for a room, or null when never reported. */
|
|
14
|
+
get(tenantId: string, roomId: string, userId: string): Promise<ProjectRoomReadState | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Upserts the read marker monotonically: an earlier lastReadAt never moves
|
|
17
|
+
* the marker backwards. Returns the stored state after the write.
|
|
18
|
+
*/
|
|
19
|
+
markRead(input: { tenantId: string; roomId: string; userId: string; lastReadAt: Date }): Promise<ProjectRoomReadState>;
|
|
20
|
+
}
|
|
@@ -83,25 +83,41 @@ export type ProjectRoomTaskChangedEvent = ProjectRoomEventOf<
|
|
|
83
83
|
}
|
|
84
84
|
>;
|
|
85
85
|
|
|
86
|
+
/** A read-marker update broadcast on the acting user's channel. */
|
|
87
|
+
export type ProjectRoomReadChangedEvent = ProjectRoomEventOf<
|
|
88
|
+
"read.changed",
|
|
89
|
+
{ projectId: string; roomId: string; lastReadAt: string }
|
|
90
|
+
>;
|
|
91
|
+
|
|
92
|
+
/** A membership mutation that may change a user's room subscription set. */
|
|
93
|
+
export type ProjectRoomMembershipAffectedEvent = ProjectRoomEventOf<
|
|
94
|
+
"membership.affected",
|
|
95
|
+
{ change: "added" | "removed" | "role_changed"; projectId: string; roomId: string }
|
|
96
|
+
>;
|
|
97
|
+
|
|
86
98
|
/** All identified business events retained by the realtime broker. */
|
|
87
99
|
export type ProjectRoomBusinessEvent =
|
|
88
100
|
| ProjectRoomMessageCreatedEvent
|
|
89
101
|
| ProjectRoomRosterChangedEvent
|
|
90
102
|
| ProjectRoomMembershipChangedEvent
|
|
91
|
-
| ProjectRoomTaskChangedEvent
|
|
103
|
+
| ProjectRoomTaskChangedEvent
|
|
104
|
+
| ProjectRoomReadChangedEvent
|
|
105
|
+
| ProjectRoomMembershipAffectedEvent;
|
|
92
106
|
|
|
93
107
|
/** A business event before the broker assigns its process-local ID. */
|
|
94
108
|
export type ProjectRoomBusinessEventDraft =
|
|
95
109
|
| Omit<ProjectRoomMessageCreatedEvent, "id">
|
|
96
110
|
| Omit<ProjectRoomRosterChangedEvent, "id">
|
|
97
111
|
| Omit<ProjectRoomMembershipChangedEvent, "id">
|
|
98
|
-
| Omit<ProjectRoomTaskChangedEvent, "id"
|
|
112
|
+
| Omit<ProjectRoomTaskChangedEvent, "id">
|
|
113
|
+
| Omit<ProjectRoomReadChangedEvent, "id">
|
|
114
|
+
| Omit<ProjectRoomMembershipAffectedEvent, "id">;
|
|
99
115
|
|
|
100
116
|
/** A connection control event; control events are never replayed. */
|
|
101
117
|
export type ProjectRoomControlEvent =
|
|
102
118
|
| { type: "ready"; data: { epoch: string; headEventId: string | null } }
|
|
103
119
|
| { type: "resync"; data: { reason: "SERVER_RESTART" | "CURSOR_EXPIRED" | "SLOW_CONSUMER" } }
|
|
104
|
-
| { type: "access.revoked"; data: { reason: "PROJECT_ACCESS_REVOKED" | "TOKEN_EXPIRED" } };
|
|
120
|
+
| { type: "access.revoked"; data: { reason: "PROJECT_ACCESS_REVOKED" | "TOKEN_EXPIRED" | "CONNECTION_SUPERSEDED" } };
|
|
105
121
|
|
|
106
122
|
/** The authenticated identity used by Project Room realtime access checks. */
|
|
107
123
|
export interface ProjectRoomRealtimeActor {
|
|
@@ -194,6 +210,15 @@ export function isProjectRoomEventId(value: unknown): value is string {
|
|
|
194
210
|
return parseProjectRoomEventId(value) !== undefined;
|
|
195
211
|
}
|
|
196
212
|
|
|
213
|
+
/** projectId sentinel marking a per-user event channel inside the room broker. */
|
|
214
|
+
export const PROJECT_ROOM_USER_CHANNEL_PROJECT = "__user_channel__";
|
|
215
|
+
|
|
216
|
+
/** Builds the per-user broker scope used for membership and read broadcasts. */
|
|
217
|
+
export function projectRoomUserEventScope(tenantId: string, userId: string): ProjectRoomEventScope {
|
|
218
|
+
if (!tenantId || !userId) throw new TypeError("Project Room user scope requires non-empty identifiers");
|
|
219
|
+
return { tenantId, roomId: `__user__:${userId}`, projectId: PROJECT_ROOM_USER_CHANNEL_PROJECT };
|
|
220
|
+
}
|
|
221
|
+
|
|
197
222
|
function isoDate(value: unknown): string | undefined {
|
|
198
223
|
if (typeof value !== "object" || value === null) return undefined;
|
|
199
224
|
try {
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { AgentConfig } from "./AgentLatticeProtocol";
|
|
2
|
+
import type { PluginConnectionFieldSchema, PluginSkillDefinition } from "./PluginProtocol";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A single tool contributed by a sandbox-authored tenant plugin.
|
|
6
|
+
*
|
|
7
|
+
* @property name - Tool name exposed to the LLM (validated `^[a-zA-Z0-9_-]{1,64}$`).
|
|
8
|
+
* @property description - Tool description.
|
|
9
|
+
* @property schema - Restricted JSON Schema subset describing the tool input.
|
|
10
|
+
* @property handler - Safe relative path (within the plugin directory) to a JS file.
|
|
11
|
+
*/
|
|
12
|
+
export interface SandboxPluginToolDef {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
schema: Record<string, unknown>;
|
|
16
|
+
handler: string;
|
|
17
|
+
expose?: boolean;
|
|
18
|
+
readOnly?: boolean;
|
|
19
|
+
destructive?: boolean;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
maxResultBytes?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Connection capability contributed by a sandbox plugin. `test`/`discover`
|
|
26
|
+
* point at handler files relative to the plugin directory.
|
|
27
|
+
*/
|
|
28
|
+
export interface SandboxPluginConnectionDef {
|
|
29
|
+
fields: PluginConnectionFieldSchema[];
|
|
30
|
+
test?: { handler: string };
|
|
31
|
+
discover?: { handler: string };
|
|
32
|
+
resourceLabel?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Authoritative declarative definition of a tenant plugin, stored at
|
|
37
|
+
* `/root/.agents/plugins/<type>/plugin.json`.
|
|
38
|
+
*/
|
|
39
|
+
export interface SandboxPluginManifest {
|
|
40
|
+
schemaVersion: 1;
|
|
41
|
+
type: string;
|
|
42
|
+
name: string;
|
|
43
|
+
description: string;
|
|
44
|
+
version: string;
|
|
45
|
+
icon?: string;
|
|
46
|
+
category?: string;
|
|
47
|
+
configSchema?: Record<string, unknown>;
|
|
48
|
+
defaultConfig?: Record<string, unknown>;
|
|
49
|
+
connection?: SandboxPluginConnectionDef;
|
|
50
|
+
tools: SandboxPluginToolDef[];
|
|
51
|
+
skills?: Record<string, PluginSkillDefinition>;
|
|
52
|
+
agents?: Record<string, AgentConfig>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Structured load/validation feedback surfaced to authors and operators. */
|
|
56
|
+
export interface SandboxPluginDiagnostic {
|
|
57
|
+
level: "error" | "warning";
|
|
58
|
+
code: string;
|
|
59
|
+
pluginType?: string;
|
|
60
|
+
tool?: string;
|
|
61
|
+
message: string;
|
|
62
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OpenGrant } from "./OpenProtocol";
|
|
1
2
|
/**
|
|
2
3
|
* SandboxResourceProtocol
|
|
3
4
|
*
|
|
@@ -35,6 +36,8 @@ export interface ShareRecord {
|
|
|
35
36
|
passwordHash: string | null;
|
|
36
37
|
expiresAt: Date | null;
|
|
37
38
|
maxAccess: number | null;
|
|
39
|
+
/** Extra Open-door grants appended to sandbox tokens (V1: kb read-only). */
|
|
40
|
+
openGrants?: OpenGrant[];
|
|
38
41
|
accessCount: number;
|
|
39
42
|
revoked: boolean;
|
|
40
43
|
createdAt: Date;
|
|
@@ -50,6 +53,8 @@ export interface CreateShareRequest {
|
|
|
50
53
|
title?: string;
|
|
51
54
|
expiresAt?: string;
|
|
52
55
|
maxAccess?: number;
|
|
56
|
+
/** Extra Open-door grants for sandbox tokens minted for this share. */
|
|
57
|
+
openGrants?: OpenGrant[];
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
/** Response returned to clients after a share is created. */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { projectRoomUserEventScope, parseProjectRoomEventId } from "../ProjectRoomRealtimeProtocol";
|
|
2
|
+
|
|
3
|
+
describe("projectRoomUserEventScope", () => {
|
|
4
|
+
it("builds a stable per-user scope that cannot collide with room scopes", () => {
|
|
5
|
+
expect(projectRoomUserEventScope("t1", "u1")).toEqual({
|
|
6
|
+
tenantId: "t1", roomId: "__user__:u1", projectId: "__user_channel__",
|
|
7
|
+
});
|
|
8
|
+
expect(projectRoomUserEventScope("t1", "u1")).toEqual(projectRoomUserEventScope("t1", "u1"));
|
|
9
|
+
expect(projectRoomUserEventScope("t1", "u2")).not.toEqual(projectRoomUserEventScope("t1", "u1"));
|
|
10
|
+
});
|
|
11
|
+
it("user scope ids are valid broker event id candidates", () => {
|
|
12
|
+
const scope = projectRoomUserEventScope("t1", "u1");
|
|
13
|
+
expect(typeof scope.roomId).toBe("string");
|
|
14
|
+
expect(parseProjectRoomEventId(`${"0a1b2c3d-4e5f-4a6b-8c9d-0e1f2a3b4c5d"}:1`)?.sequence).toBe(1);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -264,6 +264,10 @@ class FakeProjectRoomMessageStore implements ProjectRoomMessageStore {
|
|
|
264
264
|
const [tenantId, id] = args;
|
|
265
265
|
return tenantId === "tenant-1" && id === "message-1" ? message : null;
|
|
266
266
|
}
|
|
267
|
+
|
|
268
|
+
async countAfter(input: Parameters<ProjectRoomMessageStore["countAfter"]>[0]): Promise<number> {
|
|
269
|
+
return input.tenantId === "tenant-1" && input.roomId === "room-1" ? 1 : 0;
|
|
270
|
+
}
|
|
267
271
|
}
|
|
268
272
|
|
|
269
273
|
describe("ProjectRoomStore", () => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { TaskFileRef, CreateTaskRequest } from "../TaskStoreProtocol";
|
|
2
2
|
import type { A2AApiKeyRecord, CreateA2AApiKeyInput } from "../A2AApiKeyStoreProtocol";
|
|
3
|
-
import type { A2AExposure } from "../A2AProtocol";
|
|
4
3
|
|
|
5
4
|
describe("A2A protocol types", () => {
|
|
6
5
|
it("TaskFileRef supports mimeType", () => {
|
|
@@ -22,9 +21,4 @@ describe("A2A protocol types", () => {
|
|
|
22
21
|
};
|
|
23
22
|
expect("workspaceId" in rec).toBe(false);
|
|
24
23
|
});
|
|
25
|
-
|
|
26
|
-
it("A2AExposure shape", () => {
|
|
27
|
-
const exp: A2AExposure = { enabled: true, skills: [{ id: "s", name: "n", description: "d" }] };
|
|
28
|
-
expect(exp.enabled).toBe(true);
|
|
29
|
-
});
|
|
30
24
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { effectiveGrants } from "../OpenProtocol";
|
|
2
|
+
|
|
3
|
+
describe("effectiveGrants", () => {
|
|
4
|
+
it("explicit grants win", () => {
|
|
5
|
+
const grants = [{ domain: "kb", items: ["search"] }];
|
|
6
|
+
expect(
|
|
7
|
+
effectiveGrants({ grants, assistantIds: ["a1"] }),
|
|
8
|
+
).toEqual(grants);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("assistantIds derive an agent-domain grant", () => {
|
|
12
|
+
expect(effectiveGrants({ assistantIds: ["a1", "a2"] })).toEqual([
|
|
13
|
+
{ domain: "agent", items: ["a1", "a2"] },
|
|
14
|
+
]);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("legacy empty assistantIds = bare agent grant (all exposed agents)", () => {
|
|
18
|
+
expect(effectiveGrants({ assistantIds: [] })).toEqual([
|
|
19
|
+
{ domain: "agent" },
|
|
20
|
+
]);
|
|
21
|
+
expect(effectiveGrants({})).toEqual([{ domain: "agent" }]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("empty explicit grants array falls through to legacy derivation", () => {
|
|
25
|
+
expect(effectiveGrants({ grants: [], assistantIds: ["a1"] })).toEqual([
|
|
26
|
+
{ domain: "agent", items: ["a1"] },
|
|
27
|
+
]);
|
|
28
|
+
});
|
|
29
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -56,6 +56,7 @@ export * from "./ProjectRoomStoreProtocol";
|
|
|
56
56
|
export * from "./ProjectMembershipStoreProtocol";
|
|
57
57
|
export * from "./ProjectBotMembershipStoreProtocol";
|
|
58
58
|
export * from "./ProjectRoomMessageStoreProtocol";
|
|
59
|
+
export * from "./ProjectRoomReadStateProtocol";
|
|
59
60
|
export * from "./ExactDataSnapshot";
|
|
60
61
|
export * from "./ProjectRoomRealtimeProtocol";
|
|
61
62
|
|
|
@@ -84,6 +85,9 @@ export type {
|
|
|
84
85
|
PluginMiddlewareFactory,
|
|
85
86
|
} from "./PluginProtocol";
|
|
86
87
|
|
|
88
|
+
export * from "./SandboxPluginProtocol";
|
|
89
|
+
|
|
87
90
|
// 导出通用类型
|
|
88
91
|
export * from "./types";
|
|
89
92
|
export * from "./TrustedRunContextProtocol";
|
|
93
|
+
export * from "./OpenProtocol";
|