@cydm/happy-elves 0.1.0-beta.307 → 0.1.0-beta.308
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/apps/cli/dist/commands/gateway.js +19 -0
- package/apps/cli/dist/commands/lib/usage.js +2 -0
- package/apps/cli/package.json +1 -1
- package/apps/daemon/dist/gateway/handler.js +27 -1
- package/apps/daemon/dist/gateway/runtime.d.ts +5 -0
- package/apps/daemon/dist/gateway/runtime.js +62 -0
- package/apps/daemon/dist/gateway/store.d.ts +3 -3
- package/apps/daemon/package.json +1 -1
- package/package.json +1 -1
- package/packages/client/dist/types.d.ts +1 -1
- package/packages/shared/dist/protocol-schemas.d.ts +11 -6
- package/packages/shared/dist/protocol-schemas.js +6 -4
- package/packages/shared/dist/protocol-types.d.ts +6 -4
- package/packages/shared/dist/protocol.d.ts +2 -2
- package/packages/shared/dist/protocol.js +2 -2
|
@@ -118,6 +118,25 @@ export async function handleGateway({ domain, action, positional, flags }) {
|
|
|
118
118
|
ok("gateway.test", result);
|
|
119
119
|
return true;
|
|
120
120
|
}
|
|
121
|
+
if (action === "notify") {
|
|
122
|
+
const config = await readConfig(flags);
|
|
123
|
+
const client = new ControllerClient(config);
|
|
124
|
+
const machineId = await resolveGatewayMachineId(client, flags);
|
|
125
|
+
const channelId = gatewayChannelId(flags, positional[0], "lark");
|
|
126
|
+
const externalConversationId = requireString(flags, "conversation");
|
|
127
|
+
const externalMessageId = typeof flags["message-id"] === "string" ? flags["message-id"] : `notify_${Date.now()}`;
|
|
128
|
+
const text = requireString(flags, "text");
|
|
129
|
+
const result = await client.gateway({
|
|
130
|
+
machineId,
|
|
131
|
+
action: "notify",
|
|
132
|
+
channelId,
|
|
133
|
+
externalConversationId,
|
|
134
|
+
externalMessageId,
|
|
135
|
+
text,
|
|
136
|
+
});
|
|
137
|
+
ok("gateway.notify", result);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
121
140
|
if (action === "serve") {
|
|
122
141
|
const config = await readConfig(flags);
|
|
123
142
|
const port = Number(typeof flags.port === "string" ? flags.port : "8789");
|
|
@@ -119,6 +119,7 @@ session stars, recent sessions, pinned workspaces, and current project defaults.
|
|
|
119
119
|
happy-elves gateway disable [<channelId>] [--machine <machineId>] --json
|
|
120
120
|
happy-elves gateway status [--machine <machineId>] [--include-debug] [--json]
|
|
121
121
|
happy-elves gateway test [<channelId>] [--machine <machineId>] --conversation <externalConversationId> --message-id <externalMessageId> --text <prompt> --json
|
|
122
|
+
happy-elves gateway notify [<channelId>] [--machine <machineId>] --conversation <externalConversationId> [--message-id <id>] --text <message> --json
|
|
122
123
|
happy-elves gateway serve [--host 127.0.0.1] [--port 8789] --json # debug only
|
|
123
124
|
|
|
124
125
|
Gateway v1 official Feishu/Lark path is daemon-backed SDK long connection.
|
|
@@ -253,6 +254,7 @@ Core commands:
|
|
|
253
254
|
gateway status --json
|
|
254
255
|
gateway enable --channel fake --machine <machineId> --cwd <cwd> --json # debug only
|
|
255
256
|
gateway test --conversation <id> --message-id <id> --text <prompt> --json
|
|
257
|
+
gateway notify --conversation <id> --text <message> --json
|
|
256
258
|
|
|
257
259
|
memory save --project <key> --text <fact> --json
|
|
258
260
|
memory search --project <key> --query <prompt> --json
|
package/apps/cli/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decryptJson } from "../../../../packages/shared/dist/index.js";
|
|
2
2
|
import { claimCommandRequest, sendCommandResponse } from "../relay/send.js";
|
|
3
|
-
import { deliverGatewayMessage, publicChannelsForStore, setGatewayLastError, syncGatewayRuntime } from "./runtime.js";
|
|
3
|
+
import { deliverGatewayMessage, notifyGatewayConversation, publicChannelsForStore, setGatewayLastError, syncGatewayRuntime } from "./runtime.js";
|
|
4
4
|
import { readGatewayStore, updateGatewayStore, upsertGatewayChannel } from "./store.js";
|
|
5
5
|
export async function handleGatewayCommand(ws, config, command) {
|
|
6
6
|
if (!claimCommandRequest(ws, command.requestId))
|
|
@@ -86,6 +86,32 @@ async function executeGatewayCommand(config, command) {
|
|
|
86
86
|
throw error;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
+
if (command.action === "notify") {
|
|
90
|
+
const store = await readGatewayStore();
|
|
91
|
+
const channel = requireChannel(store, command.channelId);
|
|
92
|
+
if (!channel.enabled)
|
|
93
|
+
throw new Error("Gateway channel is disabled");
|
|
94
|
+
if (!command.externalConversationId || !command.externalMessageId || !command.text) {
|
|
95
|
+
throw new Error("gateway notify requires externalConversationId, externalMessageId, and text");
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const delivery = await notifyGatewayConversation(channel, {
|
|
99
|
+
externalConversationId: command.externalConversationId,
|
|
100
|
+
externalMessageId: command.externalMessageId,
|
|
101
|
+
text: command.text,
|
|
102
|
+
});
|
|
103
|
+
const nextStore = await readGatewayStore();
|
|
104
|
+
return {
|
|
105
|
+
action: "notify",
|
|
106
|
+
channel: publicChannelsForStore(nextStore).find((item) => item.id === channel.id),
|
|
107
|
+
delivery,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
await setGatewayLastError(channel.id, error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
89
115
|
throw new Error(`Unsupported gateway action: ${command.action}`);
|
|
90
116
|
}
|
|
91
117
|
async function decryptGatewayChannelInput(config, input) {
|
|
@@ -22,6 +22,11 @@ export declare function startGatewayRuntime(config: DaemonConfig): Promise<void>
|
|
|
22
22
|
export declare function stopGatewayRuntime(): Promise<void>;
|
|
23
23
|
export declare function syncGatewayRuntime(config?: DaemonConfig | undefined): Promise<void>;
|
|
24
24
|
export declare function deliverGatewayMessage(config: DaemonConfig, channel: GatewayChannelConfig, message: GatewayInboundMessage): Promise<GatewayDeliveryResult>;
|
|
25
|
+
export declare function notifyGatewayConversation(channel: GatewayChannelConfig, input: {
|
|
26
|
+
externalConversationId: string;
|
|
27
|
+
externalMessageId: string;
|
|
28
|
+
text: string;
|
|
29
|
+
}): Promise<GatewayDeliveryResult>;
|
|
25
30
|
export declare function handleGatewayTurnTerminal(config: DaemonConfig, input: GatewayTerminalInput): Promise<void>;
|
|
26
31
|
export declare function setGatewayLastError(channelId: string, error: unknown): Promise<void>;
|
|
27
32
|
export declare function publicChannelsForStore(store: GatewayStore): import("../../../../packages/shared/dist/index.js").GatewayChannelPublic[];
|
|
@@ -229,6 +229,58 @@ export async function deliverGatewayMessage(config, channel, message) {
|
|
|
229
229
|
senderDisplayName: message.senderDisplayName,
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
|
+
export async function notifyGatewayConversation(channel, input) {
|
|
233
|
+
if (!channel.enabled)
|
|
234
|
+
throw gatewayError("Gateway channel is disabled", "GATEWAY_DISABLED", 400);
|
|
235
|
+
const adapter = gatewayAdapters.get(channel.id)?.adapter;
|
|
236
|
+
if (!adapter)
|
|
237
|
+
throw gatewayError("Gateway adapter is not listening", "GATEWAY_NOT_LISTENING", 503);
|
|
238
|
+
const destination = destinationFromGatewayConversationId(input.externalConversationId);
|
|
239
|
+
const deliveryId = stableGatewayId("gw_notify", channel.id, input.externalConversationId, input.externalMessageId);
|
|
240
|
+
const result = await adapter.send({
|
|
241
|
+
deliveryId,
|
|
242
|
+
destination,
|
|
243
|
+
payload: {
|
|
244
|
+
type: "notice",
|
|
245
|
+
title: "Happy Elves",
|
|
246
|
+
text: input.text,
|
|
247
|
+
severity: "info",
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
const repliedAt = new Date().toISOString();
|
|
251
|
+
const lastReplied = {
|
|
252
|
+
externalConversationId: input.externalConversationId,
|
|
253
|
+
externalMessageId: input.externalMessageId,
|
|
254
|
+
deliveryId,
|
|
255
|
+
platformMessageId: result.platformMessageId,
|
|
256
|
+
at: repliedAt,
|
|
257
|
+
};
|
|
258
|
+
await updateGatewayStore((current) => ({
|
|
259
|
+
...current,
|
|
260
|
+
channels: current.channels.map((item) => item.id === channel.id
|
|
261
|
+
? { ...item, lastError: undefined, lastReplied, updatedAt: repliedAt }
|
|
262
|
+
: item),
|
|
263
|
+
replyHistory: [
|
|
264
|
+
{
|
|
265
|
+
channelId: channel.id,
|
|
266
|
+
externalConversationId: input.externalConversationId,
|
|
267
|
+
externalMessageId: input.externalMessageId,
|
|
268
|
+
deliveryId,
|
|
269
|
+
platformMessageId: result.platformMessageId,
|
|
270
|
+
repliedAt,
|
|
271
|
+
},
|
|
272
|
+
...current.replyHistory.filter((item) => !(item.channelId === channel.id && item.deliveryId === deliveryId)),
|
|
273
|
+
].slice(0, gatewayReplyHistoryLimit),
|
|
274
|
+
}));
|
|
275
|
+
return {
|
|
276
|
+
channelId: channel.id,
|
|
277
|
+
externalConversationId: input.externalConversationId,
|
|
278
|
+
externalMessageId: input.externalMessageId,
|
|
279
|
+
deliveryId,
|
|
280
|
+
platformMessageId: result.platformMessageId,
|
|
281
|
+
dispatch: "delivered",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
232
284
|
export async function handleGatewayTurnTerminal(config, input) {
|
|
233
285
|
await deliverPendingGatewayReply(config, input);
|
|
234
286
|
await drainGatewayQueue(config, input.sessionId);
|
|
@@ -630,6 +682,16 @@ function gatewayConversationId(destination) {
|
|
|
630
682
|
return `${destination.platformChatId}:thread:${destination.contextKey}`;
|
|
631
683
|
return `${destination.platformChatId}:main`;
|
|
632
684
|
}
|
|
685
|
+
function destinationFromGatewayConversationId(externalConversationId) {
|
|
686
|
+
const threadMatch = /^(.+):thread:(.+)$/.exec(externalConversationId);
|
|
687
|
+
if (threadMatch) {
|
|
688
|
+
return { platformChatId: threadMatch[1], contextKind: "thread", contextKey: threadMatch[2] };
|
|
689
|
+
}
|
|
690
|
+
const mainMatch = /^(.+):main$/.exec(externalConversationId);
|
|
691
|
+
if (mainMatch)
|
|
692
|
+
return { platformChatId: mainMatch[1], contextKind: "main" };
|
|
693
|
+
throw gatewayError("Gateway notify requires a conversation id like <chatId>:main or <chatId>:thread:<threadId>", "GATEWAY_CONVERSATION_INVALID", 400);
|
|
694
|
+
}
|
|
633
695
|
function gatewayAdapterKey(channel) {
|
|
634
696
|
return JSON.stringify({
|
|
635
697
|
type: channel.type,
|
|
@@ -70,9 +70,9 @@ type GatewayReplyHistory = {
|
|
|
70
70
|
channelId: string;
|
|
71
71
|
externalConversationId: string;
|
|
72
72
|
externalMessageId: string;
|
|
73
|
-
sessionId
|
|
74
|
-
requestId
|
|
75
|
-
turnId
|
|
73
|
+
sessionId?: string;
|
|
74
|
+
requestId?: string;
|
|
75
|
+
turnId?: string;
|
|
76
76
|
deliveryId: string;
|
|
77
77
|
platformMessageId?: string;
|
|
78
78
|
repliedAt: string;
|
package/apps/daemon/package.json
CHANGED
package/package.json
CHANGED
|
@@ -297,7 +297,7 @@ export type MachineDevExecInput = {
|
|
|
297
297
|
};
|
|
298
298
|
export type GatewayCommandInput = {
|
|
299
299
|
machineId: string;
|
|
300
|
-
action: "list" | "get" | "upsert" | "enable" | "disable" | "test";
|
|
300
|
+
action: "list" | "get" | "upsert" | "enable" | "disable" | "test" | "notify";
|
|
301
301
|
channelId?: string;
|
|
302
302
|
channel?: GatewayChannelInput;
|
|
303
303
|
externalConversationId?: string;
|
|
@@ -552,9 +552,11 @@ export declare const gatewayDeliveryResultSchema: z.ZodObject<{
|
|
|
552
552
|
channelId: z.ZodString;
|
|
553
553
|
externalConversationId: z.ZodString;
|
|
554
554
|
externalMessageId: z.ZodString;
|
|
555
|
-
sessionId: z.ZodString
|
|
556
|
-
requestId: z.ZodString
|
|
557
|
-
turnId: z.ZodString
|
|
555
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
556
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
557
|
+
turnId: z.ZodOptional<z.ZodString>;
|
|
558
|
+
deliveryId: z.ZodOptional<z.ZodString>;
|
|
559
|
+
platformMessageId: z.ZodOptional<z.ZodString>;
|
|
558
560
|
admission: z.ZodOptional<z.ZodEnum<{
|
|
559
561
|
accepted: "accepted";
|
|
560
562
|
replayed: "replayed";
|
|
@@ -574,6 +576,7 @@ export declare const gatewayCommandResultSchema: z.ZodObject<{
|
|
|
574
576
|
enable: "enable";
|
|
575
577
|
disable: "disable";
|
|
576
578
|
test: "test";
|
|
579
|
+
notify: "notify";
|
|
577
580
|
}>;
|
|
578
581
|
channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
579
582
|
id: z.ZodString;
|
|
@@ -727,9 +730,11 @@ export declare const gatewayCommandResultSchema: z.ZodObject<{
|
|
|
727
730
|
channelId: z.ZodString;
|
|
728
731
|
externalConversationId: z.ZodString;
|
|
729
732
|
externalMessageId: z.ZodString;
|
|
730
|
-
sessionId: z.ZodString
|
|
731
|
-
requestId: z.ZodString
|
|
732
|
-
turnId: z.ZodString
|
|
733
|
+
sessionId: z.ZodOptional<z.ZodString>;
|
|
734
|
+
requestId: z.ZodOptional<z.ZodString>;
|
|
735
|
+
turnId: z.ZodOptional<z.ZodString>;
|
|
736
|
+
deliveryId: z.ZodOptional<z.ZodString>;
|
|
737
|
+
platformMessageId: z.ZodOptional<z.ZodString>;
|
|
733
738
|
admission: z.ZodOptional<z.ZodEnum<{
|
|
734
739
|
accepted: "accepted";
|
|
735
740
|
replayed: "replayed";
|
|
@@ -281,16 +281,18 @@ export const gatewayDeliveryResultSchema = z.object({
|
|
|
281
281
|
channelId: z.string().min(1),
|
|
282
282
|
externalConversationId: z.string().min(1),
|
|
283
283
|
externalMessageId: z.string().min(1),
|
|
284
|
-
sessionId: z.string().min(1),
|
|
285
|
-
requestId: z.string().min(1),
|
|
286
|
-
turnId: z.string().min(1),
|
|
284
|
+
sessionId: z.string().min(1).optional(),
|
|
285
|
+
requestId: z.string().min(1).optional(),
|
|
286
|
+
turnId: z.string().min(1).optional(),
|
|
287
|
+
deliveryId: z.string().min(1).optional(),
|
|
288
|
+
platformMessageId: z.string().min(1).optional(),
|
|
287
289
|
admission: z.enum(["accepted", "replayed"]).optional(),
|
|
288
290
|
dispatch: z.enum(["queued", "delivered"]).optional(),
|
|
289
291
|
queued: z.boolean().optional(),
|
|
290
292
|
duplicate: z.boolean().optional(),
|
|
291
293
|
});
|
|
292
294
|
export const gatewayCommandResultSchema = z.object({
|
|
293
|
-
action: z.enum(["list", "get", "upsert", "enable", "disable", "test"]),
|
|
295
|
+
action: z.enum(["list", "get", "upsert", "enable", "disable", "test", "notify"]),
|
|
294
296
|
channels: z.array(gatewayChannelPublicSchema).optional(),
|
|
295
297
|
channel: gatewayChannelPublicSchema.optional(),
|
|
296
298
|
delivery: gatewayDeliveryResultSchema.optional(),
|
|
@@ -295,16 +295,18 @@ export type GatewayDeliveryResult = {
|
|
|
295
295
|
channelId: string;
|
|
296
296
|
externalConversationId: string;
|
|
297
297
|
externalMessageId: string;
|
|
298
|
-
sessionId
|
|
299
|
-
requestId
|
|
300
|
-
turnId
|
|
298
|
+
sessionId?: string;
|
|
299
|
+
requestId?: string;
|
|
300
|
+
turnId?: string;
|
|
301
|
+
deliveryId?: string;
|
|
302
|
+
platformMessageId?: string;
|
|
301
303
|
admission?: "accepted" | "replayed";
|
|
302
304
|
dispatch?: "queued" | "delivered";
|
|
303
305
|
queued?: boolean;
|
|
304
306
|
duplicate?: boolean;
|
|
305
307
|
};
|
|
306
308
|
export type GatewayCommandResult = {
|
|
307
|
-
action: "list" | "get" | "upsert" | "enable" | "disable" | "test";
|
|
309
|
+
action: "list" | "get" | "upsert" | "enable" | "disable" | "test" | "notify";
|
|
308
310
|
channels?: GatewayChannelPublic[];
|
|
309
311
|
channel?: GatewayChannelPublic;
|
|
310
312
|
delivery?: GatewayDeliveryResult;
|
|
@@ -85,7 +85,7 @@ export type ControllerClientMessage = {
|
|
|
85
85
|
type: "controller:gateway";
|
|
86
86
|
requestId: string;
|
|
87
87
|
machineId: string;
|
|
88
|
-
action: "list" | "get" | "upsert" | "enable" | "disable" | "test";
|
|
88
|
+
action: "list" | "get" | "upsert" | "enable" | "disable" | "test" | "notify";
|
|
89
89
|
channelId?: string;
|
|
90
90
|
channel?: GatewayChannelInput;
|
|
91
91
|
externalConversationId?: string;
|
|
@@ -398,7 +398,7 @@ export type MachineCommand = {
|
|
|
398
398
|
type: "machine:gateway";
|
|
399
399
|
requestId: string;
|
|
400
400
|
machineId: string;
|
|
401
|
-
action: "list" | "get" | "upsert" | "enable" | "disable" | "test";
|
|
401
|
+
action: "list" | "get" | "upsert" | "enable" | "disable" | "test" | "notify";
|
|
402
402
|
channelId?: string;
|
|
403
403
|
channel?: GatewayChannelInput;
|
|
404
404
|
externalConversationId?: string;
|
|
@@ -97,7 +97,7 @@ const controllerMessageSchema = z.discriminatedUnion("type", [
|
|
|
97
97
|
type: z.literal("controller:gateway"),
|
|
98
98
|
requestId: z.string().min(1),
|
|
99
99
|
machineId: z.string().min(1),
|
|
100
|
-
action: z.enum(["list", "get", "upsert", "enable", "disable", "test"]),
|
|
100
|
+
action: z.enum(["list", "get", "upsert", "enable", "disable", "test", "notify"]),
|
|
101
101
|
channelId: z.string().min(1).optional(),
|
|
102
102
|
channel: gatewayChannelInputSchema.optional(),
|
|
103
103
|
externalConversationId: z.string().min(1).optional(),
|
|
@@ -473,7 +473,7 @@ const machineCommandSchema = z.discriminatedUnion("type", [
|
|
|
473
473
|
type: z.literal("machine:gateway"),
|
|
474
474
|
requestId: z.string().min(1),
|
|
475
475
|
machineId: z.string().min(1),
|
|
476
|
-
action: z.enum(["list", "get", "upsert", "enable", "disable", "test"]),
|
|
476
|
+
action: z.enum(["list", "get", "upsert", "enable", "disable", "test", "notify"]),
|
|
477
477
|
channelId: z.string().min(1).optional(),
|
|
478
478
|
channel: gatewayChannelInputSchema.optional(),
|
|
479
479
|
externalConversationId: z.string().min(1).optional(),
|