@axiom-lattice/protocols 4.2.0 → 4.2.2
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 +20 -0
- package/dist/index.d.mts +266 -20
- package/dist/index.d.ts +266 -20
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -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/AgentWebAppStreamProjection.ts +21 -0
- package/src/ChannelAdapterProtocol.ts +12 -0
- package/src/MessageProtocol.ts +6 -0
- package/src/OpenProtocol.ts +125 -0
- package/src/PluginProtocol.ts +19 -0
- package/src/ProjectRoomMessageStoreProtocol.ts +4 -1
- package/src/ProjectRoomProtocol.ts +11 -0
- package/src/ProjectRoomRealtimeProtocol.ts +6 -0
- package/src/SandboxPluginProtocol.ts +62 -0
- package/src/SandboxResourceProtocol.ts +5 -0
- package/src/UserPushSubscriptionStoreProtocol.ts +25 -0
- package/src/__tests__/AgentWebAppStreamProjection.test.ts +59 -0
- package/src/__tests__/ProjectRoomRealtimeProtocol.test.ts +2 -2
- 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
|
/**
|
|
@@ -62,6 +62,27 @@ export function projectAgentWebAppChunk(
|
|
|
62
62
|
{ type: "stream.completed" },
|
|
63
63
|
];
|
|
64
64
|
}
|
|
65
|
+
if (chunk.type === MessageChunkTypes.ERROR) {
|
|
66
|
+
const code = typeof chunk.data.code === "string" ? chunk.data.code : "stream_error";
|
|
67
|
+
if (code === "aborted" || code === "superseded") {
|
|
68
|
+
return [{ type: "stream.completed" }];
|
|
69
|
+
}
|
|
70
|
+
// Expose the real reason to the client for debugging.
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
type: "error",
|
|
74
|
+
error: {
|
|
75
|
+
code: "STREAM_FAILED",
|
|
76
|
+
message:
|
|
77
|
+
typeof chunk.data.content === "string" && chunk.data.content
|
|
78
|
+
? chunk.data.content
|
|
79
|
+
: "Stream failed",
|
|
80
|
+
retryable: false,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
{ type: "stream.completed" },
|
|
84
|
+
];
|
|
85
|
+
}
|
|
65
86
|
if (chunk.type === MessageChunkTypes.MESSAGE_FAILED) {
|
|
66
87
|
return [
|
|
67
88
|
{ type: "error", error: { code: "STREAM_FAILED", message: "Stream failed", retryable: true } },
|
|
@@ -76,6 +76,18 @@ export interface ChannelAdapter<TConfig = unknown> {
|
|
|
76
76
|
message: OutboundMessage,
|
|
77
77
|
installation: ChannelInstallation,
|
|
78
78
|
): Promise<void>;
|
|
79
|
+
/**
|
|
80
|
+
* 可选:把 Agent 暂停等待人工输入时的问题/提示投影到会话中。
|
|
81
|
+
*
|
|
82
|
+
* 与 `sendReply` 不同,这不是对某条入站消息的最终回复,而是运行中途
|
|
83
|
+
* 产生的一条独立 bot 消息(例如 Project Room 的 clarify 卡片)。
|
|
84
|
+
* `message.metadata.inputMessageId` 关联触发该运行的入站队列消息。
|
|
85
|
+
*/
|
|
86
|
+
sendInterrupt?(
|
|
87
|
+
replyTarget: ReplyTarget,
|
|
88
|
+
message: OutboundMessage,
|
|
89
|
+
installation: ChannelInstallation,
|
|
90
|
+
): Promise<void>;
|
|
79
91
|
/**
|
|
80
92
|
* 可选:Channel 自定义 thread ID 生成策略。
|
|
81
93
|
* 如果提供,MessageRouter 会优先使用此方法决定 thread ID,
|
package/src/MessageProtocol.ts
CHANGED
|
@@ -82,6 +82,7 @@ export const MessageChunkTypes = {
|
|
|
82
82
|
AI: 'ai',
|
|
83
83
|
TOOL: 'tool',
|
|
84
84
|
INTERRUPT: 'interrupt',
|
|
85
|
+
ERROR: 'error',
|
|
85
86
|
MESSAGE_COMPLETED: 'message_completed',
|
|
86
87
|
MESSAGE_FAILED: 'message_failed',
|
|
87
88
|
THREAD_IDLE: 'thread_idle',
|
|
@@ -94,6 +95,11 @@ export interface MessageChunk {
|
|
|
94
95
|
data: {
|
|
95
96
|
id: string;
|
|
96
97
|
content?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Machine-readable end reason for {@link MessageChunkTypes.ERROR} chunks.
|
|
100
|
+
* Known values: `aborted`, `superseded`, `stream_error`.
|
|
101
|
+
*/
|
|
102
|
+
code?: string;
|
|
97
103
|
tool_call_chunks?: Array<{
|
|
98
104
|
name?: string;
|
|
99
105
|
args?: string;
|
|
@@ -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;
|
|
@@ -18,6 +18,17 @@ export type ProjectRoomMessageSource =
|
|
|
18
18
|
| "routine"
|
|
19
19
|
| "system";
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Canonical label prefix for the one-time room event that announces agent-to-agent task delegation.
|
|
23
|
+
*
|
|
24
|
+
* The projection text is `${PROJECT_ROOM_TASK_DELEGATED_LABEL} · <creator> → <owner>`; the UI maps
|
|
25
|
+
* the exact label or this prefix to the "delegated" task-event kind and never parses arbitrary prose.
|
|
26
|
+
*/
|
|
27
|
+
export const PROJECT_ROOM_TASK_DELEGATED_LABEL = "Task delegated";
|
|
28
|
+
|
|
29
|
+
/** Separator between the delegation label and its resolved actor labels. */
|
|
30
|
+
export const PROJECT_ROOM_TASK_DELEGATED_SEPARATOR = " · ";
|
|
31
|
+
|
|
21
32
|
/** The main room associated with a project. */
|
|
22
33
|
export interface ProjectRoom {
|
|
23
34
|
id: string;
|
|
@@ -21,6 +21,8 @@ export interface ProjectRoomPublicMessage {
|
|
|
21
21
|
mentions: ProjectRoomMention[];
|
|
22
22
|
replyToMessageId?: string;
|
|
23
23
|
source: ProjectRoomMessageSource;
|
|
24
|
+
/** Canonical source identity (e.g. the Task id for source "task"). */
|
|
25
|
+
sourceId?: string;
|
|
24
26
|
createdAt: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -264,6 +266,10 @@ function mapPublicMessageRecord(record: Record<string, unknown>): ProjectRoomPub
|
|
|
264
266
|
if (typeof record.replyToMessageId !== "string") return undefined;
|
|
265
267
|
result.replyToMessageId = record.replyToMessageId;
|
|
266
268
|
}
|
|
269
|
+
if (record.sourceId !== undefined) {
|
|
270
|
+
if (typeof record.sourceId !== "string") return undefined;
|
|
271
|
+
result.sourceId = record.sourceId;
|
|
272
|
+
}
|
|
267
273
|
return result;
|
|
268
274
|
}
|
|
269
275
|
|
|
@@ -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,25 @@
|
|
|
1
|
+
/** A browser Web Push subscription belonging to one user. */
|
|
2
|
+
export interface UserPushSubscription {
|
|
3
|
+
id: string;
|
|
4
|
+
tenantId: string;
|
|
5
|
+
userId: string;
|
|
6
|
+
/** Push service endpoint URL from the browser's PushSubscription. */
|
|
7
|
+
endpoint: string;
|
|
8
|
+
/** Client public keys used to encrypt the payload. */
|
|
9
|
+
keys: { p256dh: string; auth: string };
|
|
10
|
+
userAgent?: string;
|
|
11
|
+
createdAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Persistence operations for per-user Web Push subscriptions. */
|
|
15
|
+
export interface UserPushSubscriptionStore {
|
|
16
|
+
/** Lists every subscription for a user (a user may have several devices). */
|
|
17
|
+
list(tenantId: string, userId: string): Promise<UserPushSubscription[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Saves a subscription. Re-saving the same endpoint replaces its keys/agent
|
|
20
|
+
* (idempotent per tenant+user+endpoint).
|
|
21
|
+
*/
|
|
22
|
+
save(input: Omit<UserPushSubscription, "id" | "createdAt">): Promise<UserPushSubscription>;
|
|
23
|
+
/** Deletes one subscription by endpoint. Returns whether a row was removed. */
|
|
24
|
+
delete(tenantId: string, userId: string, endpoint: string): Promise<boolean>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentWebAppStreamProjection error handling tests.
|
|
3
|
+
*
|
|
4
|
+
* Internal `error` chunks (emitted when an Agent run is aborted or fails)
|
|
5
|
+
* must be projected onto the stable public event surface instead of being
|
|
6
|
+
* silently dropped or mistaken for assistant text.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { projectAgentWebAppChunk } from "../AgentWebAppStreamProjection";
|
|
10
|
+
|
|
11
|
+
describe("projectAgentWebAppChunk - error chunks", () => {
|
|
12
|
+
it("projects a stream_error chunk to the real error message", () => {
|
|
13
|
+
const events = projectAgentWebAppChunk({
|
|
14
|
+
type: "error",
|
|
15
|
+
data: { id: "err-1", content: "Provider rate limit exceeded", code: "stream_error" },
|
|
16
|
+
} as never);
|
|
17
|
+
|
|
18
|
+
expect(events).toEqual([
|
|
19
|
+
{
|
|
20
|
+
type: "error",
|
|
21
|
+
error: { code: "STREAM_FAILED", message: "Provider rate limit exceeded", retryable: false },
|
|
22
|
+
},
|
|
23
|
+
{ type: "stream.completed" },
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("falls back to 'Stream failed' when no content is provided", () => {
|
|
28
|
+
const events = projectAgentWebAppChunk({
|
|
29
|
+
type: "error",
|
|
30
|
+
data: { id: "err-2", code: "stream_error" },
|
|
31
|
+
} as never);
|
|
32
|
+
|
|
33
|
+
expect(events).toEqual([
|
|
34
|
+
{
|
|
35
|
+
type: "error",
|
|
36
|
+
error: { code: "STREAM_FAILED", message: "Stream failed", retryable: false },
|
|
37
|
+
},
|
|
38
|
+
{ type: "stream.completed" },
|
|
39
|
+
]);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("treats a user abort as a neutral completion without an error event", () => {
|
|
43
|
+
const events = projectAgentWebAppChunk({
|
|
44
|
+
type: "error",
|
|
45
|
+
data: { id: "err-2", content: "Thread aborted", code: "aborted" },
|
|
46
|
+
} as never);
|
|
47
|
+
|
|
48
|
+
expect(events).toEqual([{ type: "stream.completed" }]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("treats a superseded run as a neutral completion without an error event", () => {
|
|
52
|
+
const events = projectAgentWebAppChunk({
|
|
53
|
+
type: "error",
|
|
54
|
+
data: { id: "err-3", content: "Thread superseded", code: "superseded" },
|
|
55
|
+
} as never);
|
|
56
|
+
|
|
57
|
+
expect(events).toEqual([{ type: "stream.completed" }]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -29,6 +29,7 @@ const message: ProjectRoomBusinessEvent = {
|
|
|
29
29
|
content: { type: "text", text: "Complete" },
|
|
30
30
|
mentions: [],
|
|
31
31
|
source: "agent",
|
|
32
|
+
sourceId: "reply-1",
|
|
32
33
|
createdAt: "2026-09-03T00:00:00.000Z",
|
|
33
34
|
},
|
|
34
35
|
},
|
|
@@ -84,7 +85,6 @@ describe("Project Room realtime protocol", () => {
|
|
|
84
85
|
"resync",
|
|
85
86
|
]);
|
|
86
87
|
expect(JSON.stringify(message)).not.toContain("assistantId");
|
|
87
|
-
expect(JSON.stringify(message)).not.toContain("sourceId");
|
|
88
88
|
expect(scope).toEqual({ tenantId: "tenant-1", roomId: "room-1", projectId: "project-1" });
|
|
89
89
|
expect(actor).toEqual({
|
|
90
90
|
tenantId: "tenant-1",
|
|
@@ -127,7 +127,7 @@ describe("Project Room realtime protocol", () => {
|
|
|
127
127
|
status: "active", joinedAt: "2026-09-03T00:00:00.000Z", updatedAt: "2026-09-03T00:00:01.000Z",
|
|
128
128
|
});
|
|
129
129
|
expect(JSON.stringify(toProjectRoomPublicMessage(internalMessage))).not.toMatch(
|
|
130
|
-
/tenantId|workspaceId|projectId|assistantId|thread|idempotency
|
|
130
|
+
/tenantId|workspaceId|projectId|assistantId|thread|idempotency/,
|
|
131
131
|
);
|
|
132
132
|
});
|
|
133
133
|
|
|
@@ -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
|
@@ -57,6 +57,7 @@ export * from "./ProjectMembershipStoreProtocol";
|
|
|
57
57
|
export * from "./ProjectBotMembershipStoreProtocol";
|
|
58
58
|
export * from "./ProjectRoomMessageStoreProtocol";
|
|
59
59
|
export * from "./ProjectRoomReadStateProtocol";
|
|
60
|
+
export * from "./UserPushSubscriptionStoreProtocol";
|
|
60
61
|
export * from "./ExactDataSnapshot";
|
|
61
62
|
export * from "./ProjectRoomRealtimeProtocol";
|
|
62
63
|
|
|
@@ -85,6 +86,9 @@ export type {
|
|
|
85
86
|
PluginMiddlewareFactory,
|
|
86
87
|
} from "./PluginProtocol";
|
|
87
88
|
|
|
89
|
+
export * from "./SandboxPluginProtocol";
|
|
90
|
+
|
|
88
91
|
// 导出通用类型
|
|
89
92
|
export * from "./types";
|
|
90
93
|
export * from "./TrustedRunContextProtocol";
|
|
94
|
+
export * from "./OpenProtocol";
|