@niq.run/lark-worker 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -2
- package/dist/start.d.ts +3 -0
- package/dist/start.d.ts.map +1 -0
- package/dist/start.js +86 -0
- package/dist/start.js.map +1 -0
- package/dist/worker.d.ts +221 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +552 -0
- package/dist/worker.js.map +1 -0
- package/package.json +36 -3
package/README.md
CHANGED
|
@@ -1,3 +1,71 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @niq.run/lark-worker
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A **Feishu / Lark WebSocket bridge** for [niq](https://github.com/niq-run/niq).
|
|
4
|
+
It listens for Feishu messages, forwards them to a bound **reason worker** as
|
|
5
|
+
`worker.input` events, and pushes the reason worker's reply back into the
|
|
6
|
+
Feishu chat. Chat → worker routing is persistent and reconfigurable at runtime
|
|
7
|
+
(per-chat → default → fallback).
|
|
8
|
+
|
|
9
|
+
Built on [`@niq.run/worker-sdk`](https://www.npmjs.com/package/@niq.run/worker-sdk).
|
|
10
|
+
It is an out-of-process extension worker: it connects to a niq bus over
|
|
11
|
+
HTTP + SSE and is meant to be launched by the niq project supervisor (or by you,
|
|
12
|
+
for a standalone bridge).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm install -g @niq.run/lark-worker
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Run
|
|
21
|
+
|
|
22
|
+
The package ships a `niq-lark` bin (compiled app entry `dist/start.js`), so you
|
|
23
|
+
can start it directly — with `npx` (no install needed):
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
# npx — most convenient for launching, e.g. from a niq project's worker config
|
|
27
|
+
npx @niq.run/lark-worker --reason-worker reason.sales
|
|
28
|
+
|
|
29
|
+
# same thing once installed globally
|
|
30
|
+
niq-lark --reason-worker reason.sales
|
|
31
|
+
|
|
32
|
+
# or run it via node from a checkout
|
|
33
|
+
node dist/start.js --reason-worker reason.sales
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Connection to the bus comes from the environment the supervisor injects
|
|
37
|
+
(`NIQ_BUS_URL`, `NIQ_WORKER_ID`, `NIQ_WORKER_CREDENTIAL`), so a niq project can
|
|
38
|
+
spawn it with `npx @niq.run/lark-worker` and nothing else. CLI flags override
|
|
39
|
+
the env.
|
|
40
|
+
|
|
41
|
+
## Configuration
|
|
42
|
+
|
|
43
|
+
| Purpose | Env | Flag |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| Feishu app id | `LARK_APP_ID` | `--app-id` |
|
|
46
|
+
| Feishu app secret | `LARK_APP_SECRET` | `--app-secret` |
|
|
47
|
+
| Feishu/Lark domain | `LARK_DOMAIN` | `--domain` |
|
|
48
|
+
| Proactive message recipient | `LARK_DEFAULT_USER_ID` | `--default-user` |
|
|
49
|
+
| **Default reason worker** | `NIQ_REASON_WORKER` | `--reason-worker` |
|
|
50
|
+
| **Fallback reason worker** | `LARK_FALLBACK_REASON_WORKER` | `--fallback-reason-worker` |
|
|
51
|
+
| **Per-chat routing** | `LARK_REASON_WORKER_MAPPINGS` | `--reason-mappings` |
|
|
52
|
+
| Persisted routing state | `LARK_STATE_FILE` | `--state-file` |
|
|
53
|
+
|
|
54
|
+
At least one of the default or fallback reason worker must be configured.
|
|
55
|
+
Run `niq-lark --help` for the full flag list.
|
|
56
|
+
|
|
57
|
+
## Usage in code
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { LarkWorker, larkConfigFromEnv } from "@niq.run/lark-worker";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Details
|
|
64
|
+
|
|
65
|
+
See [`src/README.md`](src/README.md) for the full design: the `worker.input`
|
|
66
|
+
flow, `lark.reason.*` runtime routing (`lark.reason.set` / `unset` / `get`),
|
|
67
|
+
and the persisted routing state (`LARK_STATE_FILE`).
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/dist/start.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":""}
|
package/dist/start.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shipped as the binary `niq-lark` (@niq.run/lark-worker). Entry point for
|
|
4
|
+
* running the Lark/Feishu worker as its own process (the "worker as an app"
|
|
5
|
+
* model):
|
|
6
|
+
*
|
|
7
|
+
* node dist/start.js [--key value ...]
|
|
8
|
+
*
|
|
9
|
+
* Feishu long-connection + bus bridge to reason workers. Connects to Feishu
|
|
10
|
+
* (LARK_APP_ID / LARK_APP_SECRET) and the bus (NIQ_BUS_URL /
|
|
11
|
+
* NIQ_WORKER_ID / NIQ_WORKER_CREDENTIAL, merged by the SDK helper
|
|
12
|
+
* `busConnFromArgs`); forwards Feishu messages as worker.input to the default
|
|
13
|
+
* worker named by NIQ_REASON_WORKER (overridable per-chat via --reason-mappings
|
|
14
|
+
* / LARK_REASON_WORKER_MAPPINGS, a JSON object of {chat_id: worker_id}). When
|
|
15
|
+
* neither a per-chat mapping nor the default resolves, messages go to the
|
|
16
|
+
* last-resort fallback named by --fallback-reason-worker /
|
|
17
|
+
* LARK_FALLBACK_REASON_WORKER. The chat→worker routing is persisted
|
|
18
|
+
* (--state-file / LARK_STATE_FILE, default ./lark-reason-state.json).
|
|
19
|
+
*
|
|
20
|
+
* Proactive reason→lark messages use lark.send with no target and fall back to
|
|
21
|
+
* --default-user / LARK_DEFAULT_USER_ID (a Feishu open_id) when the reason
|
|
22
|
+
* worker supplies no explicit target.
|
|
23
|
+
*/
|
|
24
|
+
import { HTTPWorkerClient, busConnFromArgs, parseCliArgs, runWorkerApp, } from "@niq.run/worker-sdk";
|
|
25
|
+
import { LarkWorker, larkConfigFromEnv } from "./worker.js";
|
|
26
|
+
function usage() {
|
|
27
|
+
console.error("usage: niq-lark [--key value ...]\n" +
|
|
28
|
+
" --bus-url <url> bus base URL (else NIQ_BUS_URL)\n" +
|
|
29
|
+
" --worker-id <id> bus identity (else NIQ_WORKER_ID)\n" +
|
|
30
|
+
" --credential <cred> bus credential (else NIQ_WORKER_CREDENTIAL)\n" +
|
|
31
|
+
" lark: --app-id <id>, --app-secret <secret> (default: LARK_APP_ID / LARK_APP_SECRET), " +
|
|
32
|
+
"--default-user <open_id> (default: LARK_DEFAULT_USER_ID), --reason-worker <id> (default: NIQ_REASON_WORKER), " +
|
|
33
|
+
"--fallback-reason-worker <id> (default: LARK_FALLBACK_REASON_WORKER), " +
|
|
34
|
+
"--reason-mappings <json> (default: LARK_REASON_WORKER_MAPPINGS; {chat_id: worker_id}), " +
|
|
35
|
+
"--state-file <path> (default: LARK_STATE_FILE / ./lark-reason-state.json)");
|
|
36
|
+
}
|
|
37
|
+
const { help, opts } = parseCliArgs(process.argv);
|
|
38
|
+
if (help) {
|
|
39
|
+
usage();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
await runWorkerApp(async () => {
|
|
43
|
+
const bus = new HTTPWorkerClient(busConnFromArgs(opts));
|
|
44
|
+
const reasonWorkerID = String(opts["reason-worker"] ?? process.env.NIQ_REASON_WORKER ?? process.env.LARK_REASON_WORKER ?? "");
|
|
45
|
+
const fallbackReasonWorkerID = String(opts["fallback-reason-worker"] ?? process.env.LARK_FALLBACK_REASON_WORKER ?? "");
|
|
46
|
+
if (!reasonWorkerID && !fallbackReasonWorkerID) {
|
|
47
|
+
throw new Error("at least one of NIQ_REASON_WORKER / --reason-worker or LARK_FALLBACK_REASON_WORKER / --fallback-reason-worker must be set (bound reason worker id; fallback is the last resort)");
|
|
48
|
+
}
|
|
49
|
+
const lark = larkConfigFromEnv();
|
|
50
|
+
if (opts["app-id"] !== undefined)
|
|
51
|
+
lark.appId = String(opts["app-id"]);
|
|
52
|
+
if (opts["app-secret"] !== undefined)
|
|
53
|
+
lark.appSecret = String(opts["app-secret"]);
|
|
54
|
+
if (opts["domain"] !== undefined)
|
|
55
|
+
lark.domain = String(opts["domain"]);
|
|
56
|
+
const defaultUserOpenId = String(opts["default-user"] ?? process.env.LARK_DEFAULT_USER_ID ?? "");
|
|
57
|
+
// Optional initial chat → reason worker routing from config.
|
|
58
|
+
let reasonWorkerMappings;
|
|
59
|
+
const mappingsRaw = opts["reason-mappings"] ?? process.env.LARK_REASON_WORKER_MAPPINGS;
|
|
60
|
+
if (mappingsRaw) {
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(String(mappingsRaw));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new Error(`invalid reason mappings JSON: ${err.message}`);
|
|
67
|
+
}
|
|
68
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
69
|
+
throw new Error("reason mappings must be an object of {chat_id: worker_id}");
|
|
70
|
+
}
|
|
71
|
+
reasonWorkerMappings = parsed;
|
|
72
|
+
}
|
|
73
|
+
const stateFile = String(opts["state-file"] ?? process.env.LARK_STATE_FILE ?? "");
|
|
74
|
+
const workerOpts = {
|
|
75
|
+
...lark,
|
|
76
|
+
bus,
|
|
77
|
+
...(reasonWorkerID ? { reasonWorkerID } : {}),
|
|
78
|
+
...(fallbackReasonWorkerID ? { fallbackReasonWorkerID } : {}),
|
|
79
|
+
...(reasonWorkerMappings ? { reasonWorkerMappings } : {}),
|
|
80
|
+
...(stateFile ? { stateFile } : {}),
|
|
81
|
+
...(defaultUserOpenId ? { defaultUserOpenId } : {}),
|
|
82
|
+
};
|
|
83
|
+
const worker = new LarkWorker(workerOpts);
|
|
84
|
+
await worker.run();
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=start.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start.js","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { LarkChannelOptions, NormalizedMessage, RejectEvent, SendInput } from "@larksuite/channel";
|
|
2
|
+
import { type WorkerSideChannel } from "@niq.run/worker-sdk";
|
|
3
|
+
/** Minimal surface of `@larksuite/channel` this worker uses. Injectable for tests. */
|
|
4
|
+
export interface LarkChannel {
|
|
5
|
+
connect(): Promise<void>;
|
|
6
|
+
disconnect(): Promise<void>;
|
|
7
|
+
on(name: "message", handler: (msg: NormalizedMessage) => void | Promise<void>): () => void;
|
|
8
|
+
on(name: "error", handler: (err: unknown) => void | Promise<void>): () => void;
|
|
9
|
+
on(name: "reject", handler: (evt: RejectEvent) => void | Promise<void>): () => void;
|
|
10
|
+
on(name: "reconnecting" | "reconnected", handler: () => void | Promise<void>): () => void;
|
|
11
|
+
onRawEvent(type: string, handler: (payload: unknown) => void | Promise<void>): () => void;
|
|
12
|
+
send(to: string, input: SendInput): Promise<{
|
|
13
|
+
messageId: string;
|
|
14
|
+
}>;
|
|
15
|
+
/** Rewrite an already-sent text message's content in place (Feishu message edit). */
|
|
16
|
+
editMessage(messageId: string, text: string): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
/** A minimal logger interface, satisfied by `console`. */
|
|
19
|
+
export interface Logger {
|
|
20
|
+
info(...args: unknown[]): void;
|
|
21
|
+
warn(...args: unknown[]): void;
|
|
22
|
+
error(...args: unknown[]): void;
|
|
23
|
+
}
|
|
24
|
+
/** Wrap a logger so every line is prefixed with an ISO timestamp. */
|
|
25
|
+
export declare function withTimestamps<T extends Logger>(base: T): T;
|
|
26
|
+
/** The subset of the `fs/promises` API the file state store uses. Injectable. */
|
|
27
|
+
export interface StateFileFs {
|
|
28
|
+
mkdir(dir: string, opts: {
|
|
29
|
+
recursive: boolean;
|
|
30
|
+
}): Promise<string | undefined>;
|
|
31
|
+
readFile(file: string, enc: "utf8"): Promise<string>;
|
|
32
|
+
writeFile(file: string, data: string, enc: "utf8"): Promise<void>;
|
|
33
|
+
rename(oldPath: string, newPath: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Persistence for the reason-routing state. Injected for tests; the default
|
|
37
|
+
* is {@link FileLarkStateStore}. This is the seam a future snapshot/restore
|
|
38
|
+
* implementation can plug into without touching the worker's logic.
|
|
39
|
+
*/
|
|
40
|
+
export interface LarkStateStore {
|
|
41
|
+
load(): Promise<Record<string, unknown>>;
|
|
42
|
+
save(state: Record<string, unknown>): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A {@link LarkStateStore} backed by a local JSON file, written atomically
|
|
46
|
+
* (temp file + rename) so a crash mid-write cannot corrupt the saved state.
|
|
47
|
+
* A missing file loads as an empty state.
|
|
48
|
+
*/
|
|
49
|
+
export declare class FileLarkStateStore implements LarkStateStore {
|
|
50
|
+
private readonly file;
|
|
51
|
+
private readonly fs;
|
|
52
|
+
constructor(file: string, fsImpl?: StateFileFs);
|
|
53
|
+
load(): Promise<Record<string, unknown>>;
|
|
54
|
+
save(state: Record<string, unknown>): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** The default state file path when none is configured. */
|
|
57
|
+
export declare function defaultStateFile(env?: Record<string, string | undefined>): string;
|
|
58
|
+
/** Connection settings for the Lark side, read from the environment by default. */
|
|
59
|
+
export interface LarkConfig {
|
|
60
|
+
appId: string;
|
|
61
|
+
appSecret: string;
|
|
62
|
+
/** Request domain, e.g. `https://open.feishu.cn` (default) or a Lark domain. */
|
|
63
|
+
domain?: string;
|
|
64
|
+
/** Extra options forwarded to the channel (see {@link LarkChannelOptions}). */
|
|
65
|
+
channel?: Omit<LarkChannelOptions, "appId" | "appSecret" | "domain">;
|
|
66
|
+
/** Injectable factory — defaults to {@link createLarkChannel}. */
|
|
67
|
+
createChannel?: (opts: LarkChannelOptions) => LarkChannel;
|
|
68
|
+
}
|
|
69
|
+
/** Build a {@link LarkConfig} from env. Throws naming any missing vars. */
|
|
70
|
+
export declare function larkConfigFromEnv(env?: Record<string, string | undefined>): LarkConfig;
|
|
71
|
+
/** Configuration for the assembled bridge. */
|
|
72
|
+
export interface LarkBridgeOptions extends LarkConfig {
|
|
73
|
+
/** Worker-side bus channel. Injected for tests; built from env otherwise. */
|
|
74
|
+
bus: WorkerSideChannel;
|
|
75
|
+
/**
|
|
76
|
+
* The default reason worker Feishu messages are forwarded to when a chat has
|
|
77
|
+
* no per-chat override (see {@link LarkBridgeOptions.reasonWorkerMappings}),
|
|
78
|
+
* unless a persisted state file overrides it. Optional: when unset, routing
|
|
79
|
+
* falls through to {@link LarkBridgeOptions.fallbackReasonWorkerID}. Config
|
|
80
|
+
* only seeds this value.
|
|
81
|
+
*/
|
|
82
|
+
reasonWorkerID?: string;
|
|
83
|
+
/**
|
|
84
|
+
* The last-resort reason worker used when neither a per-chat mapping nor the
|
|
85
|
+
* default resolves. Optional; config only seeds it (mutable at runtime and
|
|
86
|
+
* persisted like the rest of the routing).
|
|
87
|
+
*/
|
|
88
|
+
fallbackReasonWorkerID?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Initial chat_id → reason worker id routing. Config only seeds the runtime
|
|
91
|
+
* map; it is updateable at runtime via the `lark.reason.*` extensions and
|
|
92
|
+
* persisted (see {@link LarkBridgeOptions.stateStore}).
|
|
93
|
+
*/
|
|
94
|
+
reasonWorkerMappings?: Record<string, string>;
|
|
95
|
+
/**
|
|
96
|
+
* Persistence backend for the reason-routing state. Defaults to a local JSON
|
|
97
|
+
* file at {@link LarkBridgeOptions.stateFile}. Inject for tests to avoid I/O.
|
|
98
|
+
* This is the seam a future snapshot/restore implementation plugs into.
|
|
99
|
+
*/
|
|
100
|
+
stateStore?: LarkStateStore;
|
|
101
|
+
/**
|
|
102
|
+
* Path for the default file state store (ignored when `stateStore` is given).
|
|
103
|
+
* Falls back to `LARK_STATE_FILE`, then `./lark-reason-state.json`.
|
|
104
|
+
*/
|
|
105
|
+
stateFile?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Optional replacement for the default `<system-reminder>` prepended to
|
|
108
|
+
* outbound worker.input text.
|
|
109
|
+
*/
|
|
110
|
+
systemReminder?: string;
|
|
111
|
+
/** When set, echo Feishu messages back instead of forwarding (connectivity check). */
|
|
112
|
+
echo?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Default Feishu user (open_id) a `lark.send` call falls back to when the
|
|
115
|
+
* reason worker does not supply an explicit `target` (e.g. a proactive push
|
|
116
|
+
* with no chat context). Without a target and no default user, the call
|
|
117
|
+
* fails with `request.failed`.
|
|
118
|
+
*/
|
|
119
|
+
defaultUserOpenId?: string;
|
|
120
|
+
/**
|
|
121
|
+
* When a reason worker makes several `lark.send` calls to the same target
|
|
122
|
+
* within one reply turn, append each piece of text onto the message it last
|
|
123
|
+
* sent to that target (updating it in place via Feishu's message edit) instead
|
|
124
|
+
* of posting a brand-new message per call. Each new inbound Feishu message
|
|
125
|
+
* starts a fresh target message. Defaults to `true`.
|
|
126
|
+
*/
|
|
127
|
+
appendOutbound?: boolean;
|
|
128
|
+
/** Where to send diagnostics. Defaults to `console`. */
|
|
129
|
+
logger?: Logger;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* The extension lark exposes to peers (reason) for sending a Feishu message to
|
|
133
|
+
* a user/chat. Non-SelfOnly so it is discovered by reason and callable.
|
|
134
|
+
*/
|
|
135
|
+
export declare const LARK_SEND_EVENT = "lark.send";
|
|
136
|
+
/**
|
|
137
|
+
* The group of extensions lark exposes for managing the chat → reason worker
|
|
138
|
+
* routing at runtime. All are non-SelfOnly so a control plane / admin peer can
|
|
139
|
+
* discover and call them. Each mutation also persists the new routing.
|
|
140
|
+
*/
|
|
141
|
+
/** Route a Feishu chat (or set the default) to a specific reason worker. */
|
|
142
|
+
export declare const LARK_REASON_SET = "lark.reason.set";
|
|
143
|
+
/** Stop routing a chat to its own worker; fall back to the default. */
|
|
144
|
+
export declare const LARK_REASON_UNSET = "lark.reason.unset";
|
|
145
|
+
/** Return the current routing (default + per-chat overrides). */
|
|
146
|
+
export declare const LARK_REASON_GET = "lark.reason.get";
|
|
147
|
+
/**
|
|
148
|
+
* The Feishu ⇄ reason bridge. Connect both sides, then forward Feishu
|
|
149
|
+
* messages to the bound reason worker and push its replies back to Feishu.
|
|
150
|
+
*/
|
|
151
|
+
export declare class LarkWorker {
|
|
152
|
+
private readonly lark;
|
|
153
|
+
private readonly bus;
|
|
154
|
+
private readonly base;
|
|
155
|
+
private readonly workerID;
|
|
156
|
+
/** Default reason worker; overridden per-chat by {@link perChat}. Mutable at runtime. */
|
|
157
|
+
private defaultReasonWorker;
|
|
158
|
+
/** Last-resort worker used when neither per-chat nor default resolves. Mutable at runtime. */
|
|
159
|
+
private fallbackReasonWorker;
|
|
160
|
+
/** chat_id → reason worker id. Updated by config seed, runtime events, and persisted state. */
|
|
161
|
+
private readonly perChat;
|
|
162
|
+
private readonly stateStore;
|
|
163
|
+
private readonly systemReminder;
|
|
164
|
+
private readonly echo;
|
|
165
|
+
private readonly defaultUserOpenId;
|
|
166
|
+
private readonly appendMode;
|
|
167
|
+
/** target → the last outbound message this worker is extending (append mode); text is the in-place accumulated content. */
|
|
168
|
+
private readonly appendTargets;
|
|
169
|
+
private readonly logger;
|
|
170
|
+
/** Settled once both the bus and Feishu sides are connected (see connect()). */
|
|
171
|
+
private readonly ready;
|
|
172
|
+
private readonly markReady;
|
|
173
|
+
constructor(opts: LarkBridgeOptions);
|
|
174
|
+
/** Connect both the Feishu long connection and the bus; announce presence. */
|
|
175
|
+
connect(): Promise<void>;
|
|
176
|
+
/** Connect and then run the bus event loop (blocks until {@link close}). */
|
|
177
|
+
run(): Promise<void>;
|
|
178
|
+
/** Close both sides. */
|
|
179
|
+
close(): Promise<void>;
|
|
180
|
+
private handleFeishuMessage;
|
|
181
|
+
/**
|
|
182
|
+
* Resolve the reason worker a chat's messages are forwarded to: per-chat
|
|
183
|
+
* mapping, else the default, else the last-resort fallback. Returns "" when
|
|
184
|
+
* none is configured.
|
|
185
|
+
*/
|
|
186
|
+
private reasonWorkerFor;
|
|
187
|
+
/**
|
|
188
|
+
* Restore persisted routing on top of the config seed. A persisted value wins
|
|
189
|
+
* over config because it is the most recent state (last write wins).
|
|
190
|
+
*/
|
|
191
|
+
private loadState;
|
|
192
|
+
/** Persist the current routing. Best-effort: a failed write is logged, not thrown. */
|
|
193
|
+
private persistState;
|
|
194
|
+
/** Fill the {chat_id} / {sender_open_id} placeholders in the reminder template. */
|
|
195
|
+
private renderReminder;
|
|
196
|
+
/**
|
|
197
|
+
* Serve the peer-callable lark.send extension: a tool invocation from reason
|
|
198
|
+
* (via the discovery/tool bridge) carrying `target` (chat_id / open_id /
|
|
199
|
+
* user_id, falling back to the default user) and `text`. Replied with a
|
|
200
|
+
* request.completed / request.failed echoing the call id.
|
|
201
|
+
*/
|
|
202
|
+
private handleLarkSend;
|
|
203
|
+
/**
|
|
204
|
+
* Append-mode send: if this target already has an in-progress message from
|
|
205
|
+
* this turn, extend it in place; otherwise post a fresh message and remember
|
|
206
|
+
* it. Repeated `lark.send` calls to the same target therefore assemble into
|
|
207
|
+
* one message instead of flooding the Feishu chat.
|
|
208
|
+
*/
|
|
209
|
+
private sendAppended;
|
|
210
|
+
/**
|
|
211
|
+
* Serve the `lark.reason.set` extension: route a chat to a worker (`chat_id`),
|
|
212
|
+
* set the default (neither `chat_id` nor `fallback`), or set the last-resort
|
|
213
|
+
* fallback (`fallback: true`). Persists the change so it survives a restart.
|
|
214
|
+
*/
|
|
215
|
+
private handleReasonSet;
|
|
216
|
+
/** Serve the `lark.reason.unset` extension: drop a chat override or clear the fallback. */
|
|
217
|
+
private handleReasonUnset;
|
|
218
|
+
/** Serve the `lark.reason.get` extension: return the current routing as JSON. */
|
|
219
|
+
private handleReasonGet;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lark worker — a WebSocket bridge between the niq event bus and Feishu/Lark,
|
|
3
|
+
* built on `@larksuite/channel` (which wraps the official
|
|
4
|
+
* `@larksuiteoapi/node-sdk`).
|
|
5
|
+
*
|
|
6
|
+
* Flow:
|
|
7
|
+
* Feishu message ──worker.input──▶ bound reason worker
|
|
8
|
+
* reason reply ──lark.send────▶ this worker ──send──▶ Feishu chat
|
|
9
|
+
*
|
|
10
|
+
* Inbound: every Feishu message is forwarded to a single bound reason worker
|
|
11
|
+
* as a `worker.input` event with `input_mode: "append"`, payload
|
|
12
|
+
* `{ text, chat_id, sender_open_id }`. The text is prefixed with a
|
|
13
|
+
* `<system-reminder>` telling reason this came from Feishu and to reply with
|
|
14
|
+
* the `lark.send` extension targeting the `chat_id`.
|
|
15
|
+
*
|
|
16
|
+
* Outbound: the reason worker replies by calling the `lark.send` extension
|
|
17
|
+
* (exposed by this worker) with `target` (a chat_id / open_id / user_id) and
|
|
18
|
+
* `text`. This worker sends that text to the target Feishu chat. The worker
|
|
19
|
+
* does NOT consume `worker.input` events as replies — `lark.send` is the only
|
|
20
|
+
* inbound path for reason → Feishu output.
|
|
21
|
+
*
|
|
22
|
+
* Credentials come from the environment: `LARK_APP_ID` / `LARK_APP_SECRET`
|
|
23
|
+
* (+ `LARK_DOMAIN`), and the bus via `NIQ_BUS_URL` / `NIQ_WORKER_ID` /
|
|
24
|
+
* `NIQ_WORKER_CREDENTIAL` — the niq project supervisor injects these as launch
|
|
25
|
+
* args (project.json env). The default reason worker id comes from
|
|
26
|
+
* `NIQ_REASON_WORKER`; an initial per-chat override map can come from
|
|
27
|
+
* `LARK_REASON_WORKER_MAPPINGS` (a JSON object of {chat_id: worker_id}).
|
|
28
|
+
*
|
|
29
|
+
* Routing (which reason worker a Feishu chat is forwarded to) is a mutable,
|
|
30
|
+
* persisted property: configured on startup, updated at runtime via the
|
|
31
|
+
* `lark.reason.*` extension group, and saved to a state file so it survives
|
|
32
|
+
* restarts. The runtime saves it via an injectable {@link LarkStateStore}.
|
|
33
|
+
* (Third-party process snapshot/restore is not implemented yet; the worker
|
|
34
|
+
* persists its own state for now.)
|
|
35
|
+
*
|
|
36
|
+
* Both external sides sit behind narrow interfaces so tests run without real
|
|
37
|
+
* Feishu credentials or a live bus.
|
|
38
|
+
*/
|
|
39
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
40
|
+
import { dirname } from "node:path";
|
|
41
|
+
import { createLarkChannel } from "@larksuite/channel";
|
|
42
|
+
import { BaseWorker, argString, createEvent, EventType, parseToolCall, } from "@niq.run/worker-sdk";
|
|
43
|
+
/** Wrap a logger so every line is prefixed with an ISO timestamp. */
|
|
44
|
+
export function withTimestamps(base) {
|
|
45
|
+
const ts = () => new Date().toISOString();
|
|
46
|
+
for (const m of ["info", "warn", "error"]) {
|
|
47
|
+
const orig = base[m].bind(base);
|
|
48
|
+
base[m] = (...args) => orig(`[${ts()}]`, ...args);
|
|
49
|
+
}
|
|
50
|
+
return base;
|
|
51
|
+
}
|
|
52
|
+
const DEFAULT_LOGGER = withTimestamps(console);
|
|
53
|
+
/**
|
|
54
|
+
* A {@link LarkStateStore} backed by a local JSON file, written atomically
|
|
55
|
+
* (temp file + rename) so a crash mid-write cannot corrupt the saved state.
|
|
56
|
+
* A missing file loads as an empty state.
|
|
57
|
+
*/
|
|
58
|
+
export class FileLarkStateStore {
|
|
59
|
+
file;
|
|
60
|
+
fs;
|
|
61
|
+
constructor(file, fsImpl = { mkdir, readFile, writeFile, rename }) {
|
|
62
|
+
this.file = file;
|
|
63
|
+
this.fs = fsImpl;
|
|
64
|
+
}
|
|
65
|
+
async load() {
|
|
66
|
+
let raw;
|
|
67
|
+
try {
|
|
68
|
+
raw = await this.fs.readFile(this.file, "utf8");
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
// First run: no state file yet — start from config defaults.
|
|
72
|
+
if (err?.code === "ENOENT")
|
|
73
|
+
return {};
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
const parsed = JSON.parse(raw);
|
|
77
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
78
|
+
? parsed
|
|
79
|
+
: {};
|
|
80
|
+
}
|
|
81
|
+
async save(state) {
|
|
82
|
+
const tmp = `${this.file}.${process.pid}.${Date.now()}.tmp`;
|
|
83
|
+
await this.fs.mkdir(dirname(this.file), { recursive: true });
|
|
84
|
+
await this.fs.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
|
|
85
|
+
await this.fs.rename(tmp, this.file);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** The default state file path when none is configured. */
|
|
89
|
+
export function defaultStateFile(env = process.env) {
|
|
90
|
+
return env.LARK_STATE_FILE?.trim() || "lark-reason-state.json";
|
|
91
|
+
}
|
|
92
|
+
/** Build a {@link LarkConfig} from env. Throws naming any missing vars. */
|
|
93
|
+
export function larkConfigFromEnv(env = process.env) {
|
|
94
|
+
const appId = env.LARK_APP_ID?.trim() ?? "";
|
|
95
|
+
const appSecret = env.LARK_APP_SECRET?.trim() ?? "";
|
|
96
|
+
const domain = env.LARK_DOMAIN?.trim() || "https://open.feishu.cn";
|
|
97
|
+
const missing = [];
|
|
98
|
+
if (!appId)
|
|
99
|
+
missing.push("LARK_APP_ID");
|
|
100
|
+
if (!appSecret)
|
|
101
|
+
missing.push("LARK_APP_SECRET");
|
|
102
|
+
if (missing.length > 0) {
|
|
103
|
+
throw new Error(`niq: missing environment variable(s): ${missing.join(", ")}. ` +
|
|
104
|
+
`Provide them as launch args (project.json env) when the worker is ` +
|
|
105
|
+
`started by the niq project supervisor.`);
|
|
106
|
+
}
|
|
107
|
+
return { appId, appSecret, domain };
|
|
108
|
+
}
|
|
109
|
+
const DEFAULT_SYSTEM_REMINDER = `<system-reminder>
|
|
110
|
+
This message was sent by a user over Feishu (Lark).
|
|
111
|
+
Chat id: {chat_id}
|
|
112
|
+
Sender open_id: {sender_open_id}
|
|
113
|
+
Reply to the user by calling the lark.send tool with target = "{chat_id}" and
|
|
114
|
+
text = your reply. The reply is delivered straight back into the user's Feishu
|
|
115
|
+
chat. Several lark.send calls to the same target within one reply are appended
|
|
116
|
+
into a single message, so just send your answer — do not number or header each
|
|
117
|
+
piece. Keep your answer in the language the user wrote in, and do not preface it
|
|
118
|
+
with any system notes.
|
|
119
|
+
</system-reminder>`;
|
|
120
|
+
const NS = "lark";
|
|
121
|
+
/**
|
|
122
|
+
* The extension lark exposes to peers (reason) for sending a Feishu message to
|
|
123
|
+
* a user/chat. Non-SelfOnly so it is discovered by reason and callable.
|
|
124
|
+
*/
|
|
125
|
+
export const LARK_SEND_EVENT = "lark.send";
|
|
126
|
+
/**
|
|
127
|
+
* The group of extensions lark exposes for managing the chat → reason worker
|
|
128
|
+
* routing at runtime. All are non-SelfOnly so a control plane / admin peer can
|
|
129
|
+
* discover and call them. Each mutation also persists the new routing.
|
|
130
|
+
*/
|
|
131
|
+
/** Route a Feishu chat (or set the default) to a specific reason worker. */
|
|
132
|
+
export const LARK_REASON_SET = "lark.reason.set";
|
|
133
|
+
/** Stop routing a chat to its own worker; fall back to the default. */
|
|
134
|
+
export const LARK_REASON_UNSET = "lark.reason.unset";
|
|
135
|
+
/** Return the current routing (default + per-chat overrides). */
|
|
136
|
+
export const LARK_REASON_GET = "lark.reason.get";
|
|
137
|
+
/**
|
|
138
|
+
* The Feishu ⇄ reason bridge. Connect both sides, then forward Feishu
|
|
139
|
+
* messages to the bound reason worker and push its replies back to Feishu.
|
|
140
|
+
*/
|
|
141
|
+
export class LarkWorker {
|
|
142
|
+
lark;
|
|
143
|
+
bus;
|
|
144
|
+
base;
|
|
145
|
+
workerID;
|
|
146
|
+
/** Default reason worker; overridden per-chat by {@link perChat}. Mutable at runtime. */
|
|
147
|
+
defaultReasonWorker;
|
|
148
|
+
/** Last-resort worker used when neither per-chat nor default resolves. Mutable at runtime. */
|
|
149
|
+
fallbackReasonWorker;
|
|
150
|
+
/** chat_id → reason worker id. Updated by config seed, runtime events, and persisted state. */
|
|
151
|
+
perChat = new Map();
|
|
152
|
+
stateStore;
|
|
153
|
+
systemReminder;
|
|
154
|
+
echo;
|
|
155
|
+
defaultUserOpenId;
|
|
156
|
+
appendMode;
|
|
157
|
+
/** target → the last outbound message this worker is extending (append mode); text is the in-place accumulated content. */
|
|
158
|
+
appendTargets = new Map();
|
|
159
|
+
logger;
|
|
160
|
+
/** Settled once both the bus and Feishu sides are connected (see connect()). */
|
|
161
|
+
ready;
|
|
162
|
+
markReady;
|
|
163
|
+
constructor(opts) {
|
|
164
|
+
const create = opts.createChannel ?? createLarkChannel;
|
|
165
|
+
this.lark = create({
|
|
166
|
+
appId: opts.appId,
|
|
167
|
+
appSecret: opts.appSecret,
|
|
168
|
+
domain: opts.domain,
|
|
169
|
+
...opts.channel,
|
|
170
|
+
});
|
|
171
|
+
this.bus = opts.bus;
|
|
172
|
+
this.workerID = opts.bus.id;
|
|
173
|
+
// Config seeds the routing state; a persisted state file overrides it on load.
|
|
174
|
+
this.defaultReasonWorker = opts.reasonWorkerID ?? "";
|
|
175
|
+
this.fallbackReasonWorker = opts.fallbackReasonWorkerID ?? "";
|
|
176
|
+
for (const [chat, wid] of Object.entries(opts.reasonWorkerMappings ?? {})) {
|
|
177
|
+
if (wid)
|
|
178
|
+
this.perChat.set(chat, wid);
|
|
179
|
+
}
|
|
180
|
+
this.stateStore =
|
|
181
|
+
opts.stateStore ??
|
|
182
|
+
new FileLarkStateStore(opts.stateFile ?? defaultStateFile());
|
|
183
|
+
this.systemReminder = opts.systemReminder ?? DEFAULT_SYSTEM_REMINDER;
|
|
184
|
+
this.echo = opts.echo ?? false;
|
|
185
|
+
this.defaultUserOpenId = opts.defaultUserOpenId ?? "";
|
|
186
|
+
this.appendMode = opts.appendOutbound ?? true;
|
|
187
|
+
this.logger = opts.logger ?? DEFAULT_LOGGER;
|
|
188
|
+
this.base = new BaseWorker({
|
|
189
|
+
id: this.workerID,
|
|
190
|
+
subscriptions: [
|
|
191
|
+
{ type: LARK_REASON_SET },
|
|
192
|
+
{ type: LARK_REASON_UNSET },
|
|
193
|
+
{ type: LARK_REASON_GET },
|
|
194
|
+
],
|
|
195
|
+
channel: this.bus,
|
|
196
|
+
});
|
|
197
|
+
// Expose a peer-callable extension so reason can send Feishu messages with
|
|
198
|
+
// an explicit target (chat_id / open_id / user_id). Non-SelfOnly so it is
|
|
199
|
+
// discovered by reason and invoked via the tool-name bridge.
|
|
200
|
+
this.base.register({
|
|
201
|
+
event: LARK_SEND_EVENT,
|
|
202
|
+
description: "Send a message to a Feishu user or chat. Provide target (chat_id, open_id, or user_id) and text.",
|
|
203
|
+
parameters: {
|
|
204
|
+
type: "object",
|
|
205
|
+
properties: {
|
|
206
|
+
target: {
|
|
207
|
+
type: "string",
|
|
208
|
+
description: "Feishu chat_id, open_id, or user_id of the recipient",
|
|
209
|
+
},
|
|
210
|
+
text: { type: "string", description: "Message text" },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
}, (evt) => this.handleLarkSend(evt));
|
|
214
|
+
// The lark.reason.* group: manage the chat → reason worker routing at
|
|
215
|
+
// runtime. Every mutation is persisted so the routing survives restarts.
|
|
216
|
+
this.base.register({
|
|
217
|
+
event: LARK_REASON_SET,
|
|
218
|
+
description: "Route a Feishu chat to a reason worker, set the default, or set the fallback. " +
|
|
219
|
+
"Provide worker_id. Include chat_id to route only that chat; include fallback=true " +
|
|
220
|
+
"(no chat_id) to set the last-resort fallback; otherwise the default reason worker is set.",
|
|
221
|
+
parameters: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
chat_id: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Feishu chat_id to route (omit to set the default or fallback)",
|
|
227
|
+
},
|
|
228
|
+
worker_id: {
|
|
229
|
+
type: "string",
|
|
230
|
+
description: "Reason worker id to route matching messages to",
|
|
231
|
+
},
|
|
232
|
+
fallback: {
|
|
233
|
+
type: "boolean",
|
|
234
|
+
description: "Set the last-resort fallback reason worker instead of the default (ignored when chat_id is given)",
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
}, (evt) => this.handleReasonSet(evt));
|
|
239
|
+
this.base.register({
|
|
240
|
+
event: LARK_REASON_UNSET,
|
|
241
|
+
description: "Stop routing a Feishu chat to its own reason worker and fall back to the default, " +
|
|
242
|
+
"or clear the fallback. Provide chat_id to drop a chat override; include fallback=true " +
|
|
243
|
+
"(chat_id optional) to clear the fallback.",
|
|
244
|
+
parameters: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
chat_id: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "Feishu chat_id to stop overriding, falling back to the default",
|
|
250
|
+
},
|
|
251
|
+
fallback: {
|
|
252
|
+
type: "boolean",
|
|
253
|
+
description: "Clear the fallback reason worker",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
}, (evt) => this.handleReasonUnset(evt));
|
|
258
|
+
this.base.register({
|
|
259
|
+
event: LARK_REASON_GET,
|
|
260
|
+
description: "Return the current chat → reason worker routing (default + per-chat overrides).",
|
|
261
|
+
}, (evt) => this.handleReasonGet(evt));
|
|
262
|
+
let markReady;
|
|
263
|
+
this.ready = new Promise((resolve) => {
|
|
264
|
+
markReady = resolve;
|
|
265
|
+
});
|
|
266
|
+
this.markReady = markReady;
|
|
267
|
+
// The message handler is registered once, in the constructor, so a Feishu
|
|
268
|
+
// message can never be missed due to connect() ordering. handleFeishuMessage
|
|
269
|
+
// awaits this.ready before touching the bus, which preserves the original
|
|
270
|
+
// guarantee: we never publish to the bus before it is connected (the WS can
|
|
271
|
+
// be live before the bus SSE is ready).
|
|
272
|
+
this.lark.on("message", (msg) => this.handleFeishuMessage(msg));
|
|
273
|
+
this.lark.on("error", (err) => this.logger.error(`[${NS}] feishu error:`, err));
|
|
274
|
+
this.lark.on("reject", (evt) => this.logger.warn(`[${NS}] feishu message REJECTED by policy: ${JSON.stringify(evt)}`));
|
|
275
|
+
// Lowest-level probe: logs any im.message.receive_v1 the WS actually
|
|
276
|
+
// delivers, before normalization / policy. If this fires but the
|
|
277
|
+
// normalized 'message' event does not, the drop is in the channel's
|
|
278
|
+
// normalization or policy layer, not in Feishu delivery.
|
|
279
|
+
this.lark.onRawEvent("im.message.receive_v1", () => this.logger.info(`[${NS}] raw im.message.receive_v1 received on WS`));
|
|
280
|
+
this.lark.on("reconnecting", () => this.logger.warn(`[${NS}] feishu websocket reconnecting`));
|
|
281
|
+
this.lark.on("reconnected", () => this.logger.info(`[${NS}] feishu websocket reconnected`));
|
|
282
|
+
}
|
|
283
|
+
/** Connect both the Feishu long connection and the bus; announce presence. */
|
|
284
|
+
async connect() {
|
|
285
|
+
await Promise.all([
|
|
286
|
+
this.bus.connect().catch((err) => {
|
|
287
|
+
this.logger.error(`[${NS}] bus connect failed:`, err);
|
|
288
|
+
throw err;
|
|
289
|
+
}),
|
|
290
|
+
this.lark.connect().catch((err) => {
|
|
291
|
+
this.logger.error(`[${NS}] feishu connect failed:`, err);
|
|
292
|
+
throw err;
|
|
293
|
+
}),
|
|
294
|
+
]);
|
|
295
|
+
// Restore persisted routing before being ready, so the first Feishu message
|
|
296
|
+
// already uses the saved chat → reason worker mapping.
|
|
297
|
+
await this.loadState();
|
|
298
|
+
this.markReady();
|
|
299
|
+
this.logger.info(`[${NS}] connected: bus worker_id=${this.workerID} default_reason=${this.defaultReasonWorker || "—"} fallback=${this.fallbackReasonWorker || "—"} per_chat=${this.perChat.size} feishu app=${this.lark.constructor.name}`);
|
|
300
|
+
// Presence is live-only, not durable history. The SDK's announceReady would
|
|
301
|
+
// persist this worker.ready into the project event store (it sets no
|
|
302
|
+
// transient flag); a presence stalling the durable log is noise. Broadcast
|
|
303
|
+
// a transient ready instead so lark's presence never lands in events.db.
|
|
304
|
+
const ready = createEvent(EventType.WorkerReady, {
|
|
305
|
+
type: "lark",
|
|
306
|
+
watch: this.base.watchEntries(false),
|
|
307
|
+
publishes: [
|
|
308
|
+
{ type: "worker.input", description: "Feishu messages forwarded to reason" },
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
ready.transient = true; // presence: live-only, not durable history
|
|
312
|
+
ready.exclude_worker_id = this.workerID;
|
|
313
|
+
await this.base.channel.broadcast(ready);
|
|
314
|
+
this.logger.info(`[${NS}] announced worker.ready (transient)`);
|
|
315
|
+
}
|
|
316
|
+
/** Connect and then run the bus event loop (blocks until {@link close}). */
|
|
317
|
+
async run() {
|
|
318
|
+
await this.connect();
|
|
319
|
+
// The worker never acts on `worker.input` events — reason does not reply
|
|
320
|
+
// to lark that way. Replies arrive only via the `lark.send` extension, and
|
|
321
|
+
// routing is managed via the `lark.reason.*` extensions; every event is
|
|
322
|
+
// dispatched to the registered extension handlers (unknown events are
|
|
323
|
+
// ignored).
|
|
324
|
+
for await (const evt of this.bus.events()) {
|
|
325
|
+
this.base.dispatchExtension(evt);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/** Close both sides. */
|
|
329
|
+
async close() {
|
|
330
|
+
await Promise.all([
|
|
331
|
+
this.bus.close().catch(() => { }),
|
|
332
|
+
this.lark.disconnect().catch(() => { }),
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
async handleFeishuMessage(msg) {
|
|
336
|
+
// Wait until the bus is connected before doing anything, so we never
|
|
337
|
+
// publish before the bus is up. The handler is registered in the
|
|
338
|
+
// constructor, so a message arriving during connect() simply waits here.
|
|
339
|
+
await this.ready;
|
|
340
|
+
// Don't relay the bot's own outbound messages if Feishu delivers them.
|
|
341
|
+
if (msg.senderType === "bot") {
|
|
342
|
+
this.logger.info(`[${NS}] skip bot-originated message chat=${msg.chatId} msg=${msg.messageId}`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
this.logger.info(`[${NS}] feishu message chat=${msg.chatId} msg=${msg.messageId} sender=${msg.senderId} type=${msg.senderType ?? "na"} text=${JSON.stringify(msg.content)}`);
|
|
346
|
+
if (this.echo) {
|
|
347
|
+
await this.lark.send(msg.chatId, { text: "echo: " + msg.content });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// A new user message begins a fresh reply: forget any in-progress
|
|
351
|
+
// outbound append so the next lark.send posts a new message instead of
|
|
352
|
+
// extending the previous turn's.
|
|
353
|
+
this.appendTargets.delete(msg.chatId);
|
|
354
|
+
const input = createEvent("worker.input", {
|
|
355
|
+
text: this.renderReminder(msg) + "\n" + msg.content,
|
|
356
|
+
chat_id: msg.chatId,
|
|
357
|
+
sender_open_id: msg.senderId,
|
|
358
|
+
input_mode: "append",
|
|
359
|
+
});
|
|
360
|
+
const reasonWorker = this.reasonWorkerFor(msg.chatId);
|
|
361
|
+
if (!reasonWorker) {
|
|
362
|
+
this.logger.warn(`[${NS}] drop feishu message chat=${msg.chatId} msg=${msg.messageId}: no reason worker (no per-chat mapping, default, or fallback)`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
await this.bus.send(input, reasonWorker);
|
|
366
|
+
this.logger.info(`[${NS}] sent worker.input → reason ${reasonWorker} (chat ${msg.chatId})`);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Resolve the reason worker a chat's messages are forwarded to: per-chat
|
|
370
|
+
* mapping, else the default, else the last-resort fallback. Returns "" when
|
|
371
|
+
* none is configured.
|
|
372
|
+
*/
|
|
373
|
+
reasonWorkerFor(chatId) {
|
|
374
|
+
const perChat = this.perChat.get(chatId);
|
|
375
|
+
if (perChat)
|
|
376
|
+
return perChat;
|
|
377
|
+
if (this.defaultReasonWorker)
|
|
378
|
+
return this.defaultReasonWorker;
|
|
379
|
+
return this.fallbackReasonWorker;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Restore persisted routing on top of the config seed. A persisted value wins
|
|
383
|
+
* over config because it is the most recent state (last write wins).
|
|
384
|
+
*/
|
|
385
|
+
async loadState() {
|
|
386
|
+
let state;
|
|
387
|
+
try {
|
|
388
|
+
state = await this.stateStore.load();
|
|
389
|
+
}
|
|
390
|
+
catch (err) {
|
|
391
|
+
this.logger.error(`[${NS}] failed to load reason state:`, err);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (typeof state["default_reason_worker"] === "string" && state["default_reason_worker"]) {
|
|
395
|
+
this.defaultReasonWorker = state["default_reason_worker"];
|
|
396
|
+
}
|
|
397
|
+
if (typeof state["fallback_reason_worker"] === "string" && state["fallback_reason_worker"]) {
|
|
398
|
+
this.fallbackReasonWorker = state["fallback_reason_worker"];
|
|
399
|
+
}
|
|
400
|
+
const perChat = state["per_chat"];
|
|
401
|
+
if (perChat && typeof perChat === "object" && !Array.isArray(perChat)) {
|
|
402
|
+
this.perChat.clear();
|
|
403
|
+
for (const [chat, wid] of Object.entries(perChat)) {
|
|
404
|
+
if (typeof wid === "string" && wid)
|
|
405
|
+
this.perChat.set(chat, wid);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
this.logger.info(`[${NS}] loaded reason state: default=${this.defaultReasonWorker || "—"} fallback=${this.fallbackReasonWorker || "—"} per_chat=${this.perChat.size}`);
|
|
409
|
+
}
|
|
410
|
+
/** Persist the current routing. Best-effort: a failed write is logged, not thrown. */
|
|
411
|
+
async persistState() {
|
|
412
|
+
try {
|
|
413
|
+
await this.stateStore.save({
|
|
414
|
+
default_reason_worker: this.defaultReasonWorker,
|
|
415
|
+
fallback_reason_worker: this.fallbackReasonWorker,
|
|
416
|
+
per_chat: Object.fromEntries(this.perChat),
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
this.logger.error(`[${NS}] failed to persist reason state:`, err);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/** Fill the {chat_id} / {sender_open_id} placeholders in the reminder template. */
|
|
424
|
+
renderReminder(msg) {
|
|
425
|
+
return this.systemReminder
|
|
426
|
+
.replaceAll("{chat_id}", msg.chatId)
|
|
427
|
+
.replaceAll("{sender_open_id}", msg.senderId);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Serve the peer-callable lark.send extension: a tool invocation from reason
|
|
431
|
+
* (via the discovery/tool bridge) carrying `target` (chat_id / open_id /
|
|
432
|
+
* user_id, falling back to the default user) and `text`. Replied with a
|
|
433
|
+
* request.completed / request.failed echoing the call id.
|
|
434
|
+
*/
|
|
435
|
+
async handleLarkSend(evt) {
|
|
436
|
+
const tc = parseToolCall(evt);
|
|
437
|
+
const target = argString(tc.args, "target") ||
|
|
438
|
+
argString(tc.args, "chat_id") ||
|
|
439
|
+
argString(tc.args, "open_id") ||
|
|
440
|
+
argString(tc.args, "user_id") ||
|
|
441
|
+
this.defaultUserOpenId;
|
|
442
|
+
const text = argString(tc.args, "text");
|
|
443
|
+
if (!text) {
|
|
444
|
+
await this.base.replyFailed(tc.callerID, tc.callID, "text is required", tc.traceID);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
if (!target) {
|
|
448
|
+
await this.base.replyFailed(tc.callerID, tc.callID, "no target given and no default user open_id configured", tc.traceID);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
if (this.appendMode) {
|
|
453
|
+
await this.sendAppended(target, text);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
await this.lark.send(target, { text });
|
|
457
|
+
this.logger.info(`[${NS}] lark.send → ${target} text=${JSON.stringify(text)}`);
|
|
458
|
+
}
|
|
459
|
+
await this.base.replyCompleted(tc.callerID, tc.callID, "sent", tc.traceID);
|
|
460
|
+
}
|
|
461
|
+
catch (err) {
|
|
462
|
+
// Failed mid-append: drop the half-updated entry so the next send starts
|
|
463
|
+
// a fresh message rather than corrupting the accumulated text.
|
|
464
|
+
this.appendTargets.delete(target);
|
|
465
|
+
this.logger.error(`[${NS}] lark.send failed:`, err);
|
|
466
|
+
await this.base.replyFailed(tc.callerID, tc.callID, err.message, tc.traceID);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Append-mode send: if this target already has an in-progress message from
|
|
471
|
+
* this turn, extend it in place; otherwise post a fresh message and remember
|
|
472
|
+
* it. Repeated `lark.send` calls to the same target therefore assemble into
|
|
473
|
+
* one message instead of flooding the Feishu chat.
|
|
474
|
+
*/
|
|
475
|
+
async sendAppended(target, text) {
|
|
476
|
+
const pending = this.appendTargets.get(target);
|
|
477
|
+
if (!pending) {
|
|
478
|
+
const res = await this.lark.send(target, { text });
|
|
479
|
+
this.appendTargets.set(target, { messageId: res.messageId, text });
|
|
480
|
+
this.logger.info(`[${NS}] lark.send → ${target} text=${JSON.stringify(text)}`);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
// Join segments with a single newline so separate sends read as distinct
|
|
484
|
+
// parts, not run-on text.
|
|
485
|
+
const merged = pending.text.replace(/\s+$/, "") + "\n" + text.replace(/\s+$/, "");
|
|
486
|
+
await this.lark.editMessage(pending.messageId, merged);
|
|
487
|
+
pending.text = merged;
|
|
488
|
+
this.logger.info(`[${NS}] lark.append → ${target} (extended ${pending.messageId})`);
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Serve the `lark.reason.set` extension: route a chat to a worker (`chat_id`),
|
|
492
|
+
* set the default (neither `chat_id` nor `fallback`), or set the last-resort
|
|
493
|
+
* fallback (`fallback: true`). Persists the change so it survives a restart.
|
|
494
|
+
*/
|
|
495
|
+
async handleReasonSet(evt) {
|
|
496
|
+
const tc = parseToolCall(evt);
|
|
497
|
+
const workerId = argString(tc.args, "worker_id") || argString(tc.args, "workerId");
|
|
498
|
+
const chatId = argString(tc.args, "chat_id") || argString(tc.args, "chatId");
|
|
499
|
+
const isFallback = tc.args["fallback"] === true;
|
|
500
|
+
if (!workerId) {
|
|
501
|
+
await this.base.replyFailed(tc.callerID, tc.callID, "worker_id is required", tc.traceID);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (chatId) {
|
|
505
|
+
this.perChat.set(chatId, workerId);
|
|
506
|
+
this.logger.info(`[${NS}] lark.reason.set chat=${chatId} → ${workerId}`);
|
|
507
|
+
}
|
|
508
|
+
else if (isFallback) {
|
|
509
|
+
this.fallbackReasonWorker = workerId;
|
|
510
|
+
this.logger.info(`[${NS}] lark.reason.set fallback → ${workerId}`);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
this.defaultReasonWorker = workerId;
|
|
514
|
+
this.logger.info(`[${NS}] lark.reason.set default → ${workerId}`);
|
|
515
|
+
}
|
|
516
|
+
await this.persistState();
|
|
517
|
+
await this.base.replyCompleted(tc.callerID, tc.callID, "ok", tc.traceID);
|
|
518
|
+
}
|
|
519
|
+
/** Serve the `lark.reason.unset` extension: drop a chat override or clear the fallback. */
|
|
520
|
+
async handleReasonUnset(evt) {
|
|
521
|
+
const tc = parseToolCall(evt);
|
|
522
|
+
const chatId = argString(tc.args, "chat_id") || argString(tc.args, "chatId");
|
|
523
|
+
const isFallback = tc.args["fallback"] === true;
|
|
524
|
+
if (isFallback) {
|
|
525
|
+
this.fallbackReasonWorker = "";
|
|
526
|
+
await this.persistState();
|
|
527
|
+
await this.base.replyCompleted(tc.callerID, tc.callID, "ok", tc.traceID);
|
|
528
|
+
this.logger.info(`[${NS}] lark.reason.unset fallback cleared`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!chatId) {
|
|
532
|
+
await this.base.replyFailed(tc.callerID, tc.callID, "chat_id is required (or set fallback=true to clear the fallback)", tc.traceID);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
this.perChat.delete(chatId);
|
|
536
|
+
await this.persistState();
|
|
537
|
+
await this.base.replyCompleted(tc.callerID, tc.callID, "ok", tc.traceID);
|
|
538
|
+
this.logger.info(`[${NS}] lark.reason.unset chat=${chatId} → default ${this.defaultReasonWorker}`);
|
|
539
|
+
}
|
|
540
|
+
/** Serve the `lark.reason.get` extension: return the current routing as JSON. */
|
|
541
|
+
async handleReasonGet(evt) {
|
|
542
|
+
const tc = parseToolCall(evt);
|
|
543
|
+
const result = JSON.stringify({
|
|
544
|
+
default_reason_worker: this.defaultReasonWorker,
|
|
545
|
+
fallback_reason_worker: this.fallbackReasonWorker,
|
|
546
|
+
per_chat: Object.fromEntries(this.perChat),
|
|
547
|
+
});
|
|
548
|
+
await this.base.replyCompleted(tc.callerID, tc.callID, result, tc.traceID);
|
|
549
|
+
this.logger.info(`[${NS}] lark.reason.get default=${this.defaultReasonWorker || "—"} fallback=${this.fallbackReasonWorker || "—"} per_chat=${this.perChat.size}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@niq.run/lark-worker",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Feishu/Lark WebSocket bridge Worker for niq — forwards Feishu messages to a bound reason worker and pushes its replies back. Built on @niq.run/worker-sdk.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/niq-run/workers.git"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://www.npmjs.com/package/@niq.run/lark-worker",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "./dist/worker.js",
|
|
12
|
+
"types": "./dist/worker.d.ts",
|
|
13
|
+
"bin": {
|
|
14
|
+
"niq-lark": "./dist/start.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc -p tsconfig.build.json",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"release": "git diff --quiet && git diff --cached --quiet || { echo \"Error: git working tree not clean\" >&2; exit 1; } && npm run test && npm run typecheck && npm run build && npm version --no-git-tag-version && npm publish"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@larksuite/channel": "^0.6.0",
|
|
31
|
+
"@niq.run/worker-sdk": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.13.3",
|
|
35
|
+
"typescript": "^5.8.3",
|
|
36
|
+
"vitest": "^3.2.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT"
|
|
6
39
|
}
|