@axiom-lattice/protocols 4.2.1 → 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 +14 -0
- package/dist/index.d.mts +54 -1
- package/dist/index.d.ts +54 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +28 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AgentWebAppStreamProjection.ts +21 -0
- package/src/ChannelAdapterProtocol.ts +12 -0
- package/src/MessageProtocol.ts +6 -0
- package/src/ProjectRoomProtocol.ts +11 -0
- package/src/ProjectRoomRealtimeProtocol.ts +6 -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/index.ts +1 -0
package/package.json
CHANGED
|
@@ -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;
|
|
@@ -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,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
|
|
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
|
|