@gavinc-cn/touchstone-dsh 0.2.0 → 0.4.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 +0 -0
- package/README.md +23 -15
- package/README.zh.md +17 -12
- package/auth.py +0 -0
- package/backend/lib/access.js +44 -0
- package/backend/lib/agents.js +341 -0
- package/backend/lib/args.js +94 -0
- package/backend/lib/auth.js +195 -0
- package/backend/lib/board.js +6227 -0
- package/backend/lib/builtin-assets.js +1177 -0
- package/backend/lib/casescan.js +456 -0
- package/backend/lib/chat.js +2090 -0
- package/backend/lib/cookie.js +76 -0
- package/backend/lib/db.js +2260 -0
- package/backend/lib/dshdriver.js +1148 -0
- package/backend/lib/dshevents.js +851 -0
- package/backend/lib/export-cases.js +261 -0
- package/backend/lib/feishu.js +1747 -0
- package/backend/lib/feishu_cfg.js +262 -0
- package/backend/lib/feishu_conv.js +1484 -0
- package/backend/lib/feishu_device.js +428 -0
- package/backend/lib/feishu_inbound.js +2077 -0
- package/backend/lib/feishu_provision.js +886 -0
- package/backend/lib/feishu_rest.js +398 -0
- package/backend/lib/feishu_slash.js +568 -0
- package/backend/lib/feishu_ws.js +950 -0
- package/backend/lib/http.js +312 -0
- package/backend/lib/lib.js +973 -0
- package/backend/lib/loadcase.js +749 -0
- package/backend/lib/loadgen.js +1407 -0
- package/backend/lib/localbus.js +164 -0
- package/backend/lib/lock.js +426 -0
- package/backend/lib/proc.js +361 -0
- package/backend/lib/prompts.js +1021 -0
- package/backend/lib/pybase64.js +114 -0
- package/backend/lib/pyjson.js +214 -0
- package/backend/lib/query.js +125 -0
- package/backend/lib/rag.js +1310 -0
- package/backend/lib/runner.js +4546 -0
- package/backend/lib/seed_hint.js +40 -0
- package/backend/lib/servercaps.js +182 -0
- package/backend/lib/serverstate.js +227 -0
- package/backend/lib/sessparse.js +1176 -0
- package/backend/lib/sse.js +207 -0
- package/backend/lib/static.js +84 -0
- package/backend/lib/taskjson.js +302 -0
- package/backend/lib/waitq.js +2114 -0
- package/backend/lib/worktree.js +588 -0
- package/backend/lib/zstd-frames.js +218 -0
- package/backend/routes/admin.js +553 -0
- package/backend/routes/agents.js +149 -0
- package/backend/routes/assets.js +145 -0
- package/backend/routes/board.js +1228 -0
- package/backend/routes/feishu.js +269 -0
- package/backend/routes/feishu_app.js +249 -0
- package/backend/routes/feishu_hook.js +358 -0
- package/backend/routes/index.js +298 -0
- package/backend/routes/projects.js +1138 -0
- package/backend/routes/sessions.js +1003 -0
- package/backend/routes/tasks.js +1530 -0
- package/backend/schema/fixtures/ancient.sql +189 -0
- package/backend/schema/fixtures/pre_alter.sql +177 -0
- package/backend/schema/migrations.json +154 -0
- package/backend/schema/schema.json +1451 -0
- package/backend/schema/schema.sql +216 -0
- package/backend/server.js +505 -0
- package/board.py +96 -13
- package/builtin_assets.py +0 -0
- package/builtin_prompts/free_style/flow.md +0 -0
- package/builtin_prompts/free_style/load_test.md +0 -0
- package/builtin_prompts/free_style/templates/bug_report_template.md +0 -0
- package/builtin_prompts/free_style/templates/case_template.md +0 -0
- package/builtin_prompts/free_style/templates/index_template.md +0 -0
- package/builtin_prompts/free_style/templates/load_charts_template.md +0 -0
- package/builtin_prompts/free_style/templates/load_index_template.md +0 -0
- package/builtin_prompts/free_style/templates/load_plan_template.md +0 -0
- package/builtin_prompts/free_style/templates/load_scenario_template.md +0 -0
- package/builtin_prompts/free_style/templates/status_template.md +0 -0
- package/chat.py +33 -0
- package/db.py +294 -129
- package/dsh-plugin/cordis.patch.yml +0 -0
- package/dsh-plugin/install.sh +22 -8
- package/dsh-plugin/lib/client.bundle.js +111 -2
- package/dsh-plugin/lib/index.js +340 -29
- package/dsh-plugin/lib/pack-closure.mjs +291 -0
- package/dsh-plugin/scripts/build.mjs +0 -0
- package/dsh-plugin/scripts/prepack-check.mjs +30 -2
- package/dsh-plugin/scripts/select-bundle.mjs +0 -0
- package/dsh-plugin/src/client.js +111 -2
- package/dshevents.py +0 -0
- package/export_cases.py +0 -0
- package/extensions/dsh-autocommit/asset.json +0 -0
- package/extensions/dsh-autocommit/pkg/lib/index.mjs +0 -0
- package/extensions/dsh-autocommit/pkg/package.json +0 -0
- package/feishu.py +417 -3
- package/feishu_conv.py +0 -0
- package/lib.py +0 -0
- package/lifecycle.py +116 -13
- package/loadcase.py +0 -0
- package/loadgen.py +0 -0
- package/localbus.py +0 -0
- package/migrate_p7b.py +0 -0
- package/package.json +6 -1
- package/platcompat.py +0 -0
- package/prompts.py +0 -0
- package/rag.py +0 -0
- package/requirements.txt +0 -0
- package/server.py +155 -11
- package/touchstone.py +0 -0
- package/waitq.py +0 -0
- package/webui/dist-plugin/assets/index-3QsYQi7y.css +1 -0
- package/webui/dist-plugin/assets/index-B8AMtW7y.js +486 -0
- package/webui/dist-plugin/index.html +2 -2
- package/worktree.py +0 -0
- package/webui/dist-plugin/assets/index-JLwyZZxC.js +0 -484
- package/webui/dist-plugin/assets/index-tJ0TlvQ3.css +0 -1
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 飞书配置读助手(M7-R1;`feishu.py:31-145` 常量/`user_config`/`notify_events`/`hook_of`
|
|
3
|
+
* 与 `:1053-1071` 的 `app_config`/`inbound_status` 的对应物)。
|
|
4
|
+
*
|
|
5
|
+
* **本文件的职责边界:只做「读配置 / 选目的地」**——取用户级配置、判定项目级事件闸门、
|
|
6
|
+
* 算出群 webhook 目的地、报告入站连接状态。**不渲染通知文案、不投递、不发任何网络请求**:
|
|
7
|
+
* 渲染与出站属 `backend/lib/feishu.js`(后续 R2 车道),本文件不碰。这与 Python 的分工
|
|
8
|
+
* 一字不差:`user_config`/`app_config`/`notify_events`/`hook_of`/`inbound_status` 全是纯读,
|
|
9
|
+
* 发送线程(`feishu.py:2599+`)与 REST(`:1039+`)在别的函数里。
|
|
10
|
+
*
|
|
11
|
+
* 零 npm 依赖(只用 `./db.js` 的读口)。
|
|
12
|
+
*
|
|
13
|
+
* 与 Python 的形态差异(有意,逐条登记):
|
|
14
|
+
* - 常量在 Python 是 tuple,这里用**冻结数组**(保序、可迭代,语义最接近);对拍按元素比。
|
|
15
|
+
* - `notify_events` 在 Python 返回 `set[str]`,这里返回 **`Set`**(同构;顺序为
|
|
16
|
+
* `FEISHU_EVENTS` 的过滤序,Python set 无定义序——按集合语义比,别比顺序)。
|
|
17
|
+
* - 纯读函数末位多一个 `options = {db, dbPath, env}`(与 `db.js` 既有读口同形),
|
|
18
|
+
* 逐层透传给 db 访问函数。
|
|
19
|
+
* - 非 dict 的 `feishu_user_cfgs.json`(手工改库才会出现):Python 会在 `cfg.get(...)`
|
|
20
|
+
* 上抛 `AttributeError`,这里 `appConfig` 回 `null`(宽松)。属**已知分歧**,登记在
|
|
21
|
+
* M7-R1 未验项里;`hookOf` / `notifyEvents` 走的 `dictGet` 对 `null` 同样抛 TypeError。
|
|
22
|
+
*
|
|
23
|
+
* 【M7.5 接缝 —— 入站线程注册表】`inboundStatus` 依赖入站线程注册表(`feishu.py:1608`
|
|
24
|
+
* 的 `_WS_THREADS`),而入站 WS 长连接是 **M7.5** 才做的。本文件先把**注册表本体与
|
|
25
|
+
* 注册/注销接口**(`setInboundThread` / `clearInboundThread`)摆好:
|
|
26
|
+
* - 注册表值的契约 = 与 Python `threading.Thread` 同形的 `{ isAlive(): boolean }`;
|
|
27
|
+
* M7.5 的 WS 客户端句柄用一个两行适配器套上再注册(或直接给它加 `isAlive()`)。
|
|
28
|
+
* - **今天没有任何注册方** ⇒ `inboundStatus(userId).thread` 必然是 `false`。这是
|
|
29
|
+
* **当前线程模型的正确返回,不是占位假值**(等价于 Python 进程刚起来、还没
|
|
30
|
+
* `start_inbound_for` 时的形态:`_WS_THREADS` 空 ⇒ `thread=False`)。
|
|
31
|
+
* - M7.5 要往里塞真状态:照 `feishu.py:1632-1643` 的 `start_inbound_for` 实现,
|
|
32
|
+
* 成功拉起后 `setInboundThread(userId, handle)`;并保留同一条判据
|
|
33
|
+
* (`cur.is_alive()` 为假 = 需要重拉;凭据缺失不拉)。线程退出后 Python 侧条目
|
|
34
|
+
* 残留、靠 `is_alive()` 判死 ⇒ 这里同形(不自动清扫),差别只是 Node 要显式注销。
|
|
35
|
+
* - 未覆盖:本文件不实现 `start_inbound_all()` 的遍历拉起(M7.5)。
|
|
36
|
+
*/
|
|
37
|
+
import { getFeishuHook, getFeishuUserCfg, getProject } from './db.js';
|
|
38
|
+
|
|
39
|
+
/** 全部可选事件类别(对齐 `feishu.FEISHU_EVENTS`(feishu.py:31),顺序即渲染/对拍顺序) */
|
|
40
|
+
export const FEISHU_EVENTS = Object.freeze([
|
|
41
|
+
'blocked_interaction', 'task_failed', 'card_review',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 未单独绑定项目时的默认事件集(对齐 `feishu.FEISHU_DEFAULT_EVENTS`(feishu.py:35)):
|
|
46
|
+
* 交互等待/任务失败开、卡片待审核默认关——与设置页 `GET /api/projects/<pid>/feishu-hook`
|
|
47
|
+
* 的默认回显一致;2026-10-10 前回落分支写死全事件,那是「界面未勾选却实际会推」的实障来源。
|
|
48
|
+
*/
|
|
49
|
+
export const FEISHU_DEFAULT_EVENTS = Object.freeze(['blocked_interaction', 'task_failed']);
|
|
50
|
+
|
|
51
|
+
/** 入站线程注册表(`user_id` → 线程形对象;对齐 `feishu._WS_THREADS`,见文件头 M7.5 接缝) */
|
|
52
|
+
const INBOUND_THREADS = new Map();
|
|
53
|
+
/**
|
|
54
|
+
* `user_id` → **拉起该线程时**用的 app_id(对齐 `feishu._WS_APP_ID`,M10 2026-10-17)。
|
|
55
|
+
* 为什么需要:lark SDK 无干净停止 ⇒ 换凭据后旧连接仍在跑、只有重启站点才换;
|
|
56
|
+
* doctor 靠它判「线程绑的应用 ≠ 配置里的应用」,否则那一项会一直假绿。
|
|
57
|
+
*/
|
|
58
|
+
const INBOUND_APP_IDS = new Map();
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Python 真值口径(`""`/`0`/`None`/`False`/空容器为假)。
|
|
62
|
+
* JS 的 `[]`/`{}` 是 truthy,`if (x)` 直接写会与 oracle 分叉,故显式收口。
|
|
63
|
+
* @param {unknown} value
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
function pyTruthy(value) {
|
|
67
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
68
|
+
if (value !== null && typeof value === 'object') return Object.keys(value).length > 0;
|
|
69
|
+
return Boolean(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Python `dict.get(key, default)`:**键存在但值为 None 时返回 None**(不是 default)。
|
|
74
|
+
* 直接写 `obj[key] ?? dflt` 会把 None 折成 default,与 oracle 分叉(`default_secret: null`)。
|
|
75
|
+
* @param {object} dict
|
|
76
|
+
* @param {string} key
|
|
77
|
+
* @param {any} [dflt]
|
|
78
|
+
* @returns {any}
|
|
79
|
+
*/
|
|
80
|
+
function dictGet(dict, key, dflt = undefined) {
|
|
81
|
+
return Object.hasOwn(dict, key) ? dict[key] : dflt;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Python `str.rstrip("/")`:只削尾部的 `/`(不是 `db.js:pyRstrip` 的空白口径)。
|
|
86
|
+
* 非字符串抛 TypeError(Python 在非 str 上是 `AttributeError`——两边都是响亮失败,
|
|
87
|
+
* 只是异常类型不同,登记为已知分歧)。
|
|
88
|
+
* @param {unknown} value
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function rstripSlashes(value) {
|
|
92
|
+
if (typeof value !== 'string') {
|
|
93
|
+
throw new TypeError('base_url 必须是字符串(Python 侧非 str 会抛 AttributeError)');
|
|
94
|
+
}
|
|
95
|
+
return value.replace(/\/+$/, '');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 用户飞书配置(`feishu_user_cfgs` 表;enabled/base_url/默认 webhook/应用凭据)。
|
|
100
|
+
* 对齐 `feishu.user_config`(feishu.py:72)——就是 `db.get_feishu_user_cfg` 的薄封装。
|
|
101
|
+
* @param {number} userId
|
|
102
|
+
* @param {{db?: object, dbPath?: string, env?: NodeJS.ProcessEnv}} [options]
|
|
103
|
+
* @returns {object}
|
|
104
|
+
*/
|
|
105
|
+
export function userConfig(userId, options = {}) {
|
|
106
|
+
return getFeishuUserCfg(userId, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 用户自建应用凭据(配置里的 `app_id` + `app_secret`);任一缺失(或假值)返回 `null`。
|
|
111
|
+
* 对齐 `feishu.app_config`(feishu.py:1053):入站长连接与 REST 均以用户自己的应用为主体。
|
|
112
|
+
* 注意返回的是**整个配置对象**(不是只挑两个键)——调用方按需取键。
|
|
113
|
+
* @param {number} userId
|
|
114
|
+
* @param {{db?: object, dbPath?: string, env?: NodeJS.ProcessEnv}} [options]
|
|
115
|
+
* @returns {object|null}
|
|
116
|
+
*/
|
|
117
|
+
export function appConfig(userId, options = {}) {
|
|
118
|
+
const cfg = userConfig(userId, options);
|
|
119
|
+
if (pyTruthy(cfg.app_id) && pyTruthy(cfg.app_secret)) return cfg;
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 项目级推送事件闸门(**全通道共用**),对齐 `feishu.notify_events`(feishu.py:78)。
|
|
125
|
+
*
|
|
126
|
+
* 口径(先到先算,逐条照抄):
|
|
127
|
+
* 1. 先看**用户级总控** `push_enabled`(缺键按开):关 ⇒ 空集,一票否决(项目行开着也静默);
|
|
128
|
+
* 2. 有绑定行:`enabled=0` ⇒ 空集(显式关闭,不回落用户配置);否则取行内勾选的事件
|
|
129
|
+
* (`row.events` 是逗号拼接串,按 `FEISHU_EVENTS` 全集过滤,未知类别自动丢);
|
|
130
|
+
* 行内勾空 ⇒ 空集(=都不推);
|
|
131
|
+
* 3. 无绑定行:回落项目所有者的 `FEISHU_DEFAULT_EVENTS`,并受用户级旧开关 `enabled`
|
|
132
|
+
* (缺键按开)约束;项目被删(取不到 owner)时按 `user_id=0` 的配置判定。
|
|
133
|
+
*
|
|
134
|
+
* 返回 `Set<string>`;空集 = 该项目不推任何事件(调用方据此短路,见 `hookOf`)。
|
|
135
|
+
* @param {number} projectId
|
|
136
|
+
* @param {{db?: object, dbPath?: string, env?: NodeJS.ProcessEnv}} [options]
|
|
137
|
+
* @returns {Set<string>}
|
|
138
|
+
*/
|
|
139
|
+
export function notifyEvents(projectId, options = {}) {
|
|
140
|
+
const row = getFeishuHook(projectId, options);
|
|
141
|
+
const proj = getProject(projectId, options);
|
|
142
|
+
const ownerId = proj ? proj.user_id : 0;
|
|
143
|
+
const cfg = userConfig(ownerId, options);
|
|
144
|
+
if (!pyTruthy(dictGet(cfg, 'push_enabled', true))) return new Set(); // 总控:一票否决
|
|
145
|
+
if (row !== null) {
|
|
146
|
+
if (!pyTruthy(row.enabled)) return new Set();
|
|
147
|
+
const events = row.events || '';
|
|
148
|
+
return new Set(FEISHU_EVENTS.filter((event) => events.includes(event)));
|
|
149
|
+
}
|
|
150
|
+
if (!pyTruthy(dictGet(cfg, 'enabled', true))) return new Set();
|
|
151
|
+
return new Set(FEISHU_DEFAULT_EVENTS);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 项目推送**群 webhook 目的地**,对齐 `feishu.hook_of`(feishu.py:117)。
|
|
156
|
+
* 绑定行 URL 优先;行内无 url(或无绑定行)时回落**项目所有者**的用户级默认 webhook
|
|
157
|
+
* (回落要求其总开关 `enabled` 开着且配了 `default_webhook`)。
|
|
158
|
+
*
|
|
159
|
+
* 返回 `{target, secret, events, base_url, user_id}` 或 `null`(不推)。三处易错点照抄:
|
|
160
|
+
* - 事件闸门**只**由 `notifyEvents` 决定,空集直接 `null`(回落分支同样尊重项目勾选);
|
|
161
|
+
* - `base_url` 只在项目存在时算(项目被删 ⇒ 空串),且取 `rstrip("/")` 后的值;
|
|
162
|
+
* - 回落的 `secret` 用 `dictGet(cfg, "default_secret", "")`(键存在但为 null ⇒ 回 null,
|
|
163
|
+
* 不是 ""——Python `.get(k, d)` 的语义)。
|
|
164
|
+
* @param {number} projectId
|
|
165
|
+
* @param {{db?: object, dbPath?: string, env?: NodeJS.ProcessEnv}} [options]
|
|
166
|
+
* @returns {{target: string, secret: any, events: Set<string>, base_url: string,
|
|
167
|
+
* user_id: number}|null}
|
|
168
|
+
*/
|
|
169
|
+
export function hookOf(projectId, options = {}) {
|
|
170
|
+
const events = notifyEvents(projectId, options);
|
|
171
|
+
if (events.size === 0) return null;
|
|
172
|
+
const row = getFeishuHook(projectId, options);
|
|
173
|
+
const proj = getProject(projectId, options); // 所有者为 0 时(项目被删)回落 userConfig(0)
|
|
174
|
+
const ownerId = proj ? proj.user_id : 0;
|
|
175
|
+
const baseUrl = proj ? rstripSlashes(userConfig(ownerId, options).base_url || '') : '';
|
|
176
|
+
if (row !== null && row.webhook_url) {
|
|
177
|
+
return {
|
|
178
|
+
target: row.webhook_url,
|
|
179
|
+
secret: row.webhook_secret || '',
|
|
180
|
+
events,
|
|
181
|
+
base_url: baseUrl,
|
|
182
|
+
user_id: ownerId,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
const cfg = userConfig(ownerId, options);
|
|
186
|
+
if (!pyTruthy(dictGet(cfg, 'enabled', true))) return null;
|
|
187
|
+
if (!pyTruthy(dictGet(cfg, 'default_webhook'))) return null;
|
|
188
|
+
return {
|
|
189
|
+
target: dictGet(cfg, 'default_webhook'),
|
|
190
|
+
secret: dictGet(cfg, 'default_secret', ''),
|
|
191
|
+
events,
|
|
192
|
+
base_url: baseUrl,
|
|
193
|
+
user_id: ownerId,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 用户入站长连接状态摘要(设置页展示用),对齐 `feishu.inbound_status`(feishu.py:1062):
|
|
199
|
+
* `configured` = 应用凭据齐全(`appConfig` 非 null);`thread` = 注册表里的线程还活着。
|
|
200
|
+
*
|
|
201
|
+
* `thread: true` 只代表线程还在 `client.start()` 内(SDK 内置重连),连接失败细节看 server.log。
|
|
202
|
+
* **形状是 `GET /api/me/feishu-cfg` 响应的子对象 ⇒ 只许这两键**(「线程绑的哪个应用」
|
|
203
|
+
* 不放进这里,见 `inboundThreadAppId`;加键会改对外契约,node 的形状判据当场会红)。
|
|
204
|
+
* @param {number} userId
|
|
205
|
+
* @param {{db?: object, dbPath?: string, env?: NodeJS.ProcessEnv}} [options]
|
|
206
|
+
* @returns {{configured: boolean, thread: boolean}}
|
|
207
|
+
*/
|
|
208
|
+
export function inboundStatus(userId, options = {}) {
|
|
209
|
+
const cfg = appConfig(userId, options);
|
|
210
|
+
const thread = INBOUND_THREADS.get(userId);
|
|
211
|
+
return {
|
|
212
|
+
configured: cfg !== null,
|
|
213
|
+
thread: thread !== undefined && thread !== null && Boolean(thread.isAlive()),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* **拉起入站线程时**用的 app_id(对齐 `feishu.inbound_thread_app_id`,M10 2026-10-17):
|
|
219
|
+
* 换凭据后旧连接仍在跑、只有重启站点才换 ⇒ doctor 靠它判「线程绑的应用 ≠ 配置里的应用」
|
|
220
|
+
* 的假绿。空串 = 本进程没为该用户拉过线程 ⇒ 调用方不许据此告警。
|
|
221
|
+
* 刻意**不并进 `inboundStatus`**:那是 `GET /api/me/feishu-cfg` 的响应子对象。
|
|
222
|
+
* @param {number} userId
|
|
223
|
+
* @returns {string}
|
|
224
|
+
*/
|
|
225
|
+
export function inboundThreadAppId(userId) {
|
|
226
|
+
return INBOUND_APP_IDS.get(userId) || '';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 【M7.5 接缝】登记某用户的入站线程(对齐 `feishu._WS_THREADS[user_id] = t`,feishu.py:1643)。
|
|
231
|
+
* 值的契约:`{ isAlive(): boolean }`(与 `threading.Thread` 同形)。幂等覆盖,不校验重复。
|
|
232
|
+
* @param {number} userId
|
|
233
|
+
* @param {{isAlive: () => boolean}} thread
|
|
234
|
+
* @param {string} [appId] 拉起该线程时用的 app_id(对齐 `feishu._WS_APP_ID[user_id]`)
|
|
235
|
+
*/
|
|
236
|
+
export function setInboundThread(userId, thread, appId = '') {
|
|
237
|
+
INBOUND_THREADS.set(userId, thread);
|
|
238
|
+
INBOUND_APP_IDS.set(userId, appId || '');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 【M7.5 接缝】注销某用户的入站线程(线程退出/凭据被清时调用)。
|
|
243
|
+
* Python 侧没有对应动作(daemon 线程退出后条目残留、靠 `is_alive()` 判死);
|
|
244
|
+
* 这里是 Node 侧更诚实的等价物,`inboundStatus` 的返回形态不变。
|
|
245
|
+
* @param {number} userId
|
|
246
|
+
*/
|
|
247
|
+
export function clearInboundThread(userId) {
|
|
248
|
+
INBOUND_THREADS.delete(userId);
|
|
249
|
+
INBOUND_APP_IDS.delete(userId);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 【M11 接缝】取某用户当前登记的入站 runner(**内部用**:M11 起要在进程内停连接,
|
|
254
|
+
* `isAlive()` 不够——得拿到 runner 背后的 ws 客户端,见 `feishu_inbound.js:stopInboundFor`)。
|
|
255
|
+
* Python 侧没有对应函数:那边直接读模块级 `_WS_THREADS`/`_WS_CLIENTS` 两个字典。
|
|
256
|
+
* **不并进 `inboundStatus`**(那是 `GET /api/me/feishu-cfg` 的响应子对象,形状只许两键)。
|
|
257
|
+
* @param {number} userId
|
|
258
|
+
* @returns {{isAlive: () => boolean}|null}
|
|
259
|
+
*/
|
|
260
|
+
export function inboundRunner(userId) {
|
|
261
|
+
return INBOUND_THREADS.get(userId) || null;
|
|
262
|
+
}
|