@opentag/lark 0.2.0
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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/inbound.d.ts +73 -0
- package/dist/inbound.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +640 -0
- package/dist/index.js.map +1 -0
- package/dist/ingress.d.ts +40 -0
- package/dist/ingress.d.ts.map +1 -0
- package/dist/normalize.d.ts +36 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/outbound.d.ts +27 -0
- package/dist/outbound.d.ts.map +1 -0
- package/dist/registration.d.ts +73 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.d.ts.map +1 -0
- package/package.json +46 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amplift
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @opentag/lark
|
|
2
|
+
|
|
3
|
+
Lark / Feishu adapter helpers for OpenTag.
|
|
4
|
+
|
|
5
|
+
Use this package to receive Lark Personal Agent messages, normalize them into OpenTag events, register a Personal Agent by QR scan, and send local replies through the OpenTag dispatcher.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @opentag/lark
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Exports
|
|
14
|
+
|
|
15
|
+
- `createLarkMessageHandler`: handles `im.message.receive_v1` events.
|
|
16
|
+
- `startLarkIngress`: starts a Lark long-connection ingress.
|
|
17
|
+
- `registerLarkPersonalAgent`: creates a Personal Agent registration flow.
|
|
18
|
+
- `normalizeLarkMessage`: converts Lark messages into `OpenTagEvent` objects.
|
|
19
|
+
- `renderLarkFinalResult`: renders OpenTag run results for Lark.
|
|
20
|
+
|
|
21
|
+
## Example
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { startLarkIngress } from "@opentag/lark";
|
|
25
|
+
|
|
26
|
+
const ingress = startLarkIngress({
|
|
27
|
+
appId: process.env.LARK_APP_ID!,
|
|
28
|
+
appSecret: process.env.LARK_APP_SECRET!,
|
|
29
|
+
domain: "lark",
|
|
30
|
+
dispatcherUrl: "http://localhost:3030",
|
|
31
|
+
agentId: "opentag"
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await ingress.startPromise;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Stability
|
|
38
|
+
|
|
39
|
+
The event normalization and ingress config shapes are public adapter contracts. Add optional fields instead of changing existing required fields.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type OpenTagEvent } from "@opentag/core";
|
|
2
|
+
import type { CreateRunResult } from "@opentag/client";
|
|
3
|
+
import { type LarkChannelBinding } from "./normalize.js";
|
|
4
|
+
export type LarkMention = {
|
|
5
|
+
key?: string;
|
|
6
|
+
id?: {
|
|
7
|
+
open_id?: string;
|
|
8
|
+
};
|
|
9
|
+
name?: string;
|
|
10
|
+
};
|
|
11
|
+
export type LarkInboundMessageEvent = {
|
|
12
|
+
event_id?: string;
|
|
13
|
+
event_type?: string;
|
|
14
|
+
create_time?: string;
|
|
15
|
+
tenant_key?: string;
|
|
16
|
+
app_id?: string;
|
|
17
|
+
sender?: {
|
|
18
|
+
sender_id?: {
|
|
19
|
+
open_id?: string;
|
|
20
|
+
user_id?: string;
|
|
21
|
+
union_id?: string;
|
|
22
|
+
};
|
|
23
|
+
sender_type?: string;
|
|
24
|
+
tenant_key?: string;
|
|
25
|
+
};
|
|
26
|
+
message?: {
|
|
27
|
+
message_id?: string;
|
|
28
|
+
root_id?: string;
|
|
29
|
+
parent_id?: string;
|
|
30
|
+
chat_id?: string;
|
|
31
|
+
chat_type?: string;
|
|
32
|
+
message_type?: string;
|
|
33
|
+
content?: string;
|
|
34
|
+
mentions?: LarkMention[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type LarkMessageHandlerConfig = {
|
|
38
|
+
agentId: string;
|
|
39
|
+
botOpenId?: string;
|
|
40
|
+
callbackUri?: string;
|
|
41
|
+
defaultRepoBinding?: {
|
|
42
|
+
repoProvider: string;
|
|
43
|
+
owner: string;
|
|
44
|
+
repo: string;
|
|
45
|
+
};
|
|
46
|
+
resolveChannelBinding(input: {
|
|
47
|
+
tenantKey: string;
|
|
48
|
+
chatId: string;
|
|
49
|
+
}): Promise<LarkChannelBinding | null>;
|
|
50
|
+
createRun(event: OpenTagEvent): Promise<CreateRunResult>;
|
|
51
|
+
bindChannel?(input: {
|
|
52
|
+
tenantKey: string;
|
|
53
|
+
chatId: string;
|
|
54
|
+
repoProvider: string;
|
|
55
|
+
owner: string;
|
|
56
|
+
repo: string;
|
|
57
|
+
}): Promise<void>;
|
|
58
|
+
reply?(input: {
|
|
59
|
+
messageId: string;
|
|
60
|
+
text: string;
|
|
61
|
+
}): Promise<void>;
|
|
62
|
+
now?(): number;
|
|
63
|
+
};
|
|
64
|
+
export type LarkMessageHandlerOutcome = {
|
|
65
|
+
status: "created" | "bound" | "ignored_non_text" | "ignored_invalid_payload" | "ignored_group_requires_bot_open_id" | "ignored_not_addressed" | "ignored_bind_usage" | "ignored_bind_unavailable" | "ignored_unbound_chat" | "ignored_empty_command" | "follow_up_queued" | "needs_human_decision";
|
|
66
|
+
runId?: string;
|
|
67
|
+
followUpRequestId?: string;
|
|
68
|
+
reason?: string;
|
|
69
|
+
tenantKey?: string;
|
|
70
|
+
chatId?: string;
|
|
71
|
+
};
|
|
72
|
+
export declare function createLarkMessageHandler(config: LarkMessageHandlerConfig): (data: LarkInboundMessageEvent) => Promise<LarkMessageHandlerOutcome>;
|
|
73
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
// src/normalize.ts
|
|
2
|
+
import { commandFromRawText } from "@opentag/core";
|
|
3
|
+
function stripLarkMention(text) {
|
|
4
|
+
return text.replace(/@_user_\d+/g, " ").replace(/\s+/g, " ").trim();
|
|
5
|
+
}
|
|
6
|
+
function encodeLarkThreadKey(input) {
|
|
7
|
+
return `${input.tenantKey}|${input.chatId}|${input.messageId}`;
|
|
8
|
+
}
|
|
9
|
+
function parseLarkThreadKey(threadKey) {
|
|
10
|
+
const [tenantKey, chatId, messageId] = threadKey.split("|");
|
|
11
|
+
if (!tenantKey || !chatId || !messageId) {
|
|
12
|
+
throw new Error(`Invalid Lark thread key: ${threadKey}`);
|
|
13
|
+
}
|
|
14
|
+
return { tenantKey, chatId, messageId };
|
|
15
|
+
}
|
|
16
|
+
function permissionsForIntent(intent) {
|
|
17
|
+
const permissions = [
|
|
18
|
+
{
|
|
19
|
+
scope: "chat:postMessage",
|
|
20
|
+
reason: "reply in the originating Lark thread"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
scope: "runner:local",
|
|
24
|
+
reason: "execute the run on a paired local daemon"
|
|
25
|
+
}
|
|
26
|
+
];
|
|
27
|
+
if (intent === "fix" || intent === "run") {
|
|
28
|
+
permissions.push(
|
|
29
|
+
{
|
|
30
|
+
scope: "repo:read",
|
|
31
|
+
reason: "inspect the repository in the paired local checkout"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
scope: "repo:write",
|
|
35
|
+
reason: "commit code changes on an isolated run branch"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
scope: "pr:create",
|
|
39
|
+
reason: "open a pull request for completed code changes"
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return permissions;
|
|
44
|
+
}
|
|
45
|
+
function contextPointersForCommand(command) {
|
|
46
|
+
const context = [];
|
|
47
|
+
for (const reference of command.parsed?.references ?? []) {
|
|
48
|
+
if (reference.kind === "url") {
|
|
49
|
+
context.push({
|
|
50
|
+
kind: "url",
|
|
51
|
+
uri: reference.uri,
|
|
52
|
+
visibility: "organization",
|
|
53
|
+
title: reference.title ?? "Command URL reference"
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (reference.kind === "file" || reference.kind === "path") {
|
|
58
|
+
context.push({
|
|
59
|
+
kind: "file",
|
|
60
|
+
uri: reference.uri,
|
|
61
|
+
...reference.line ? { line: reference.line } : {},
|
|
62
|
+
...reference.startLine ? { startLine: reference.startLine } : {},
|
|
63
|
+
...reference.endLine ? { endLine: reference.endLine } : {},
|
|
64
|
+
visibility: "organization",
|
|
65
|
+
title: referenceTitle(reference)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return context;
|
|
70
|
+
}
|
|
71
|
+
function referenceTitle(reference) {
|
|
72
|
+
return reference.title ?? "Command file reference";
|
|
73
|
+
}
|
|
74
|
+
function commandMetadata(command) {
|
|
75
|
+
if (!command.parsed) return {};
|
|
76
|
+
return {
|
|
77
|
+
commandParser: command.parsed.version,
|
|
78
|
+
commandDiagnostics: command.parsed.diagnostics,
|
|
79
|
+
...command.parsed.approval ? { approval: command.parsed.approval } : {},
|
|
80
|
+
...command.parsed.network ? { network: command.parsed.network } : {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function normalizeLarkMessage(input) {
|
|
84
|
+
const rawText = stripLarkMention(input.text);
|
|
85
|
+
if (!rawText) return null;
|
|
86
|
+
const command = commandFromRawText(rawText);
|
|
87
|
+
const agentId = input.agentId ?? "opentag";
|
|
88
|
+
return {
|
|
89
|
+
id: `evt_lark_message_${input.eventId}`,
|
|
90
|
+
source: "lark",
|
|
91
|
+
sourceEventId: input.eventId,
|
|
92
|
+
receivedAt: new Date(input.eventTimeMs).toISOString(),
|
|
93
|
+
actor: {
|
|
94
|
+
provider: "lark",
|
|
95
|
+
providerUserId: input.senderOpenId,
|
|
96
|
+
handle: input.senderOpenId,
|
|
97
|
+
organizationId: input.tenantKey
|
|
98
|
+
},
|
|
99
|
+
target: {
|
|
100
|
+
mention: input.botOpenId ? `@${input.botOpenId}` : "@app",
|
|
101
|
+
agentId,
|
|
102
|
+
...command.parsed?.executorHint ? { executorHint: command.parsed.executorHint } : {}
|
|
103
|
+
},
|
|
104
|
+
command,
|
|
105
|
+
context: [
|
|
106
|
+
{
|
|
107
|
+
provider: "lark",
|
|
108
|
+
kind: "message",
|
|
109
|
+
uri: `lark://tenant/${input.tenantKey}/chat/${input.chatId}/message/${input.messageId}`,
|
|
110
|
+
visibility: "organization",
|
|
111
|
+
title: "Lark message"
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
kind: "text",
|
|
115
|
+
uri: input.text,
|
|
116
|
+
visibility: "organization",
|
|
117
|
+
title: "Lark message text"
|
|
118
|
+
},
|
|
119
|
+
...contextPointersForCommand(command)
|
|
120
|
+
],
|
|
121
|
+
permissions: permissionsForIntent(command.intent),
|
|
122
|
+
callback: {
|
|
123
|
+
provider: "lark",
|
|
124
|
+
uri: input.callbackUri ?? "lark://im/v1/messages",
|
|
125
|
+
threadKey: encodeLarkThreadKey({
|
|
126
|
+
tenantKey: input.tenantKey,
|
|
127
|
+
chatId: input.chatId,
|
|
128
|
+
messageId: input.messageId
|
|
129
|
+
})
|
|
130
|
+
},
|
|
131
|
+
metadata: {
|
|
132
|
+
tenantKey: input.tenantKey,
|
|
133
|
+
chatId: input.chatId,
|
|
134
|
+
messageId: input.messageId,
|
|
135
|
+
chatType: input.chatType,
|
|
136
|
+
...input.rootId ? { rootId: input.rootId } : {},
|
|
137
|
+
...input.botOpenId ? { larkBotOpenId: input.botOpenId } : {},
|
|
138
|
+
...commandMetadata(command),
|
|
139
|
+
repoProvider: input.binding.repoProvider ?? "github",
|
|
140
|
+
owner: input.binding.owner,
|
|
141
|
+
repo: input.binding.repo
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/inbound.ts
|
|
147
|
+
import { parseProjectTargetRef } from "@opentag/core";
|
|
148
|
+
var BIND_USAGE = "Usage: /bind <owner>/<repo> \u2014 e.g. /bind amplifthq/opentag (or /bind github:amplifthq/opentag).";
|
|
149
|
+
var UNBOUND_HINT = "This chat isn't connected to a Project Target yet. @-mention me with `/bind <owner>/<repo>` to connect it \u2014 e.g. /bind amplifthq/opentag.";
|
|
150
|
+
function bindingFromDefault(input) {
|
|
151
|
+
return {
|
|
152
|
+
tenantKey: input.tenantKey,
|
|
153
|
+
chatId: input.chatId,
|
|
154
|
+
repoProvider: input.binding.repoProvider,
|
|
155
|
+
owner: input.binding.owner,
|
|
156
|
+
repo: input.binding.repo
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function shouldMigrateLegacyLocalBinding(input) {
|
|
160
|
+
return input.defaultBinding.repoProvider === "local" && input.defaultBinding.owner.startsWith("path_") && input.existing.repoProvider === "local" && input.existing.owner === "local" && input.existing.repo === input.defaultBinding.repo;
|
|
161
|
+
}
|
|
162
|
+
function extractText(content) {
|
|
163
|
+
if (!content) return "";
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(content);
|
|
166
|
+
return typeof parsed.text === "string" ? parsed.text : "";
|
|
167
|
+
} catch {
|
|
168
|
+
return "";
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function mentionsBot(mentions, botOpenId) {
|
|
172
|
+
return (mentions ?? []).some((mention) => mention.id?.open_id === botOpenId);
|
|
173
|
+
}
|
|
174
|
+
function parseBindCommand(command) {
|
|
175
|
+
if (!/^\/bind(\s|$)/.test(command)) return null;
|
|
176
|
+
const match = command.match(/^\/bind\s+(\S+)\s*$/);
|
|
177
|
+
if (!match) return { ok: false };
|
|
178
|
+
try {
|
|
179
|
+
const ref = parseProjectTargetRef(match[1]);
|
|
180
|
+
return { ok: true, repoProvider: ref.provider, owner: ref.owner, repo: ref.repo };
|
|
181
|
+
} catch {
|
|
182
|
+
return { ok: false };
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function createLarkMessageHandler(config) {
|
|
186
|
+
return async function handleLarkMessage(data) {
|
|
187
|
+
const message = data.message;
|
|
188
|
+
if (!message || message.message_type !== "text") {
|
|
189
|
+
return { status: "ignored_non_text" };
|
|
190
|
+
}
|
|
191
|
+
const tenantKey = data.tenant_key ?? data.sender?.tenant_key;
|
|
192
|
+
const chatId = message.chat_id;
|
|
193
|
+
const messageId = message.message_id;
|
|
194
|
+
const eventId = data.event_id;
|
|
195
|
+
const senderOpenId = data.sender?.sender_id?.open_id;
|
|
196
|
+
if (!tenantKey || !chatId || !messageId || !eventId || !senderOpenId) {
|
|
197
|
+
return { status: "ignored_invalid_payload" };
|
|
198
|
+
}
|
|
199
|
+
const isDirect = message.chat_type === "p2p";
|
|
200
|
+
if (!isDirect) {
|
|
201
|
+
if (!config.botOpenId) {
|
|
202
|
+
return { status: "ignored_group_requires_bot_open_id", tenantKey, chatId };
|
|
203
|
+
}
|
|
204
|
+
if (!mentionsBot(message.mentions, config.botOpenId)) {
|
|
205
|
+
return { status: "ignored_not_addressed", tenantKey, chatId };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const command = stripLarkMention(extractText(message.content));
|
|
209
|
+
const bindRequest = parseBindCommand(command);
|
|
210
|
+
if (bindRequest && !config.bindChannel) {
|
|
211
|
+
return { status: "ignored_bind_unavailable", tenantKey, chatId };
|
|
212
|
+
}
|
|
213
|
+
if (bindRequest && config.bindChannel) {
|
|
214
|
+
if (!bindRequest.ok) {
|
|
215
|
+
await config.reply?.({ messageId, text: BIND_USAGE });
|
|
216
|
+
return { status: "ignored_bind_usage", tenantKey, chatId };
|
|
217
|
+
}
|
|
218
|
+
await config.bindChannel({
|
|
219
|
+
tenantKey,
|
|
220
|
+
chatId,
|
|
221
|
+
repoProvider: bindRequest.repoProvider,
|
|
222
|
+
owner: bindRequest.owner,
|
|
223
|
+
repo: bindRequest.repo
|
|
224
|
+
});
|
|
225
|
+
await config.reply?.({
|
|
226
|
+
messageId,
|
|
227
|
+
text: `Connected this chat to Project Target ${bindRequest.repoProvider}:${bindRequest.owner}/${bindRequest.repo}. @-mention me with a task to start a run.`
|
|
228
|
+
});
|
|
229
|
+
return { status: "bound", tenantKey, chatId };
|
|
230
|
+
}
|
|
231
|
+
if (command.trim().length === 0) {
|
|
232
|
+
return { status: "ignored_empty_command", tenantKey, chatId };
|
|
233
|
+
}
|
|
234
|
+
let binding = await config.resolveChannelBinding({ tenantKey, chatId });
|
|
235
|
+
if (binding && config.defaultRepoBinding && config.bindChannel) {
|
|
236
|
+
if (shouldMigrateLegacyLocalBinding({ existing: binding, defaultBinding: config.defaultRepoBinding })) {
|
|
237
|
+
await config.bindChannel({
|
|
238
|
+
tenantKey,
|
|
239
|
+
chatId,
|
|
240
|
+
repoProvider: config.defaultRepoBinding.repoProvider,
|
|
241
|
+
owner: config.defaultRepoBinding.owner,
|
|
242
|
+
repo: config.defaultRepoBinding.repo
|
|
243
|
+
});
|
|
244
|
+
binding = bindingFromDefault({ tenantKey, chatId, binding: config.defaultRepoBinding });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!binding) {
|
|
248
|
+
if (config.defaultRepoBinding && config.bindChannel) {
|
|
249
|
+
await config.bindChannel({
|
|
250
|
+
tenantKey,
|
|
251
|
+
chatId,
|
|
252
|
+
repoProvider: config.defaultRepoBinding.repoProvider,
|
|
253
|
+
owner: config.defaultRepoBinding.owner,
|
|
254
|
+
repo: config.defaultRepoBinding.repo
|
|
255
|
+
});
|
|
256
|
+
binding = bindingFromDefault({ tenantKey, chatId, binding: config.defaultRepoBinding });
|
|
257
|
+
} else {
|
|
258
|
+
await config.reply?.({ messageId, text: UNBOUND_HINT });
|
|
259
|
+
return { status: "ignored_unbound_chat", tenantKey, chatId };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const parsedTime = data.create_time ? Number(data.create_time) : Number.NaN;
|
|
263
|
+
const eventTimeMs = Number.isFinite(parsedTime) ? parsedTime : config.now?.() ?? Date.now();
|
|
264
|
+
const event = normalizeLarkMessage({
|
|
265
|
+
tenantKey,
|
|
266
|
+
chatId,
|
|
267
|
+
chatType: message.chat_type ?? "group",
|
|
268
|
+
senderOpenId,
|
|
269
|
+
text: extractText(message.content),
|
|
270
|
+
messageId,
|
|
271
|
+
...message.root_id ? { rootId: message.root_id } : {},
|
|
272
|
+
eventId,
|
|
273
|
+
eventTimeMs,
|
|
274
|
+
agentId: config.agentId,
|
|
275
|
+
...config.botOpenId ? { botOpenId: config.botOpenId } : {},
|
|
276
|
+
...config.callbackUri ? { callbackUri: config.callbackUri } : {},
|
|
277
|
+
binding
|
|
278
|
+
});
|
|
279
|
+
if (!event) {
|
|
280
|
+
return { status: "ignored_empty_command" };
|
|
281
|
+
}
|
|
282
|
+
const result = await config.createRun(event);
|
|
283
|
+
if (result.outcome === "run_created") {
|
|
284
|
+
return { status: "created", runId: result.run.id, tenantKey, chatId };
|
|
285
|
+
}
|
|
286
|
+
if (result.outcome === "follow_up_queued") {
|
|
287
|
+
return {
|
|
288
|
+
status: "follow_up_queued",
|
|
289
|
+
followUpRequestId: result.followUpRequest.id,
|
|
290
|
+
...result.decision.activeRunId ? { runId: result.decision.activeRunId } : {},
|
|
291
|
+
reason: result.decision.reason,
|
|
292
|
+
tenantKey,
|
|
293
|
+
chatId
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
status: "needs_human_decision",
|
|
298
|
+
reason: result.decision.reason,
|
|
299
|
+
tenantKey,
|
|
300
|
+
chatId
|
|
301
|
+
};
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/ingress.ts
|
|
306
|
+
import { randomUUID } from "crypto";
|
|
307
|
+
import * as lark2 from "@larksuiteoapi/node-sdk";
|
|
308
|
+
import { createOpenTagClient } from "@opentag/client";
|
|
309
|
+
import { parseProjectTargetRef as parseProjectTargetRef2 } from "@opentag/core";
|
|
310
|
+
|
|
311
|
+
// src/outbound.ts
|
|
312
|
+
import * as lark from "@larksuiteoapi/node-sdk";
|
|
313
|
+
|
|
314
|
+
// src/render.ts
|
|
315
|
+
function nextActionSummary(result) {
|
|
316
|
+
if (!result.nextAction) return void 0;
|
|
317
|
+
if (typeof result.nextAction === "string") return result.nextAction;
|
|
318
|
+
return result.nextAction.summary;
|
|
319
|
+
}
|
|
320
|
+
function renderLarkAcknowledgement(runId) {
|
|
321
|
+
return `I picked this up: ${runId}`;
|
|
322
|
+
}
|
|
323
|
+
function renderLarkFinalResult(result) {
|
|
324
|
+
const lines = [`Finished with ${result.conclusion}.`, "", result.summary];
|
|
325
|
+
if (result.verification?.length) {
|
|
326
|
+
lines.push("", "Verification");
|
|
327
|
+
for (const check of result.verification) {
|
|
328
|
+
lines.push(`- ${check.command}: ${check.outcome}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const nextAction = nextActionSummary(result);
|
|
332
|
+
if (nextAction) {
|
|
333
|
+
lines.push("", `Next action: ${nextAction}`);
|
|
334
|
+
}
|
|
335
|
+
return lines.join("\n");
|
|
336
|
+
}
|
|
337
|
+
function createLarkTextMessageContent(text) {
|
|
338
|
+
return JSON.stringify({ text });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// src/outbound.ts
|
|
342
|
+
function createLarkReplyClient(input) {
|
|
343
|
+
return new lark.Client({
|
|
344
|
+
appId: input.appId,
|
|
345
|
+
appSecret: input.appSecret,
|
|
346
|
+
domain: input.domain === "feishu" ? lark.Domain.Feishu : lark.Domain.Lark
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async function replyLarkMessage(client, input) {
|
|
350
|
+
await client.im.message.reply({
|
|
351
|
+
path: { message_id: input.messageId },
|
|
352
|
+
data: { content: createLarkTextMessageContent(input.text), msg_type: "text", reply_in_thread: true }
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/ingress.ts
|
|
357
|
+
var DEFAULT_AGENT_ID = "opentag";
|
|
358
|
+
function defaultRepoBindingFromEnv(value) {
|
|
359
|
+
if (!value) return void 0;
|
|
360
|
+
try {
|
|
361
|
+
const ref = parseProjectTargetRef2(value);
|
|
362
|
+
return {
|
|
363
|
+
repoProvider: ref.provider,
|
|
364
|
+
owner: ref.owner,
|
|
365
|
+
repo: ref.repo
|
|
366
|
+
};
|
|
367
|
+
} catch {
|
|
368
|
+
throw new Error("OPENTAG_LARK_DEFAULT_REPO must be formatted as owner/repo or provider:owner/repo");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function domainFromEnv(value) {
|
|
372
|
+
const domain = value ?? "lark";
|
|
373
|
+
if (domain !== "lark" && domain !== "feishu") {
|
|
374
|
+
throw new Error("LARK_DOMAIN must be either lark or feishu");
|
|
375
|
+
}
|
|
376
|
+
return domain;
|
|
377
|
+
}
|
|
378
|
+
function larkIngressConfigFromEnv(env) {
|
|
379
|
+
const appId = env.LARK_APP_ID;
|
|
380
|
+
const appSecret = env.LARK_APP_SECRET;
|
|
381
|
+
const dispatcherUrl = env.OPENTAG_DISPATCHER_URL;
|
|
382
|
+
if (!appId || !appSecret) {
|
|
383
|
+
throw new Error("LARK_APP_ID and LARK_APP_SECRET are required");
|
|
384
|
+
}
|
|
385
|
+
if (!dispatcherUrl) {
|
|
386
|
+
throw new Error("OPENTAG_DISPATCHER_URL is required");
|
|
387
|
+
}
|
|
388
|
+
const defaultRepoBinding = defaultRepoBindingFromEnv(env.OPENTAG_LARK_DEFAULT_REPO);
|
|
389
|
+
return {
|
|
390
|
+
appId,
|
|
391
|
+
appSecret,
|
|
392
|
+
dispatcherUrl,
|
|
393
|
+
domain: domainFromEnv(env.LARK_DOMAIN),
|
|
394
|
+
agentId: env.OPENTAG_LARK_AGENT_ID ?? DEFAULT_AGENT_ID,
|
|
395
|
+
...env.OPENTAG_DISPATCHER_TOKEN ? { dispatcherToken: env.OPENTAG_DISPATCHER_TOKEN } : {},
|
|
396
|
+
...env.LARK_BOT_OPEN_ID ? { botOpenId: env.LARK_BOT_OPEN_ID } : {},
|
|
397
|
+
...defaultRepoBinding ? { defaultRepoBinding } : {}
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
function createDefaultWsClient(config) {
|
|
401
|
+
return new lark2.WSClient({
|
|
402
|
+
appId: config.appId,
|
|
403
|
+
appSecret: config.appSecret,
|
|
404
|
+
domain: config.domain === "feishu" ? lark2.Domain.Feishu : lark2.Domain.Lark
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
function createDefaultEventDispatcher(handler) {
|
|
408
|
+
return new lark2.EventDispatcher({}).register({
|
|
409
|
+
"im.message.receive_v1": (data) => handler(data)
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function logIgnored(outcome) {
|
|
413
|
+
if (outcome.status === "created" || outcome.status === "bound") return;
|
|
414
|
+
if (outcome.status === "follow_up_queued") {
|
|
415
|
+
console.log(
|
|
416
|
+
`[lark] queued follow-up${outcome.followUpRequestId ? ` follow_up_request_id=${outcome.followUpRequestId}` : ""}${outcome.runId ? ` active_run_id=${outcome.runId}` : ""}`
|
|
417
|
+
);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (outcome.status === "needs_human_decision") {
|
|
421
|
+
console.log(`[lark] needs human decision${outcome.reason ? `: ${outcome.reason}` : ""}`);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
if (outcome.status === "ignored_unbound_chat") {
|
|
425
|
+
console.log(
|
|
426
|
+
`[lark] ignored unbound chat - bind it: provider=lark accountId(tenant_key)=${outcome.tenantKey} conversationId(chat_id)=${outcome.chatId} (reply '/bind owner/repo' with a Project Target ref, or POST /v1/channel-bindings)`
|
|
427
|
+
);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
console.log(`[lark] ignored event: ${outcome.status}${outcome.chatId ? ` chat_id=${outcome.chatId}` : ""}`);
|
|
431
|
+
}
|
|
432
|
+
function startLarkIngress(config, dependencies = {}) {
|
|
433
|
+
const dispatcherClient = createOpenTagClient({
|
|
434
|
+
dispatcherUrl: config.dispatcherUrl,
|
|
435
|
+
...config.dispatcherToken ? { pairingToken: config.dispatcherToken } : {}
|
|
436
|
+
});
|
|
437
|
+
let replyClient;
|
|
438
|
+
const reply = dependencies.reply ?? ((input) => {
|
|
439
|
+
replyClient ??= createLarkReplyClient({ appId: config.appId, appSecret: config.appSecret, domain: config.domain });
|
|
440
|
+
return replyLarkMessage(replyClient, input);
|
|
441
|
+
});
|
|
442
|
+
const handler = createLarkMessageHandler({
|
|
443
|
+
agentId: config.agentId,
|
|
444
|
+
...config.botOpenId ? { botOpenId: config.botOpenId } : {},
|
|
445
|
+
...config.defaultRepoBinding ? { defaultRepoBinding: config.defaultRepoBinding } : {},
|
|
446
|
+
async resolveChannelBinding(input) {
|
|
447
|
+
try {
|
|
448
|
+
const { binding } = await dispatcherClient.getChannelBinding({
|
|
449
|
+
provider: "lark",
|
|
450
|
+
accountId: input.tenantKey,
|
|
451
|
+
conversationId: input.chatId
|
|
452
|
+
});
|
|
453
|
+
return {
|
|
454
|
+
tenantKey: binding.accountId,
|
|
455
|
+
chatId: binding.conversationId,
|
|
456
|
+
repoProvider: binding.repoProvider,
|
|
457
|
+
owner: binding.owner,
|
|
458
|
+
repo: binding.repo
|
|
459
|
+
};
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error instanceof Error && error.message.includes("channel_binding_not_found")) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
async bindChannel(input) {
|
|
468
|
+
await dispatcherClient.bindChannel({
|
|
469
|
+
provider: "lark",
|
|
470
|
+
accountId: input.tenantKey,
|
|
471
|
+
conversationId: input.chatId,
|
|
472
|
+
repoProvider: input.repoProvider,
|
|
473
|
+
owner: input.owner,
|
|
474
|
+
repo: input.repo
|
|
475
|
+
});
|
|
476
|
+
},
|
|
477
|
+
async reply(input) {
|
|
478
|
+
await reply(input);
|
|
479
|
+
},
|
|
480
|
+
async createRun(event) {
|
|
481
|
+
const runId = `run_${randomUUID()}`;
|
|
482
|
+
return dispatcherClient.createRun({ runId, event });
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
const eventDispatcher = (dependencies.createEventDispatcher ?? createDefaultEventDispatcher)(async (data) => {
|
|
486
|
+
try {
|
|
487
|
+
(dependencies.logIgnored ?? logIgnored)(await handler(data));
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error("[lark] failed to handle inbound message:", error);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
const wsClient = (dependencies.createWsClient ?? createDefaultWsClient)(config);
|
|
493
|
+
return {
|
|
494
|
+
startPromise: wsClient.start({ eventDispatcher }),
|
|
495
|
+
async close() {
|
|
496
|
+
await wsClient.close?.({ force: true });
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/registration.ts
|
|
502
|
+
import * as lark3 from "@larksuiteoapi/node-sdk";
|
|
503
|
+
var REGISTRATION_SOURCE = "opentag";
|
|
504
|
+
var BOT_INFO_RETRIES = 6;
|
|
505
|
+
var BOT_INFO_RETRY_DELAY_MS = 2e3;
|
|
506
|
+
function sleep(ms) {
|
|
507
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
508
|
+
}
|
|
509
|
+
function accountDomainFor(domain) {
|
|
510
|
+
return domain === "feishu" ? "accounts.feishu.cn" : "accounts.larksuite.com";
|
|
511
|
+
}
|
|
512
|
+
function sdkDomainFor(domain) {
|
|
513
|
+
return domain === "feishu" ? lark3.Domain.Feishu : lark3.Domain.Lark;
|
|
514
|
+
}
|
|
515
|
+
function registrationDomainFromUserInfo(requestedDomain, userInfo) {
|
|
516
|
+
if (userInfo?.tenant_brand === "lark") return "lark";
|
|
517
|
+
if (userInfo?.tenant_brand === "feishu") return "feishu";
|
|
518
|
+
return requestedDomain;
|
|
519
|
+
}
|
|
520
|
+
function createDefaultBotInfoClient(input) {
|
|
521
|
+
return new lark3.Client({
|
|
522
|
+
appId: input.appId,
|
|
523
|
+
appSecret: input.appSecret,
|
|
524
|
+
domain: sdkDomainFor(input.domain)
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function botInfoFromResponse(response) {
|
|
528
|
+
const value = response;
|
|
529
|
+
const bot = value.bot ?? value.data?.bot;
|
|
530
|
+
if (!bot?.open_id) {
|
|
531
|
+
return void 0;
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
botOpenId: bot.open_id,
|
|
535
|
+
botName: bot.app_name || bot.name || "OpenTag"
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
async function fetchBotIdentity(input, options) {
|
|
539
|
+
const client = options.createBotInfoClient(input);
|
|
540
|
+
let lastError;
|
|
541
|
+
for (let attempt = 1; attempt <= BOT_INFO_RETRIES; attempt += 1) {
|
|
542
|
+
try {
|
|
543
|
+
const botIdentity = botInfoFromResponse(
|
|
544
|
+
await client.request({
|
|
545
|
+
url: "/open-apis/bot/v3/info",
|
|
546
|
+
method: "GET"
|
|
547
|
+
})
|
|
548
|
+
);
|
|
549
|
+
if (botIdentity) {
|
|
550
|
+
return botIdentity;
|
|
551
|
+
}
|
|
552
|
+
lastError = new Error("bot/v3/info response did not include bot.open_id.");
|
|
553
|
+
} catch (error) {
|
|
554
|
+
lastError = error;
|
|
555
|
+
}
|
|
556
|
+
if (attempt < BOT_INFO_RETRIES) {
|
|
557
|
+
await options.sleep(BOT_INFO_RETRY_DELAY_MS);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const reason = lastError instanceof Error ? lastError.message : String(lastError);
|
|
561
|
+
options.onWarning?.(
|
|
562
|
+
`OpenTag could not fetch the Lark bot open_id automatically. Direct chat still works. For group chat, enter the bot open_id manually. Reason: ${reason}`
|
|
563
|
+
);
|
|
564
|
+
return {};
|
|
565
|
+
}
|
|
566
|
+
async function registerLarkPersonalAgent(input, dependencies = {}) {
|
|
567
|
+
const requestedDomain = input.domain ?? "lark";
|
|
568
|
+
let detectedDomain;
|
|
569
|
+
const registerApp2 = dependencies.registerApp ?? lark3.registerApp;
|
|
570
|
+
const registrationOptions = {
|
|
571
|
+
// The Personal Agent registration flow starts on Feishu and switches to Lark after scan when needed.
|
|
572
|
+
domain: accountDomainFor("feishu"),
|
|
573
|
+
larkDomain: accountDomainFor("lark"),
|
|
574
|
+
source: REGISTRATION_SOURCE,
|
|
575
|
+
createOnly: true,
|
|
576
|
+
appPreset: {
|
|
577
|
+
name: "OpenTag {user}",
|
|
578
|
+
desc: "Wake your local OpenTag agent from Lark."
|
|
579
|
+
},
|
|
580
|
+
addons: {
|
|
581
|
+
scopes: {
|
|
582
|
+
tenant: ["im:message:send_as_bot", "im:message.p2p_msg:readonly", "im:message.group_msg:readonly", "im:chat:readonly"]
|
|
583
|
+
},
|
|
584
|
+
events: {
|
|
585
|
+
items: {
|
|
586
|
+
tenant: ["im.message.receive_v1"]
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
onQRCodeReady: input.onQrCode,
|
|
591
|
+
onStatusChange(info) {
|
|
592
|
+
if (info.status === "domain_switched") {
|
|
593
|
+
detectedDomain = "lark";
|
|
594
|
+
}
|
|
595
|
+
input.onStatus?.(info);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const registration = await registerApp2({
|
|
599
|
+
...registrationOptions,
|
|
600
|
+
...input.signal ? { signal: input.signal } : {}
|
|
601
|
+
});
|
|
602
|
+
const domain = detectedDomain ?? registrationDomainFromUserInfo(requestedDomain, registration.user_info);
|
|
603
|
+
const botIdentity = await fetchBotIdentity(
|
|
604
|
+
{
|
|
605
|
+
appId: registration.client_id,
|
|
606
|
+
appSecret: registration.client_secret,
|
|
607
|
+
domain
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
createBotInfoClient: dependencies.createBotInfoClient ?? createDefaultBotInfoClient,
|
|
611
|
+
sleep: dependencies.sleep ?? sleep,
|
|
612
|
+
...input.onWarning ? { onWarning: input.onWarning } : {}
|
|
613
|
+
}
|
|
614
|
+
);
|
|
615
|
+
return {
|
|
616
|
+
appId: registration.client_id,
|
|
617
|
+
appSecret: registration.client_secret,
|
|
618
|
+
domain,
|
|
619
|
+
...registration.user_info?.open_id ? { operatorOpenId: registration.user_info.open_id } : {},
|
|
620
|
+
...botIdentity.botOpenId ? { botOpenId: botIdentity.botOpenId } : {},
|
|
621
|
+
...botIdentity.botName ? { botName: botIdentity.botName } : {}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
export {
|
|
625
|
+
DEFAULT_AGENT_ID,
|
|
626
|
+
createLarkMessageHandler,
|
|
627
|
+
createLarkReplyClient,
|
|
628
|
+
createLarkTextMessageContent,
|
|
629
|
+
encodeLarkThreadKey,
|
|
630
|
+
larkIngressConfigFromEnv,
|
|
631
|
+
normalizeLarkMessage,
|
|
632
|
+
parseLarkThreadKey,
|
|
633
|
+
registerLarkPersonalAgent,
|
|
634
|
+
renderLarkAcknowledgement,
|
|
635
|
+
renderLarkFinalResult,
|
|
636
|
+
replyLarkMessage,
|
|
637
|
+
startLarkIngress,
|
|
638
|
+
stripLarkMention
|
|
639
|
+
};
|
|
640
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/normalize.ts","../src/inbound.ts","../src/ingress.ts","../src/outbound.ts","../src/render.ts","../src/registration.ts"],"sourcesContent":["import { commandFromRawText, type ContextPointer, type OpenTagCommand, type OpenTagEvent, type PermissionGrant } from \"@opentag/core\";\n\nexport type LarkChannelBinding = {\n tenantKey: string;\n chatId: string;\n repoProvider?: string;\n owner: string;\n repo: string;\n};\n\nexport type LarkMessageInput = {\n tenantKey: string;\n chatId: string;\n chatType: string;\n senderOpenId: string;\n text: string;\n messageId: string;\n rootId?: string;\n eventId: string;\n eventTimeMs: number;\n agentId?: string;\n botOpenId?: string;\n callbackUri?: string;\n binding: LarkChannelBinding;\n};\n\n// Strip `@_user_N` mention placeholders (whole \\d+ index) and collapse whitespace.\nexport function stripLarkMention(text: string): string {\n return text\n .replace(/@_user_\\d+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\nexport function encodeLarkThreadKey(input: { tenantKey: string; chatId: string; messageId: string }): string {\n return `${input.tenantKey}|${input.chatId}|${input.messageId}`;\n}\n\nexport function parseLarkThreadKey(threadKey: string): { tenantKey: string; chatId: string; messageId: string } {\n const [tenantKey, chatId, messageId] = threadKey.split(\"|\");\n if (!tenantKey || !chatId || !messageId) {\n throw new Error(`Invalid Lark thread key: ${threadKey}`);\n }\n return { tenantKey, chatId, messageId };\n}\n\nfunction permissionsForIntent(intent: OpenTagCommand[\"intent\"]): PermissionGrant[] {\n const permissions: PermissionGrant[] = [\n {\n scope: \"chat:postMessage\",\n reason: \"reply in the originating Lark thread\"\n },\n {\n scope: \"runner:local\",\n reason: \"execute the run on a paired local daemon\"\n }\n ];\n\n if (intent === \"fix\" || intent === \"run\") {\n permissions.push(\n {\n scope: \"repo:read\",\n reason: \"inspect the repository in the paired local checkout\"\n },\n {\n scope: \"repo:write\",\n reason: \"commit code changes on an isolated run branch\"\n },\n {\n scope: \"pr:create\",\n reason: \"open a pull request for completed code changes\"\n }\n );\n }\n\n return permissions;\n}\n\nfunction contextPointersForCommand(command: OpenTagCommand): ContextPointer[] {\n const context: ContextPointer[] = [];\n\n for (const reference of command.parsed?.references ?? []) {\n if (reference.kind === \"url\") {\n context.push({\n kind: \"url\",\n uri: reference.uri,\n visibility: \"organization\",\n title: reference.title ?? \"Command URL reference\"\n });\n continue;\n }\n\n if (reference.kind === \"file\" || reference.kind === \"path\") {\n context.push({\n kind: \"file\",\n uri: reference.uri,\n ...(reference.line ? { line: reference.line } : {}),\n ...(reference.startLine ? { startLine: reference.startLine } : {}),\n ...(reference.endLine ? { endLine: reference.endLine } : {}),\n visibility: \"organization\",\n title: referenceTitle(reference)\n });\n }\n }\n\n return context;\n}\n\nfunction referenceTitle(reference: NonNullable<OpenTagCommand[\"parsed\"]>[\"references\"][number]): string {\n return reference.title ?? \"Command file reference\";\n}\n\nfunction commandMetadata(command: OpenTagCommand): Record<string, unknown> {\n if (!command.parsed) return {};\n return {\n commandParser: command.parsed.version,\n commandDiagnostics: command.parsed.diagnostics,\n ...(command.parsed.approval ? { approval: command.parsed.approval } : {}),\n ...(command.parsed.network ? { network: command.parsed.network } : {})\n };\n}\n\nexport function normalizeLarkMessage(input: LarkMessageInput): OpenTagEvent | null {\n const rawText = stripLarkMention(input.text);\n if (!rawText) return null;\n\n const command = commandFromRawText(rawText);\n const agentId = input.agentId ?? \"opentag\";\n\n return {\n id: `evt_lark_message_${input.eventId}`,\n source: \"lark\",\n sourceEventId: input.eventId,\n receivedAt: new Date(input.eventTimeMs).toISOString(),\n actor: {\n provider: \"lark\",\n providerUserId: input.senderOpenId,\n handle: input.senderOpenId,\n organizationId: input.tenantKey\n },\n target: {\n mention: input.botOpenId ? `@${input.botOpenId}` : \"@app\",\n agentId,\n ...(command.parsed?.executorHint ? { executorHint: command.parsed.executorHint } : {})\n },\n command,\n context: [\n {\n provider: \"lark\",\n kind: \"message\",\n uri: `lark://tenant/${input.tenantKey}/chat/${input.chatId}/message/${input.messageId}`,\n visibility: \"organization\",\n title: \"Lark message\"\n },\n {\n kind: \"text\",\n uri: input.text,\n visibility: \"organization\",\n title: \"Lark message text\"\n },\n ...contextPointersForCommand(command)\n ],\n permissions: permissionsForIntent(command.intent),\n callback: {\n provider: \"lark\",\n uri: input.callbackUri ?? \"lark://im/v1/messages\",\n threadKey: encodeLarkThreadKey({\n tenantKey: input.tenantKey,\n chatId: input.chatId,\n messageId: input.messageId\n })\n },\n metadata: {\n tenantKey: input.tenantKey,\n chatId: input.chatId,\n messageId: input.messageId,\n chatType: input.chatType,\n ...(input.rootId ? { rootId: input.rootId } : {}),\n ...(input.botOpenId ? { larkBotOpenId: input.botOpenId } : {}),\n ...commandMetadata(command),\n repoProvider: input.binding.repoProvider ?? \"github\",\n owner: input.binding.owner,\n repo: input.binding.repo\n }\n };\n}\n","import { parseProjectTargetRef, type OpenTagEvent } from \"@opentag/core\";\nimport type { CreateRunResult } from \"@opentag/client\";\nimport { type LarkChannelBinding, normalizeLarkMessage, stripLarkMention } from \"./normalize.js\";\n\nexport type LarkMention = { key?: string; id?: { open_id?: string }; name?: string };\n\n// Flattened `im.message.receive_v1` payload as delivered by the SDK EventDispatcher (header fields + message/sender on the top level). All optional (external input).\nexport type LarkInboundMessageEvent = {\n event_id?: string;\n event_type?: string;\n create_time?: string;\n tenant_key?: string;\n app_id?: string;\n sender?: {\n sender_id?: { open_id?: string; user_id?: string; union_id?: string };\n sender_type?: string;\n tenant_key?: string;\n };\n message?: {\n message_id?: string;\n root_id?: string;\n parent_id?: string;\n chat_id?: string;\n chat_type?: string;\n message_type?: string;\n content?: string;\n mentions?: LarkMention[];\n };\n};\n\nexport type LarkMessageHandlerConfig = {\n agentId: string;\n botOpenId?: string;\n callbackUri?: string;\n defaultRepoBinding?: { repoProvider: string; owner: string; repo: string };\n resolveChannelBinding(input: { tenantKey: string; chatId: string }): Promise<LarkChannelBinding | null>;\n createRun(event: OpenTagEvent): Promise<CreateRunResult>;\n // Self-service binding from within Lark (`/bind owner/repo`); optional so tests can omit it.\n bindChannel?(input: { tenantKey: string; chatId: string; repoProvider: string; owner: string; repo: string }): Promise<void>;\n // Reply into the originating thread (onboarding hints, bind confirmations); optional.\n reply?(input: { messageId: string; text: string }): Promise<void>;\n now?(): number;\n};\n\nexport type LarkMessageHandlerOutcome = {\n status:\n | \"created\"\n | \"bound\"\n | \"ignored_non_text\"\n | \"ignored_invalid_payload\"\n | \"ignored_group_requires_bot_open_id\"\n | \"ignored_not_addressed\"\n | \"ignored_bind_usage\"\n | \"ignored_bind_unavailable\"\n | \"ignored_unbound_chat\"\n | \"ignored_empty_command\"\n | \"follow_up_queued\"\n | \"needs_human_decision\";\n runId?: string;\n followUpRequestId?: string;\n reason?: string;\n tenantKey?: string;\n chatId?: string;\n};\n\nconst BIND_USAGE =\n \"Usage: /bind <owner>/<repo> — e.g. /bind amplifthq/opentag (or /bind github:amplifthq/opentag).\";\nconst UNBOUND_HINT =\n \"This chat isn't connected to a Project Target yet. @-mention me with `/bind <owner>/<repo>` to connect it — e.g. /bind amplifthq/opentag.\";\n\ntype DefaultRepoBinding = NonNullable<LarkMessageHandlerConfig[\"defaultRepoBinding\"]>;\n\nfunction bindingFromDefault(input: { tenantKey: string; chatId: string; binding: DefaultRepoBinding }): LarkChannelBinding {\n return {\n tenantKey: input.tenantKey,\n chatId: input.chatId,\n repoProvider: input.binding.repoProvider,\n owner: input.binding.owner,\n repo: input.binding.repo\n };\n}\n\nfunction shouldMigrateLegacyLocalBinding(input: {\n existing: LarkChannelBinding;\n defaultBinding: DefaultRepoBinding;\n}): boolean {\n return (\n input.defaultBinding.repoProvider === \"local\" &&\n input.defaultBinding.owner.startsWith(\"path_\") &&\n input.existing.repoProvider === \"local\" &&\n input.existing.owner === \"local\" &&\n input.existing.repo === input.defaultBinding.repo\n );\n}\n\nfunction extractText(content: string | undefined): string {\n if (!content) return \"\";\n try {\n const parsed = JSON.parse(content) as { text?: unknown };\n return typeof parsed.text === \"string\" ? parsed.text : \"\";\n } catch {\n return \"\";\n }\n}\n\nfunction mentionsBot(mentions: LarkMention[] | undefined, botOpenId: string): boolean {\n return (mentions ?? []).some((mention) => mention.id?.open_id === botOpenId);\n}\n\n// Parse `/bind owner/repo` (or `/bind provider:owner/repo`). null = not a bind command; {ok:false} = malformed.\nfunction parseBindCommand(\n command: string\n): { ok: true; repoProvider: string; owner: string; repo: string } | { ok: false } | null {\n if (!/^\\/bind(\\s|$)/.test(command)) return null;\n const match = command.match(/^\\/bind\\s+(\\S+)\\s*$/);\n if (!match) return { ok: false };\n try {\n const ref = parseProjectTargetRef(match[1] as string);\n return { ok: true, repoProvider: ref.provider, owner: ref.owner, repo: ref.repo };\n } catch {\n return { ok: false };\n }\n}\n\n// Handle one inbound Lark message: group messages must @-mention the bot, then handle /bind, resolve binding, normalize, create a run.\nexport function createLarkMessageHandler(config: LarkMessageHandlerConfig) {\n return async function handleLarkMessage(data: LarkInboundMessageEvent): Promise<LarkMessageHandlerOutcome> {\n const message = data.message;\n if (!message || message.message_type !== \"text\") {\n return { status: \"ignored_non_text\" };\n }\n\n const tenantKey = data.tenant_key ?? data.sender?.tenant_key;\n const chatId = message.chat_id;\n const messageId = message.message_id;\n const eventId = data.event_id;\n const senderOpenId = data.sender?.sender_id?.open_id;\n if (!tenantKey || !chatId || !messageId || !eventId || !senderOpenId) {\n return { status: \"ignored_invalid_payload\" };\n }\n\n // Group messages must @-mention the bot before triggering a (write-capable) run; p2p is exempt.\n const isDirect = message.chat_type === \"p2p\";\n if (!isDirect) {\n if (!config.botOpenId) {\n return { status: \"ignored_group_requires_bot_open_id\", tenantKey, chatId };\n }\n if (!mentionsBot(message.mentions, config.botOpenId)) {\n return { status: \"ignored_not_addressed\", tenantKey, chatId };\n }\n }\n\n const command = stripLarkMention(extractText(message.content));\n\n // Self-service binding: connect this chat to a Project Target without leaving Lark.\n const bindRequest = parseBindCommand(command);\n if (bindRequest && !config.bindChannel) {\n return { status: \"ignored_bind_unavailable\", tenantKey, chatId };\n }\n if (bindRequest && config.bindChannel) {\n if (!bindRequest.ok) {\n await config.reply?.({ messageId, text: BIND_USAGE });\n return { status: \"ignored_bind_usage\", tenantKey, chatId };\n }\n await config.bindChannel({\n tenantKey,\n chatId,\n repoProvider: bindRequest.repoProvider,\n owner: bindRequest.owner,\n repo: bindRequest.repo\n });\n await config.reply?.({\n messageId,\n text: `Connected this chat to Project Target ${bindRequest.repoProvider}:${bindRequest.owner}/${bindRequest.repo}. @-mention me with a task to start a run.`\n });\n return { status: \"bound\", tenantKey, chatId };\n }\n\n if (command.trim().length === 0) {\n return { status: \"ignored_empty_command\", tenantKey, chatId };\n }\n\n let binding = await config.resolveChannelBinding({ tenantKey, chatId });\n if (binding && config.defaultRepoBinding && config.bindChannel) {\n if (shouldMigrateLegacyLocalBinding({ existing: binding, defaultBinding: config.defaultRepoBinding })) {\n await config.bindChannel({\n tenantKey,\n chatId,\n repoProvider: config.defaultRepoBinding.repoProvider,\n owner: config.defaultRepoBinding.owner,\n repo: config.defaultRepoBinding.repo\n });\n binding = bindingFromDefault({ tenantKey, chatId, binding: config.defaultRepoBinding });\n }\n }\n if (!binding) {\n if (config.defaultRepoBinding && config.bindChannel) {\n await config.bindChannel({\n tenantKey,\n chatId,\n repoProvider: config.defaultRepoBinding.repoProvider,\n owner: config.defaultRepoBinding.owner,\n repo: config.defaultRepoBinding.repo\n });\n binding = bindingFromDefault({ tenantKey, chatId, binding: config.defaultRepoBinding });\n } else {\n await config.reply?.({ messageId, text: UNBOUND_HINT });\n return { status: \"ignored_unbound_chat\", tenantKey, chatId };\n }\n }\n\n const parsedTime = data.create_time ? Number(data.create_time) : Number.NaN;\n const eventTimeMs = Number.isFinite(parsedTime) ? parsedTime : (config.now?.() ?? Date.now());\n\n const event = normalizeLarkMessage({\n tenantKey,\n chatId,\n chatType: message.chat_type ?? \"group\",\n senderOpenId,\n text: extractText(message.content),\n messageId,\n ...(message.root_id ? { rootId: message.root_id } : {}),\n eventId,\n eventTimeMs,\n agentId: config.agentId,\n ...(config.botOpenId ? { botOpenId: config.botOpenId } : {}),\n ...(config.callbackUri ? { callbackUri: config.callbackUri } : {}),\n binding\n });\n if (!event) {\n return { status: \"ignored_empty_command\" };\n }\n\n const result = await config.createRun(event);\n if (result.outcome === \"run_created\") {\n return { status: \"created\", runId: result.run.id, tenantKey, chatId };\n }\n if (result.outcome === \"follow_up_queued\") {\n return {\n status: \"follow_up_queued\",\n followUpRequestId: result.followUpRequest.id,\n ...(result.decision.activeRunId ? { runId: result.decision.activeRunId } : {}),\n reason: result.decision.reason,\n tenantKey,\n chatId\n };\n }\n return {\n status: \"needs_human_decision\",\n reason: result.decision.reason,\n tenantKey,\n chatId\n };\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport * as lark from \"@larksuiteoapi/node-sdk\";\nimport { createOpenTagClient } from \"@opentag/client\";\nimport { parseProjectTargetRef, type OpenTagEvent } from \"@opentag/core\";\nimport { createLarkReplyClient, replyLarkMessage } from \"./outbound.js\";\nimport { createLarkMessageHandler, type LarkInboundMessageEvent, type LarkMessageHandlerOutcome } from \"./inbound.js\";\n\nexport const DEFAULT_AGENT_ID = \"opentag\";\n\nexport type LarkIngressConfig = {\n appId: string;\n appSecret: string;\n dispatcherUrl: string;\n dispatcherToken?: string;\n domain: \"lark\" | \"feishu\";\n agentId: string;\n botOpenId?: string;\n defaultRepoBinding?: { repoProvider: string; owner: string; repo: string };\n};\n\nexport type LarkWsClient = {\n start(input: { eventDispatcher: unknown }): Promise<void>;\n close?(input?: { force?: boolean }): void | Promise<void>;\n};\n\nexport type LarkIngressDependencies = {\n createWsClient?(config: LarkIngressConfig): LarkWsClient;\n createEventDispatcher?(handler: (data: LarkInboundMessageEvent) => Promise<void>): unknown;\n reply?(input: { messageId: string; text: string }): Promise<void>;\n logIgnored?(outcome: LarkMessageHandlerOutcome): void;\n};\n\nexport type LarkIngressHandle = {\n startPromise: Promise<void>;\n close(): Promise<void>;\n};\n\nfunction defaultRepoBindingFromEnv(value: string | undefined): LarkIngressConfig[\"defaultRepoBinding\"] {\n if (!value) return undefined;\n try {\n const ref = parseProjectTargetRef(value);\n return {\n repoProvider: ref.provider,\n owner: ref.owner,\n repo: ref.repo\n };\n } catch {\n throw new Error(\"OPENTAG_LARK_DEFAULT_REPO must be formatted as owner/repo or provider:owner/repo\");\n }\n}\n\nfunction domainFromEnv(value: string | undefined): LarkIngressConfig[\"domain\"] {\n const domain = value ?? \"lark\";\n if (domain !== \"lark\" && domain !== \"feishu\") {\n throw new Error(\"LARK_DOMAIN must be either lark or feishu\");\n }\n return domain;\n}\n\nexport function larkIngressConfigFromEnv(env: NodeJS.ProcessEnv): LarkIngressConfig {\n const appId = env.LARK_APP_ID;\n const appSecret = env.LARK_APP_SECRET;\n const dispatcherUrl = env.OPENTAG_DISPATCHER_URL;\n if (!appId || !appSecret) {\n throw new Error(\"LARK_APP_ID and LARK_APP_SECRET are required\");\n }\n if (!dispatcherUrl) {\n throw new Error(\"OPENTAG_DISPATCHER_URL is required\");\n }\n\n const defaultRepoBinding = defaultRepoBindingFromEnv(env.OPENTAG_LARK_DEFAULT_REPO);\n\n return {\n appId,\n appSecret,\n dispatcherUrl,\n domain: domainFromEnv(env.LARK_DOMAIN),\n agentId: env.OPENTAG_LARK_AGENT_ID ?? DEFAULT_AGENT_ID,\n ...(env.OPENTAG_DISPATCHER_TOKEN ? { dispatcherToken: env.OPENTAG_DISPATCHER_TOKEN } : {}),\n ...(env.LARK_BOT_OPEN_ID ? { botOpenId: env.LARK_BOT_OPEN_ID } : {}),\n ...(defaultRepoBinding ? { defaultRepoBinding } : {})\n };\n}\n\nfunction createDefaultWsClient(config: LarkIngressConfig): LarkWsClient {\n return new lark.WSClient({\n appId: config.appId,\n appSecret: config.appSecret,\n domain: config.domain === \"feishu\" ? lark.Domain.Feishu : lark.Domain.Lark\n });\n}\n\nfunction createDefaultEventDispatcher(handler: (data: LarkInboundMessageEvent) => Promise<void>): unknown {\n return new lark.EventDispatcher({}).register({\n \"im.message.receive_v1\": (data) => handler(data as unknown as LarkInboundMessageEvent)\n });\n}\n\nfunction logIgnored(outcome: LarkMessageHandlerOutcome): void {\n if (outcome.status === \"created\" || outcome.status === \"bound\") return;\n if (outcome.status === \"follow_up_queued\") {\n console.log(\n `[lark] queued follow-up${outcome.followUpRequestId ? ` follow_up_request_id=${outcome.followUpRequestId}` : \"\"}${outcome.runId ? ` active_run_id=${outcome.runId}` : \"\"}`\n );\n return;\n }\n if (outcome.status === \"needs_human_decision\") {\n console.log(`[lark] needs human decision${outcome.reason ? `: ${outcome.reason}` : \"\"}`);\n return;\n }\n if (outcome.status === \"ignored_unbound_chat\") {\n console.log(\n `[lark] ignored unbound chat - bind it: provider=lark accountId(tenant_key)=${outcome.tenantKey} conversationId(chat_id)=${outcome.chatId} (reply '/bind owner/repo' with a Project Target ref, or POST /v1/channel-bindings)`\n );\n return;\n }\n console.log(`[lark] ignored event: ${outcome.status}${outcome.chatId ? ` chat_id=${outcome.chatId}` : \"\"}`);\n}\n\nexport function startLarkIngress(config: LarkIngressConfig, dependencies: LarkIngressDependencies = {}): LarkIngressHandle {\n const dispatcherClient = createOpenTagClient({\n dispatcherUrl: config.dispatcherUrl,\n ...(config.dispatcherToken ? { pairingToken: config.dispatcherToken } : {})\n });\n let replyClient: ReturnType<typeof createLarkReplyClient> | undefined;\n const reply =\n dependencies.reply ??\n ((input: { messageId: string; text: string }) => {\n replyClient ??= createLarkReplyClient({ appId: config.appId, appSecret: config.appSecret, domain: config.domain });\n return replyLarkMessage(replyClient, input);\n });\n\n const handler = createLarkMessageHandler({\n agentId: config.agentId,\n ...(config.botOpenId ? { botOpenId: config.botOpenId } : {}),\n ...(config.defaultRepoBinding ? { defaultRepoBinding: config.defaultRepoBinding } : {}),\n async resolveChannelBinding(input) {\n try {\n const { binding } = await dispatcherClient.getChannelBinding({\n provider: \"lark\",\n accountId: input.tenantKey,\n conversationId: input.chatId\n });\n return {\n tenantKey: binding.accountId,\n chatId: binding.conversationId,\n repoProvider: binding.repoProvider,\n owner: binding.owner,\n repo: binding.repo\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"channel_binding_not_found\")) {\n return null;\n }\n throw error;\n }\n },\n async bindChannel(input) {\n await dispatcherClient.bindChannel({\n provider: \"lark\",\n accountId: input.tenantKey,\n conversationId: input.chatId,\n repoProvider: input.repoProvider,\n owner: input.owner,\n repo: input.repo\n });\n },\n async reply(input) {\n await reply(input);\n },\n async createRun(event: OpenTagEvent) {\n const runId = `run_${randomUUID()}`;\n return dispatcherClient.createRun({ runId, event });\n }\n });\n\n const eventDispatcher = (dependencies.createEventDispatcher ?? createDefaultEventDispatcher)(async (data) => {\n try {\n (dependencies.logIgnored ?? logIgnored)(await handler(data));\n } catch (error) {\n console.error(\"[lark] failed to handle inbound message:\", error);\n }\n });\n const wsClient = (dependencies.createWsClient ?? createDefaultWsClient)(config);\n\n return {\n startPromise: wsClient.start({ eventDispatcher }),\n async close() {\n await wsClient.close?.({ force: true });\n }\n };\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\nimport { createLarkTextMessageContent } from \"./render.js\";\n\n// Minimal client surface OpenTag uses; lark.Client satisfies it structurally.\nexport type LarkReplyClient = {\n im: {\n message: {\n reply(payload: {\n path: { message_id: string };\n data: { content: string; msg_type: string; reply_in_thread?: boolean; uuid?: string };\n }): Promise<unknown>;\n };\n };\n};\n\nexport function createLarkReplyClient(input: { appId: string; appSecret: string; domain?: \"lark\" | \"feishu\" }): LarkReplyClient {\n return new lark.Client({\n appId: input.appId,\n appSecret: input.appSecret,\n domain: input.domain === \"feishu\" ? lark.Domain.Feishu : lark.Domain.Lark\n });\n}\n\nexport async function replyLarkMessage(client: LarkReplyClient, input: { messageId: string; text: string }): Promise<void> {\n await client.im.message.reply({\n path: { message_id: input.messageId },\n data: { content: createLarkTextMessageContent(input.text), msg_type: \"text\", reply_in_thread: true }\n });\n}\n","import type { OpenTagRunResult } from \"@opentag/core\";\n\nfunction nextActionSummary(result: OpenTagRunResult): string | undefined {\n if (!result.nextAction) return undefined;\n if (typeof result.nextAction === \"string\") return result.nextAction;\n return result.nextAction.summary;\n}\n\nexport function renderLarkAcknowledgement(runId: string): string {\n return `I picked this up: ${runId}`;\n}\n\nexport function renderLarkFinalResult(result: OpenTagRunResult): string {\n const lines = [`Finished with ${result.conclusion}.`, \"\", result.summary];\n\n if (result.verification?.length) {\n lines.push(\"\", \"Verification\");\n for (const check of result.verification) {\n lines.push(`- ${check.command}: ${check.outcome}`);\n }\n }\n\n const nextAction = nextActionSummary(result);\n if (nextAction) {\n lines.push(\"\", `Next action: ${nextAction}`);\n }\n\n return lines.join(\"\\n\");\n}\n\n// Lark text message content is a JSON-encoded `{ \"text\": \"...\" }` string.\nexport function createLarkTextMessageContent(text: string): string {\n return JSON.stringify({ text });\n}\n","import * as lark from \"@larksuiteoapi/node-sdk\";\n\nconst REGISTRATION_SOURCE = \"opentag\";\nconst BOT_INFO_RETRIES = 6;\nconst BOT_INFO_RETRY_DELAY_MS = 2000;\n\nexport type LarkDomain = \"lark\" | \"feishu\";\n\nexport type LarkRegistrationQrCodeInfo = {\n url: string;\n expireIn: number;\n};\n\nexport type LarkRegistrationStatusInfo = {\n status: string;\n interval?: number;\n};\n\nexport type RegisteredLarkPersonalAgent = {\n appId: string;\n appSecret: string;\n domain: LarkDomain;\n operatorOpenId?: string;\n botOpenId?: string;\n botName?: string;\n};\n\ntype LarkRegisterAppResult = {\n client_id: string;\n client_secret: string;\n user_info?: {\n open_id?: string;\n tenant_brand?: \"feishu\" | \"lark\";\n };\n};\n\ntype LarkRegisterApp = (options: {\n domain?: string;\n larkDomain?: string;\n source?: string;\n signal?: AbortSignal;\n onQRCodeReady: (info: LarkRegistrationQrCodeInfo) => void;\n onStatusChange?: (info: LarkRegistrationStatusInfo) => void;\n appPreset?: { name?: string; desc?: string };\n addons?: {\n scopes?: { tenant?: string[] };\n events?: { items?: { tenant?: string[] } };\n };\n createOnly?: boolean;\n}) => Promise<LarkRegisterAppResult>;\n\ntype BotInfoClient = {\n request(input: { url: string; method: \"GET\" }): Promise<unknown>;\n};\n\nexport type LarkPersonalAgentRegistrationDependencies = {\n registerApp?: LarkRegisterApp;\n createBotInfoClient?(input: { appId: string; appSecret: string; domain: LarkDomain }): BotInfoClient;\n sleep?(ms: number): Promise<void>;\n};\n\nexport type RegisterLarkPersonalAgentInput = {\n domain?: LarkDomain;\n signal?: AbortSignal;\n onQrCode(info: LarkRegistrationQrCodeInfo): void;\n onStatus?(info: LarkRegistrationStatusInfo): void;\n onWarning?(message: string): void;\n};\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nfunction accountDomainFor(domain: LarkDomain): string {\n return domain === \"feishu\" ? \"accounts.feishu.cn\" : \"accounts.larksuite.com\";\n}\n\nfunction sdkDomainFor(domain: LarkDomain): lark.Domain {\n return domain === \"feishu\" ? lark.Domain.Feishu : lark.Domain.Lark;\n}\n\nfunction registrationDomainFromUserInfo(requestedDomain: LarkDomain, userInfo: LarkRegisterAppResult[\"user_info\"]): LarkDomain {\n if (userInfo?.tenant_brand === \"lark\") return \"lark\";\n if (userInfo?.tenant_brand === \"feishu\") return \"feishu\";\n return requestedDomain;\n}\n\nfunction createDefaultBotInfoClient(input: { appId: string; appSecret: string; domain: LarkDomain }): BotInfoClient {\n return new lark.Client({\n appId: input.appId,\n appSecret: input.appSecret,\n domain: sdkDomainFor(input.domain)\n });\n}\n\nfunction botInfoFromResponse(response: unknown): { botOpenId: string; botName: string } | undefined {\n const value = response as { bot?: { open_id?: string; app_name?: string; name?: string }; data?: { bot?: { open_id?: string; app_name?: string; name?: string } } };\n const bot = value.bot ?? value.data?.bot;\n if (!bot?.open_id) {\n return undefined;\n }\n return {\n botOpenId: bot.open_id,\n botName: bot.app_name || bot.name || \"OpenTag\"\n };\n}\n\nasync function fetchBotIdentity(\n input: { appId: string; appSecret: string; domain: LarkDomain },\n options: {\n createBotInfoClient: NonNullable<LarkPersonalAgentRegistrationDependencies[\"createBotInfoClient\"]>;\n sleep: (ms: number) => Promise<void>;\n onWarning?: (message: string) => void;\n }\n): Promise<Pick<RegisteredLarkPersonalAgent, \"botOpenId\" | \"botName\">> {\n const client = options.createBotInfoClient(input);\n let lastError: unknown;\n\n for (let attempt = 1; attempt <= BOT_INFO_RETRIES; attempt += 1) {\n try {\n const botIdentity = botInfoFromResponse(\n await client.request({\n url: \"/open-apis/bot/v3/info\",\n method: \"GET\"\n })\n );\n if (botIdentity) {\n return botIdentity;\n }\n lastError = new Error(\"bot/v3/info response did not include bot.open_id.\");\n } catch (error) {\n lastError = error;\n }\n\n if (attempt < BOT_INFO_RETRIES) {\n await options.sleep(BOT_INFO_RETRY_DELAY_MS);\n }\n }\n\n const reason = lastError instanceof Error ? lastError.message : String(lastError);\n options.onWarning?.(\n `OpenTag could not fetch the Lark bot open_id automatically. Direct chat still works. For group chat, enter the bot open_id manually. Reason: ${reason}`\n );\n return {};\n}\n\nexport async function registerLarkPersonalAgent(\n input: RegisterLarkPersonalAgentInput,\n dependencies: LarkPersonalAgentRegistrationDependencies = {}\n): Promise<RegisteredLarkPersonalAgent> {\n const requestedDomain = input.domain ?? \"lark\";\n let detectedDomain: LarkDomain | undefined;\n\n const registerApp = dependencies.registerApp ?? lark.registerApp;\n const registrationOptions = {\n // The Personal Agent registration flow starts on Feishu and switches to Lark after scan when needed.\n domain: accountDomainFor(\"feishu\"),\n larkDomain: accountDomainFor(\"lark\"),\n source: REGISTRATION_SOURCE,\n createOnly: true,\n appPreset: {\n name: \"OpenTag {user}\",\n desc: \"Wake your local OpenTag agent from Lark.\"\n },\n addons: {\n scopes: {\n tenant: [\"im:message:send_as_bot\", \"im:message.p2p_msg:readonly\", \"im:message.group_msg:readonly\", \"im:chat:readonly\"]\n },\n events: {\n items: {\n tenant: [\"im.message.receive_v1\"]\n }\n }\n },\n onQRCodeReady: input.onQrCode,\n onStatusChange(info) {\n if (info.status === \"domain_switched\") {\n detectedDomain = \"lark\";\n }\n input.onStatus?.(info);\n }\n } satisfies Parameters<LarkRegisterApp>[0];\n\n const registration = await registerApp({\n ...registrationOptions,\n ...(input.signal ? { signal: input.signal } : {})\n });\n\n const domain = detectedDomain ?? registrationDomainFromUserInfo(requestedDomain, registration.user_info);\n const botIdentity = await fetchBotIdentity(\n {\n appId: registration.client_id,\n appSecret: registration.client_secret,\n domain\n },\n {\n createBotInfoClient: dependencies.createBotInfoClient ?? createDefaultBotInfoClient,\n sleep: dependencies.sleep ?? sleep,\n ...(input.onWarning ? { onWarning: input.onWarning } : {})\n }\n );\n\n return {\n appId: registration.client_id,\n appSecret: registration.client_secret,\n domain,\n ...(registration.user_info?.open_id ? { operatorOpenId: registration.user_info.open_id } : {}),\n ...(botIdentity.botOpenId ? { botOpenId: botIdentity.botOpenId } : {}),\n ...(botIdentity.botName ? { botName: botIdentity.botName } : {})\n };\n}\n"],"mappings":"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","names":["lark","parseProjectTargetRef","parseProjectTargetRef","lark","registerApp"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type LarkInboundMessageEvent, type LarkMessageHandlerOutcome } from "./inbound.js";
|
|
2
|
+
export declare const DEFAULT_AGENT_ID = "opentag";
|
|
3
|
+
export type LarkIngressConfig = {
|
|
4
|
+
appId: string;
|
|
5
|
+
appSecret: string;
|
|
6
|
+
dispatcherUrl: string;
|
|
7
|
+
dispatcherToken?: string;
|
|
8
|
+
domain: "lark" | "feishu";
|
|
9
|
+
agentId: string;
|
|
10
|
+
botOpenId?: string;
|
|
11
|
+
defaultRepoBinding?: {
|
|
12
|
+
repoProvider: string;
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export type LarkWsClient = {
|
|
18
|
+
start(input: {
|
|
19
|
+
eventDispatcher: unknown;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
close?(input?: {
|
|
22
|
+
force?: boolean;
|
|
23
|
+
}): void | Promise<void>;
|
|
24
|
+
};
|
|
25
|
+
export type LarkIngressDependencies = {
|
|
26
|
+
createWsClient?(config: LarkIngressConfig): LarkWsClient;
|
|
27
|
+
createEventDispatcher?(handler: (data: LarkInboundMessageEvent) => Promise<void>): unknown;
|
|
28
|
+
reply?(input: {
|
|
29
|
+
messageId: string;
|
|
30
|
+
text: string;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
logIgnored?(outcome: LarkMessageHandlerOutcome): void;
|
|
33
|
+
};
|
|
34
|
+
export type LarkIngressHandle = {
|
|
35
|
+
startPromise: Promise<void>;
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
};
|
|
38
|
+
export declare function larkIngressConfigFromEnv(env: NodeJS.ProcessEnv): LarkIngressConfig;
|
|
39
|
+
export declare function startLarkIngress(config: LarkIngressConfig, dependencies?: LarkIngressDependencies): LarkIngressHandle;
|
|
40
|
+
//# sourceMappingURL=ingress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingress.d.ts","sourceRoot":"","sources":["../src/ingress.ts"],"names":[],"mappings":"AAKA,OAAO,EAA4B,KAAK,uBAAuB,EAAE,KAAK,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAEtH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,QAAQ,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5E,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,CAAC,KAAK,EAAE;QAAE,eAAe,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,cAAc,CAAC,CAAC,MAAM,EAAE,iBAAiB,GAAG,YAAY,CAAC;IACzD,qBAAqB,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;IAC3F,KAAK,CAAC,CAAC,KAAK,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,UAAU,CAAC,CAAC,OAAO,EAAE,yBAAyB,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAwBF,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,iBAAiB,CAuBlF;AAqCD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,EAAE,YAAY,GAAE,uBAA4B,GAAG,iBAAiB,CAwEzH"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type OpenTagEvent } from "@opentag/core";
|
|
2
|
+
export type LarkChannelBinding = {
|
|
3
|
+
tenantKey: string;
|
|
4
|
+
chatId: string;
|
|
5
|
+
repoProvider?: string;
|
|
6
|
+
owner: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
};
|
|
9
|
+
export type LarkMessageInput = {
|
|
10
|
+
tenantKey: string;
|
|
11
|
+
chatId: string;
|
|
12
|
+
chatType: string;
|
|
13
|
+
senderOpenId: string;
|
|
14
|
+
text: string;
|
|
15
|
+
messageId: string;
|
|
16
|
+
rootId?: string;
|
|
17
|
+
eventId: string;
|
|
18
|
+
eventTimeMs: number;
|
|
19
|
+
agentId?: string;
|
|
20
|
+
botOpenId?: string;
|
|
21
|
+
callbackUri?: string;
|
|
22
|
+
binding: LarkChannelBinding;
|
|
23
|
+
};
|
|
24
|
+
export declare function stripLarkMention(text: string): string;
|
|
25
|
+
export declare function encodeLarkThreadKey(input: {
|
|
26
|
+
tenantKey: string;
|
|
27
|
+
chatId: string;
|
|
28
|
+
messageId: string;
|
|
29
|
+
}): string;
|
|
30
|
+
export declare function parseLarkThreadKey(threadKey: string): {
|
|
31
|
+
tenantKey: string;
|
|
32
|
+
chatId: string;
|
|
33
|
+
messageId: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function normalizeLarkMessage(input: LarkMessageInput): OpenTagEvent | null;
|
|
36
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgE,KAAK,YAAY,EAAwB,MAAM,eAAe,CAAC;AAEtI,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAGF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKrD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAE3G;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAM9G;AA8ED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,GAAG,YAAY,GAAG,IAAI,CA+DjF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type LarkReplyClient = {
|
|
2
|
+
im: {
|
|
3
|
+
message: {
|
|
4
|
+
reply(payload: {
|
|
5
|
+
path: {
|
|
6
|
+
message_id: string;
|
|
7
|
+
};
|
|
8
|
+
data: {
|
|
9
|
+
content: string;
|
|
10
|
+
msg_type: string;
|
|
11
|
+
reply_in_thread?: boolean;
|
|
12
|
+
uuid?: string;
|
|
13
|
+
};
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function createLarkReplyClient(input: {
|
|
19
|
+
appId: string;
|
|
20
|
+
appSecret: string;
|
|
21
|
+
domain?: "lark" | "feishu";
|
|
22
|
+
}): LarkReplyClient;
|
|
23
|
+
export declare function replyLarkMessage(client: LarkReplyClient, input: {
|
|
24
|
+
messageId: string;
|
|
25
|
+
text: string;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
//# sourceMappingURL=outbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.d.ts","sourceRoot":"","sources":["../src/outbound.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE;QACF,OAAO,EAAE;YACP,KAAK,CAAC,OAAO,EAAE;gBACb,IAAI,EAAE;oBAAE,UAAU,EAAE,MAAM,CAAA;iBAAE,CAAC;gBAC7B,IAAI,EAAE;oBAAE,OAAO,EAAE,MAAM,CAAC;oBAAC,QAAQ,EAAE,MAAM,CAAC;oBAAC,eAAe,CAAC,EAAE,OAAO,CAAC;oBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;iBAAE,CAAC;aACvF,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;SACtB,CAAC;KACH,CAAC;CACH,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAA;CAAE,GAAG,eAAe,CAM9H;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAKzH"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export type LarkDomain = "lark" | "feishu";
|
|
2
|
+
export type LarkRegistrationQrCodeInfo = {
|
|
3
|
+
url: string;
|
|
4
|
+
expireIn: number;
|
|
5
|
+
};
|
|
6
|
+
export type LarkRegistrationStatusInfo = {
|
|
7
|
+
status: string;
|
|
8
|
+
interval?: number;
|
|
9
|
+
};
|
|
10
|
+
export type RegisteredLarkPersonalAgent = {
|
|
11
|
+
appId: string;
|
|
12
|
+
appSecret: string;
|
|
13
|
+
domain: LarkDomain;
|
|
14
|
+
operatorOpenId?: string;
|
|
15
|
+
botOpenId?: string;
|
|
16
|
+
botName?: string;
|
|
17
|
+
};
|
|
18
|
+
type LarkRegisterAppResult = {
|
|
19
|
+
client_id: string;
|
|
20
|
+
client_secret: string;
|
|
21
|
+
user_info?: {
|
|
22
|
+
open_id?: string;
|
|
23
|
+
tenant_brand?: "feishu" | "lark";
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
type LarkRegisterApp = (options: {
|
|
27
|
+
domain?: string;
|
|
28
|
+
larkDomain?: string;
|
|
29
|
+
source?: string;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
onQRCodeReady: (info: LarkRegistrationQrCodeInfo) => void;
|
|
32
|
+
onStatusChange?: (info: LarkRegistrationStatusInfo) => void;
|
|
33
|
+
appPreset?: {
|
|
34
|
+
name?: string;
|
|
35
|
+
desc?: string;
|
|
36
|
+
};
|
|
37
|
+
addons?: {
|
|
38
|
+
scopes?: {
|
|
39
|
+
tenant?: string[];
|
|
40
|
+
};
|
|
41
|
+
events?: {
|
|
42
|
+
items?: {
|
|
43
|
+
tenant?: string[];
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
createOnly?: boolean;
|
|
48
|
+
}) => Promise<LarkRegisterAppResult>;
|
|
49
|
+
type BotInfoClient = {
|
|
50
|
+
request(input: {
|
|
51
|
+
url: string;
|
|
52
|
+
method: "GET";
|
|
53
|
+
}): Promise<unknown>;
|
|
54
|
+
};
|
|
55
|
+
export type LarkPersonalAgentRegistrationDependencies = {
|
|
56
|
+
registerApp?: LarkRegisterApp;
|
|
57
|
+
createBotInfoClient?(input: {
|
|
58
|
+
appId: string;
|
|
59
|
+
appSecret: string;
|
|
60
|
+
domain: LarkDomain;
|
|
61
|
+
}): BotInfoClient;
|
|
62
|
+
sleep?(ms: number): Promise<void>;
|
|
63
|
+
};
|
|
64
|
+
export type RegisterLarkPersonalAgentInput = {
|
|
65
|
+
domain?: LarkDomain;
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
onQrCode(info: LarkRegistrationQrCodeInfo): void;
|
|
68
|
+
onStatus?(info: LarkRegistrationStatusInfo): void;
|
|
69
|
+
onWarning?(message: string): void;
|
|
70
|
+
};
|
|
71
|
+
export declare function registerLarkPersonalAgent(input: RegisterLarkPersonalAgentInput, dependencies?: LarkPersonalAgentRegistrationDependencies): Promise<RegisteredLarkPersonalAgent>;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../src/registration.ts"],"names":[],"mappings":"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"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { OpenTagRunResult } from "@opentag/core";
|
|
2
|
+
export declare function renderLarkAcknowledgement(runId: string): string;
|
|
3
|
+
export declare function renderLarkFinalResult(result: OpenTagRunResult): string;
|
|
4
|
+
export declare function createLarkTextMessageContent(text: string): string;
|
|
5
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAQtD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAgBtE;AAGD,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjE"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opentag/lark",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Lark/Feishu message normalization and callback helpers for OpenTag.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"development": "./src/index.ts",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"opentag",
|
|
27
|
+
"lark",
|
|
28
|
+
"feishu",
|
|
29
|
+
"events",
|
|
30
|
+
"agents"
|
|
31
|
+
],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@larksuiteoapi/node-sdk": "^1.68.0",
|
|
35
|
+
"@opentag/core": "0.2.0",
|
|
36
|
+
"@opentag/client": "0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"tsup": "^8.5.1",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup && tsc -b tsconfig.json --emitDeclarationOnly --force",
|
|
44
|
+
"lint": "tsc --noEmit"
|
|
45
|
+
}
|
|
46
|
+
}
|