engine7 7.1.36 → 7.1.38
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/dist/cli.mjs +16 -0
- package/dist/engine-startup.mjs +431 -144
- package/dist/main.mjs +352 -65
- package/package.json +1 -1
- package/templates/config.template.json +3 -0
package/dist/engine-startup.mjs
CHANGED
|
@@ -816,6 +816,9 @@ function toHookContext(ctx) {
|
|
|
816
816
|
};
|
|
817
817
|
}
|
|
818
818
|
async function executeOne(tc, ctx, signal) {
|
|
819
|
+
if (signal?.aborted) {
|
|
820
|
+
return { call: tc, result: { content: "[interrupted by user /stop]", isError: false }, summary: null };
|
|
821
|
+
}
|
|
819
822
|
const tool = registry.get(tc.function.name);
|
|
820
823
|
if (!tool) {
|
|
821
824
|
return { call: tc, result: { content: `Unknown tool: ${tc.function.name}`, isError: true }, summary: null };
|
|
@@ -2113,6 +2116,85 @@ var init_compact2 = __esm({
|
|
|
2113
2116
|
}
|
|
2114
2117
|
});
|
|
2115
2118
|
|
|
2119
|
+
// src/config/live.ts
|
|
2120
|
+
var live_exports = {};
|
|
2121
|
+
__export(live_exports, {
|
|
2122
|
+
liveConfig: () => liveConfig
|
|
2123
|
+
});
|
|
2124
|
+
var LiveConfigClass, liveConfig;
|
|
2125
|
+
var init_live = __esm({
|
|
2126
|
+
"src/config/live.ts"() {
|
|
2127
|
+
"use strict";
|
|
2128
|
+
LiveConfigClass = class {
|
|
2129
|
+
current = null;
|
|
2130
|
+
/** 启动时注入(替代 registry.config = config) */
|
|
2131
|
+
init(config) {
|
|
2132
|
+
this.current = config;
|
|
2133
|
+
}
|
|
2134
|
+
/** 取整个 config 对象(只读引用,不要缓存) */
|
|
2135
|
+
all() {
|
|
2136
|
+
if (!this.current) {
|
|
2137
|
+
throw new Error("[liveConfig] not initialized \u2014 call liveConfig.init() first");
|
|
2138
|
+
}
|
|
2139
|
+
return this.current;
|
|
2140
|
+
}
|
|
2141
|
+
/** 安全取子段('services.voice-chat.start' → current.services.voice-chat.start) */
|
|
2142
|
+
get(dotPath) {
|
|
2143
|
+
if (!this.current) return void 0;
|
|
2144
|
+
return dotPath.split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], this.current);
|
|
2145
|
+
}
|
|
2146
|
+
/** reload 时原地更新(Object.assign 保持引用不变) */
|
|
2147
|
+
assign(newConfig) {
|
|
2148
|
+
if (!this.current) {
|
|
2149
|
+
this.current = newConfig;
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
Object.assign(this.current, newConfig);
|
|
2153
|
+
}
|
|
2154
|
+
/** 改活树上的值;文件承载路径同步持久化(read-modify-write 回 config 文件) */
|
|
2155
|
+
async set(dotPath, val) {
|
|
2156
|
+
if (!this.current) throw new Error("[liveConfig] not initialized \u2014 call liveConfig.init() first");
|
|
2157
|
+
const keys = dotPath.split(".");
|
|
2158
|
+
let obj = this.current;
|
|
2159
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2160
|
+
if (obj[keys[i]] == null) obj[keys[i]] = {};
|
|
2161
|
+
obj = obj[keys[i]];
|
|
2162
|
+
}
|
|
2163
|
+
obj[keys[keys.length - 1]] = val;
|
|
2164
|
+
await this.persistToFile(dotPath, val);
|
|
2165
|
+
}
|
|
2166
|
+
/** 把改动写回 config 文件对应段(找不到文件路径则只内存生效) */
|
|
2167
|
+
async persistToFile(dotPath, val) {
|
|
2168
|
+
const fs43 = await import("node:fs");
|
|
2169
|
+
const cfgPath = this.current?._configFilePath;
|
|
2170
|
+
if (!cfgPath || !fs43.existsSync(cfgPath)) {
|
|
2171
|
+
console.warn(`[liveConfig] set: no config file path, in-memory only (${dotPath})`);
|
|
2172
|
+
return;
|
|
2173
|
+
}
|
|
2174
|
+
try {
|
|
2175
|
+
const raw = JSON.parse(fs43.readFileSync(cfgPath, "utf-8"));
|
|
2176
|
+
const keys = dotPath.split(".");
|
|
2177
|
+
let o = raw;
|
|
2178
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
2179
|
+
if (o[keys[i]] == null) o[keys[i]] = {};
|
|
2180
|
+
o = o[keys[i]];
|
|
2181
|
+
}
|
|
2182
|
+
o[keys[keys.length - 1]] = val;
|
|
2183
|
+
fs43.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
2184
|
+
console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
|
|
2185
|
+
} catch (e) {
|
|
2186
|
+
console.warn(`[liveConfig] persist failed (${dotPath}): ${e.message}`);
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
/** 是否已初始化 */
|
|
2190
|
+
isReady() {
|
|
2191
|
+
return this.current !== null;
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2194
|
+
liveConfig = new LiveConfigClass();
|
|
2195
|
+
}
|
|
2196
|
+
});
|
|
2197
|
+
|
|
2116
2198
|
// src/memory/memdir/paths.ts
|
|
2117
2199
|
var paths_exports = {};
|
|
2118
2200
|
__export(paths_exports, {
|
|
@@ -2805,7 +2887,8 @@ var init_query = __esm({
|
|
|
2805
2887
|
init_constants();
|
|
2806
2888
|
init_deferred();
|
|
2807
2889
|
init_hooks();
|
|
2808
|
-
|
|
2890
|
+
init_live();
|
|
2891
|
+
QueryEngine = class _QueryEngine {
|
|
2809
2892
|
constructor(provider, options) {
|
|
2810
2893
|
this.provider = provider;
|
|
2811
2894
|
this.options = options;
|
|
@@ -2816,6 +2899,33 @@ var init_query = __esm({
|
|
|
2816
2899
|
}
|
|
2817
2900
|
provider;
|
|
2818
2901
|
options;
|
|
2902
|
+
// === voice-chat 占位状态(#194 v4)===
|
|
2903
|
+
static placeholderQueries = /* @__PURE__ */ new Map();
|
|
2904
|
+
// queryId → ts(3 分钟过期)
|
|
2905
|
+
static placeholderCounter = 0;
|
|
2906
|
+
/** 工具>5s 触发:同一 query 只占位一次 + 30% 概率 + 轮换文案(防口头禅) */
|
|
2907
|
+
static async tryVoicePlaceholder(context) {
|
|
2908
|
+
if (context?.channel !== "voice-chat") return;
|
|
2909
|
+
if (!(liveConfig.get("voiceChat.placeholder.enabled") ?? true)) return;
|
|
2910
|
+
const qid = context.sessionId || "default";
|
|
2911
|
+
const now = Date.now();
|
|
2912
|
+
for (const [k, v] of _QueryEngine.placeholderQueries) if (now - v > 3 * 60 * 1e3) _QueryEngine.placeholderQueries.delete(k);
|
|
2913
|
+
if (_QueryEngine.placeholderQueries.has(qid)) return;
|
|
2914
|
+
if (Math.random() > 0.3) return;
|
|
2915
|
+
_QueryEngine.placeholderQueries.set(qid, now);
|
|
2916
|
+
const lines = ["\u7B49\u6211\u67E5\u4E0B\u54C8", "\u7A0D\u7B49\uFF0C\u6211\u770B\u4E00\u773C", "\u6211\u53BB\u7FFB\u7FFB", "\u7B49\u4E0B\u54E6", "\u55EF\u2026\u6211\u53BB\u770B\u770B"];
|
|
2917
|
+
const line = lines[_QueryEngine.placeholderCounter++ % lines.length];
|
|
2918
|
+
try {
|
|
2919
|
+
await fetch("https://localhost:8116/voice-reply", {
|
|
2920
|
+
method: "POST",
|
|
2921
|
+
headers: { "Content-Type": "application/json" },
|
|
2922
|
+
body: JSON.stringify({ text: line, isPlaceholder: true })
|
|
2923
|
+
});
|
|
2924
|
+
console.log(`[vc-placeholder] sent: ${line}`);
|
|
2925
|
+
} catch (e) {
|
|
2926
|
+
console.warn(`[vc-placeholder] send failed: ${e.message}`);
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2819
2929
|
abortController = null;
|
|
2820
2930
|
/** 外部 pre-query abort controller(handle-query 入口注册,弥补 query() 前的空窗期) */
|
|
2821
2931
|
preQueryAbort = null;
|
|
@@ -3170,7 +3280,18 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
|
|
|
3170
3280
|
console.warn(`[hook] PreToolUse ${toolName} error: ${e.message}`);
|
|
3171
3281
|
}
|
|
3172
3282
|
}
|
|
3173
|
-
const
|
|
3283
|
+
const vcPlaceholderOn = context?.channel === "voice-chat" && (liveConfig.get("voiceChat.placeholder.enabled") ?? true);
|
|
3284
|
+
const vcTimer = vcPlaceholderOn ? setTimeout(() => {
|
|
3285
|
+
_QueryEngine.tryVoicePlaceholder(context).catch(() => {
|
|
3286
|
+
});
|
|
3287
|
+
}, 5e3) : null;
|
|
3288
|
+
let toolRes;
|
|
3289
|
+
try {
|
|
3290
|
+
toolRes = await executeTools(toolCalls, ac.signal, toolCtx);
|
|
3291
|
+
} finally {
|
|
3292
|
+
if (vcTimer) clearTimeout(vcTimer);
|
|
3293
|
+
}
|
|
3294
|
+
const { messages: toolResults, summaries } = toolRes;
|
|
3174
3295
|
for (const s of summaries) {
|
|
3175
3296
|
try {
|
|
3176
3297
|
const postResult = await executePostToolUseHooks(s.tool, {}, s.summary, hookCtx, ac.signal);
|
|
@@ -3310,85 +3431,6 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
|
|
|
3310
3431
|
}
|
|
3311
3432
|
});
|
|
3312
3433
|
|
|
3313
|
-
// src/config/live.ts
|
|
3314
|
-
var live_exports = {};
|
|
3315
|
-
__export(live_exports, {
|
|
3316
|
-
liveConfig: () => liveConfig
|
|
3317
|
-
});
|
|
3318
|
-
var LiveConfigClass, liveConfig;
|
|
3319
|
-
var init_live = __esm({
|
|
3320
|
-
"src/config/live.ts"() {
|
|
3321
|
-
"use strict";
|
|
3322
|
-
LiveConfigClass = class {
|
|
3323
|
-
current = null;
|
|
3324
|
-
/** 启动时注入(替代 registry.config = config) */
|
|
3325
|
-
init(config) {
|
|
3326
|
-
this.current = config;
|
|
3327
|
-
}
|
|
3328
|
-
/** 取整个 config 对象(只读引用,不要缓存) */
|
|
3329
|
-
all() {
|
|
3330
|
-
if (!this.current) {
|
|
3331
|
-
throw new Error("[liveConfig] not initialized \u2014 call liveConfig.init() first");
|
|
3332
|
-
}
|
|
3333
|
-
return this.current;
|
|
3334
|
-
}
|
|
3335
|
-
/** 安全取子段('services.voice-chat.start' → current.services.voice-chat.start) */
|
|
3336
|
-
get(dotPath) {
|
|
3337
|
-
if (!this.current) return void 0;
|
|
3338
|
-
return dotPath.split(".").reduce((acc, key) => acc == null ? void 0 : acc[key], this.current);
|
|
3339
|
-
}
|
|
3340
|
-
/** reload 时原地更新(Object.assign 保持引用不变) */
|
|
3341
|
-
assign(newConfig) {
|
|
3342
|
-
if (!this.current) {
|
|
3343
|
-
this.current = newConfig;
|
|
3344
|
-
return;
|
|
3345
|
-
}
|
|
3346
|
-
Object.assign(this.current, newConfig);
|
|
3347
|
-
}
|
|
3348
|
-
/** 改活树上的值;文件承载路径同步持久化(read-modify-write 回 config 文件) */
|
|
3349
|
-
async set(dotPath, val) {
|
|
3350
|
-
if (!this.current) throw new Error("[liveConfig] not initialized \u2014 call liveConfig.init() first");
|
|
3351
|
-
const keys = dotPath.split(".");
|
|
3352
|
-
let obj = this.current;
|
|
3353
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
3354
|
-
if (obj[keys[i]] == null) obj[keys[i]] = {};
|
|
3355
|
-
obj = obj[keys[i]];
|
|
3356
|
-
}
|
|
3357
|
-
obj[keys[keys.length - 1]] = val;
|
|
3358
|
-
await this.persistToFile(dotPath, val);
|
|
3359
|
-
}
|
|
3360
|
-
/** 把改动写回 config 文件对应段(找不到文件路径则只内存生效) */
|
|
3361
|
-
async persistToFile(dotPath, val) {
|
|
3362
|
-
const fs43 = await import("node:fs");
|
|
3363
|
-
const cfgPath = this.current?._configFilePath;
|
|
3364
|
-
if (!cfgPath || !fs43.existsSync(cfgPath)) {
|
|
3365
|
-
console.warn(`[liveConfig] set: no config file path, in-memory only (${dotPath})`);
|
|
3366
|
-
return;
|
|
3367
|
-
}
|
|
3368
|
-
try {
|
|
3369
|
-
const raw = JSON.parse(fs43.readFileSync(cfgPath, "utf-8"));
|
|
3370
|
-
const keys = dotPath.split(".");
|
|
3371
|
-
let o = raw;
|
|
3372
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
3373
|
-
if (o[keys[i]] == null) o[keys[i]] = {};
|
|
3374
|
-
o = o[keys[i]];
|
|
3375
|
-
}
|
|
3376
|
-
o[keys[keys.length - 1]] = val;
|
|
3377
|
-
fs43.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
3378
|
-
console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
|
|
3379
|
-
} catch (e) {
|
|
3380
|
-
console.warn(`[liveConfig] persist failed (${dotPath}): ${e.message}`);
|
|
3381
|
-
}
|
|
3382
|
-
}
|
|
3383
|
-
/** 是否已初始化 */
|
|
3384
|
-
isReady() {
|
|
3385
|
-
return this.current !== null;
|
|
3386
|
-
}
|
|
3387
|
-
};
|
|
3388
|
-
liveConfig = new LiveConfigClass();
|
|
3389
|
-
}
|
|
3390
|
-
});
|
|
3391
|
-
|
|
3392
3434
|
// src/config/features.ts
|
|
3393
3435
|
function getFeature(key) {
|
|
3394
3436
|
const v = liveConfig.get(`agents.defaults.features.${key}`);
|
|
@@ -5165,11 +5207,11 @@ function buildPayload(messages, cfg, sessionId) {
|
|
|
5165
5207
|
const meta = m.role === "user" ? parseMeta(text) : null;
|
|
5166
5208
|
const senderName = meta?.senderName ?? (m.role === "assistant" ? cfg.agentName : void 0) ?? m.role;
|
|
5167
5209
|
const rawRole = m.role === "toolResult" ? "tool" : m.role;
|
|
5168
|
-
const
|
|
5210
|
+
const role = rawRole === "user" ? "user" : "assistant";
|
|
5169
5211
|
return {
|
|
5170
5212
|
sender_id: cfg.appId,
|
|
5171
5213
|
sender_name: senderName,
|
|
5172
|
-
role
|
|
5214
|
+
role,
|
|
5173
5215
|
timestamp: Date.now(),
|
|
5174
5216
|
content: text
|
|
5175
5217
|
};
|
|
@@ -12352,6 +12394,7 @@ var BASE_DELAY_MS = 500;
|
|
|
12352
12394
|
var MAX_DELAY_MS = 32e3;
|
|
12353
12395
|
var DEFAULT_TIMEOUT_MS = 6e5;
|
|
12354
12396
|
var READ_TIMEOUT_MS = 6e4;
|
|
12397
|
+
var FIRST_READ_TIMEOUT_MS = parseInt(process.env.API_TIMEOUT_MS || "", 10) || 6e5;
|
|
12355
12398
|
function sleep(ms, signal) {
|
|
12356
12399
|
return new Promise((resolve10, reject) => {
|
|
12357
12400
|
if (signal?.aborted) {
|
|
@@ -12615,7 +12658,15 @@ var OpenAIProvider = class {
|
|
|
12615
12658
|
try {
|
|
12616
12659
|
while (true) {
|
|
12617
12660
|
if (params.signal?.aborted) break;
|
|
12618
|
-
|
|
12661
|
+
let readRes;
|
|
12662
|
+
try {
|
|
12663
|
+
readRes = await readWithTimeout(reader, firstChunk ? FIRST_READ_TIMEOUT_MS : READ_TIMEOUT_MS, params.signal);
|
|
12664
|
+
} catch (readErr) {
|
|
12665
|
+
if (params.signal?.aborted) throw readErr;
|
|
12666
|
+
yield { type: "error", error: readErr.message };
|
|
12667
|
+
return;
|
|
12668
|
+
}
|
|
12669
|
+
const { done: done2, value } = readRes;
|
|
12619
12670
|
if (done2) break;
|
|
12620
12671
|
if (firstChunk) {
|
|
12621
12672
|
firstChunk = false;
|
|
@@ -12899,6 +12950,7 @@ var AnthropicProvider = class {
|
|
|
12899
12950
|
const reader = response.body.getReader();
|
|
12900
12951
|
const decoder = new TextDecoder();
|
|
12901
12952
|
let buffer = "";
|
|
12953
|
+
let firstChunk = true;
|
|
12902
12954
|
const toolUseBlocks = /* @__PURE__ */ new Map();
|
|
12903
12955
|
let doneYielded = false;
|
|
12904
12956
|
const handleData = function* (data) {
|
|
@@ -12988,8 +13040,20 @@ var AnthropicProvider = class {
|
|
|
12988
13040
|
try {
|
|
12989
13041
|
while (true) {
|
|
12990
13042
|
if (params.signal?.aborted) break;
|
|
12991
|
-
|
|
13043
|
+
let readRes;
|
|
13044
|
+
try {
|
|
13045
|
+
readRes = await readWithTimeout(reader, firstChunk ? FIRST_READ_TIMEOUT_MS : READ_TIMEOUT_MS, params.signal);
|
|
13046
|
+
} catch (readErr) {
|
|
13047
|
+
if (params.signal?.aborted) throw readErr;
|
|
13048
|
+
yield { type: "error", error: readErr.message };
|
|
13049
|
+
return;
|
|
13050
|
+
}
|
|
13051
|
+
const { done: done2, value } = readRes;
|
|
12992
13052
|
if (done2) break;
|
|
13053
|
+
if (firstChunk) {
|
|
13054
|
+
firstChunk = false;
|
|
13055
|
+
console.log(`[anthropic] first chunk received (${value?.length ?? 0}B)`);
|
|
13056
|
+
}
|
|
12993
13057
|
buffer += decoder.decode(value, { stream: true });
|
|
12994
13058
|
const lines = buffer.split("\n");
|
|
12995
13059
|
buffer = lines.pop();
|
|
@@ -13201,7 +13265,15 @@ var GeminiProvider = class {
|
|
|
13201
13265
|
try {
|
|
13202
13266
|
while (true) {
|
|
13203
13267
|
if (params.signal?.aborted) break;
|
|
13204
|
-
|
|
13268
|
+
let readRes;
|
|
13269
|
+
try {
|
|
13270
|
+
readRes = await readWithTimeout(reader, firstChunk ? FIRST_READ_TIMEOUT_MS : READ_TIMEOUT_MS, params.signal);
|
|
13271
|
+
} catch (readErr) {
|
|
13272
|
+
if (params.signal?.aborted) throw readErr;
|
|
13273
|
+
yield { type: "error", error: readErr.message };
|
|
13274
|
+
return;
|
|
13275
|
+
}
|
|
13276
|
+
const { done: done2, value } = readRes;
|
|
13205
13277
|
if (done2) break;
|
|
13206
13278
|
if (firstChunk) {
|
|
13207
13279
|
firstChunk = false;
|
|
@@ -14057,7 +14129,9 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14057
14129
|
}
|
|
14058
14130
|
const filename = attachment.filename || path46.basename(attachment.path);
|
|
14059
14131
|
const fileBuffer = fs43.readFileSync(attachment.path);
|
|
14060
|
-
const
|
|
14132
|
+
const safeTarget = target || this.config.defaultTarget || "";
|
|
14133
|
+
if (!safeTarget) throw new Error("Feishu sendFile: no target (\u4E3B\u52A8\u53D1\u9001\u9700\u914D\u7F6E defaultTarget \u6216\u4F20\u5165 target)");
|
|
14134
|
+
const receiveIdType = safeTarget.startsWith("ou_") ? "open_id" : "chat_id";
|
|
14061
14135
|
const mimeType = attachment.mimeType || "application/octet-stream";
|
|
14062
14136
|
if (mimeType.startsWith("image/")) {
|
|
14063
14137
|
const imageKey = await this._uploadImage(fileBuffer, filename);
|
|
@@ -14065,19 +14139,45 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14065
14139
|
const postContent = this._buildImagePost(message, imageKey);
|
|
14066
14140
|
await this.client.im.message.create({
|
|
14067
14141
|
params: { receive_id_type: receiveIdType },
|
|
14068
|
-
data: { receive_id:
|
|
14142
|
+
data: { receive_id: safeTarget, msg_type: "post", content: JSON.stringify(postContent) }
|
|
14069
14143
|
});
|
|
14070
14144
|
} else {
|
|
14071
14145
|
await this.client.im.message.create({
|
|
14072
14146
|
params: { receive_id_type: receiveIdType },
|
|
14073
|
-
data: { receive_id:
|
|
14147
|
+
data: { receive_id: safeTarget, msg_type: "image", content: JSON.stringify({ image_key: imageKey }) }
|
|
14148
|
+
});
|
|
14149
|
+
}
|
|
14150
|
+
} else if (mimeType.startsWith("audio/")) {
|
|
14151
|
+
const isOpus = filename.endsWith(".opus") || filename.endsWith(".ogg") || mimeType === "audio/opus";
|
|
14152
|
+
if (!isOpus) {
|
|
14153
|
+
console.warn(`[feishu:sendFile] audio '${filename}' \u4E0D\u662Fopus\uFF0C\u964D\u7EA7\u4E3A\u6587\u4EF6\u6D88\u606F\uFF08\u975E\u8BED\u97F3\u6761\uFF09`);
|
|
14154
|
+
const fileKey = await this._uploadFile(fileBuffer, filename);
|
|
14155
|
+
await this.client.im.message.create({
|
|
14156
|
+
params: { receive_id_type: receiveIdType },
|
|
14157
|
+
data: { receive_id: safeTarget, msg_type: "file", content: JSON.stringify({ file_key: fileKey }) }
|
|
14158
|
+
});
|
|
14159
|
+
} else {
|
|
14160
|
+
let durationMs = Math.round(fileBuffer.length / 3);
|
|
14161
|
+
try {
|
|
14162
|
+
const { execFile: execFile3 } = await import("node:child_process");
|
|
14163
|
+
const { promisify: promisify3 } = await import("node:util");
|
|
14164
|
+
const execFileAsync2 = promisify3(execFile3);
|
|
14165
|
+
const probe = await execFileAsync2("ffprobe", ["-v", "quiet", "-show_format", "-print_format", "json", attachment.path], { timeout: 1e4 });
|
|
14166
|
+
const fmt = JSON.parse(probe.stdout);
|
|
14167
|
+
if (fmt?.format?.duration) durationMs = Math.round(parseFloat(fmt.format.duration) * 1e3);
|
|
14168
|
+
} catch {
|
|
14169
|
+
}
|
|
14170
|
+
const audioKey = await this._uploadAudio(fileBuffer, filename.endsWith(".opus") ? filename : filename.replace(/\.[^.]+$/, ".opus"), durationMs);
|
|
14171
|
+
await this.client.im.message.create({
|
|
14172
|
+
params: { receive_id_type: receiveIdType },
|
|
14173
|
+
data: { receive_id: safeTarget, msg_type: "audio", content: JSON.stringify({ file_key: audioKey }) }
|
|
14074
14174
|
});
|
|
14075
14175
|
}
|
|
14076
14176
|
} else {
|
|
14077
14177
|
const fileKey = await this._uploadFile(fileBuffer, filename);
|
|
14078
14178
|
await this.client.im.message.create({
|
|
14079
14179
|
params: { receive_id_type: receiveIdType },
|
|
14080
|
-
data: { receive_id:
|
|
14180
|
+
data: { receive_id: safeTarget, msg_type: "file", content: JSON.stringify({ file_key: fileKey }) }
|
|
14081
14181
|
});
|
|
14082
14182
|
}
|
|
14083
14183
|
if (message && mimeType.startsWith("image/")) {
|
|
@@ -14086,7 +14186,7 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14086
14186
|
for (const chunk of chunks) {
|
|
14087
14187
|
await this.client.im.message.create({
|
|
14088
14188
|
params: { receive_id_type: receiveIdType },
|
|
14089
|
-
data: { receive_id:
|
|
14189
|
+
data: { receive_id: safeTarget, msg_type: "text", content: JSON.stringify({ text: chunk }) }
|
|
14090
14190
|
});
|
|
14091
14191
|
}
|
|
14092
14192
|
}
|
|
@@ -14130,6 +14230,28 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14130
14230
|
if (!fileKey) throw new Error(`Feishu file upload failed: ${JSON.stringify(data)}`);
|
|
14131
14231
|
return fileKey;
|
|
14132
14232
|
}
|
|
14233
|
+
/** #217: 上传语音(opus)——飞书语音条必须 file_type=opus + duration(毫秒) */
|
|
14234
|
+
async _uploadAudio(buffer, filename, durationMs) {
|
|
14235
|
+
const token = await this._getTenantToken();
|
|
14236
|
+
const resp = await fetch("https://open.feishu.cn/open-apis/im/v1/files", {
|
|
14237
|
+
method: "POST",
|
|
14238
|
+
headers: {
|
|
14239
|
+
"Authorization": `Bearer ${token}`
|
|
14240
|
+
},
|
|
14241
|
+
body: (() => {
|
|
14242
|
+
const formData = new FormData();
|
|
14243
|
+
formData.append("file_type", "opus");
|
|
14244
|
+
formData.append("file_name", filename);
|
|
14245
|
+
formData.append("duration", String(Math.max(1, Math.round(durationMs))));
|
|
14246
|
+
formData.append("file", new Blob([buffer], { type: "audio/opus" }), filename);
|
|
14247
|
+
return formData;
|
|
14248
|
+
})()
|
|
14249
|
+
});
|
|
14250
|
+
const data = await resp.json();
|
|
14251
|
+
const fileKey = data?.data?.file_key;
|
|
14252
|
+
if (!fileKey) throw new Error(`Feishu audio upload failed: ${JSON.stringify(data)}`);
|
|
14253
|
+
return fileKey;
|
|
14254
|
+
}
|
|
14133
14255
|
/** 通过飞书API下载消息中的图片 */
|
|
14134
14256
|
async _downloadImage(messageId, imageKey) {
|
|
14135
14257
|
const token = await this._getTenantToken();
|
|
@@ -14333,6 +14455,7 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14333
14455
|
const message = data?.message;
|
|
14334
14456
|
if (!sender || !message) return;
|
|
14335
14457
|
const messageId = message.message_id || "";
|
|
14458
|
+
console.log(`[feishu] recv msg_type=${message.message_type} chat=${message.chat_type} from=${sender.sender_id?.open_id?.slice(0, 10)}`);
|
|
14336
14459
|
if (this.recentMessageIds.has(messageId)) return;
|
|
14337
14460
|
this.recentMessageIds.add(messageId);
|
|
14338
14461
|
if (this.recentMessageIds.size > 500) {
|
|
@@ -14447,6 +14570,13 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
14447
14570
|
if (msgType === "file") {
|
|
14448
14571
|
return { text: `[\u6587\u4EF6: ${parsed.file_name || "\u672A\u77E5\u6587\u4EF6"}]`, imageKeys, fileKey: parsed.file_key, fileName: parsed.file_name };
|
|
14449
14572
|
}
|
|
14573
|
+
if (msgType === "share_location" || msgType === "location") {
|
|
14574
|
+
const name = parsed.name || "";
|
|
14575
|
+
const lat = parsed.latitude || "";
|
|
14576
|
+
const lng = parsed.longitude || "";
|
|
14577
|
+
const text = `[\u4F4D\u7F6E: ${name}${lat && lng ? ` (${lat}, ${lng})` : ""}]`;
|
|
14578
|
+
return { text, imageKeys };
|
|
14579
|
+
}
|
|
14450
14580
|
if (msgType === "post") {
|
|
14451
14581
|
const lines = [];
|
|
14452
14582
|
if (Array.isArray(parsed.content)) {
|
|
@@ -16908,6 +17038,8 @@ var SessionManager = class {
|
|
|
16908
17038
|
platformMapPath;
|
|
16909
17039
|
/** 外部 hook:每次创建新 writer 时回调(如 EverOS sync 注入 onMessageWritten) */
|
|
16910
17040
|
onWriterCreated = null;
|
|
17041
|
+
/** 外部 hook:判断 session 是否有活跃 query(idle cleanup 据此跳过,防止误关 query 正持有的 writer) */
|
|
17042
|
+
onIsSessionActive = null;
|
|
16911
17043
|
histories = /* @__PURE__ */ new Map();
|
|
16912
17044
|
restoredRecallPaths = /* @__PURE__ */ new Map();
|
|
16913
17045
|
writers = /* @__PURE__ */ new Map();
|
|
@@ -17354,6 +17486,7 @@ var SessionManager = class {
|
|
|
17354
17486
|
const now = Date.now();
|
|
17355
17487
|
for (const [id, lastUsed] of this.lastUsed) {
|
|
17356
17488
|
if (now - lastUsed > idleMs) {
|
|
17489
|
+
if (this.onIsSessionActive?.(id)) continue;
|
|
17357
17490
|
const writer = this.writers.get(id);
|
|
17358
17491
|
if (writer) {
|
|
17359
17492
|
writer.close();
|
|
@@ -18704,8 +18837,11 @@ async function deepinfraRerank(query, episodes, rerankUrl, rerankApiKey, rerankM
|
|
|
18704
18837
|
}
|
|
18705
18838
|
var DEFAULT_MIN_SCORE2 = 0.5;
|
|
18706
18839
|
async function findRelevantMemoriesEveros(query, _memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
|
|
18707
|
-
const everosUrl = options?.everosUrl
|
|
18708
|
-
const userId = options?.userId
|
|
18840
|
+
const everosUrl = options?.everosUrl;
|
|
18841
|
+
const userId = options?.userId;
|
|
18842
|
+
if (!everosUrl || !userId) {
|
|
18843
|
+
throw new Error(`[memdir] everos recall: everosUrl/userId \u672A\u914D\u7F6E (everosUrl=${everosUrl}, userId=${userId})\uFF0C\u4E0D\u6267\u884C everos recall\uFF0C\u907F\u514D\u4E32\u5230\u522B\u4EBA\u7684\u5E93`);
|
|
18844
|
+
}
|
|
18709
18845
|
const topK = options?.topK ?? 3;
|
|
18710
18846
|
const minScore = options?.minScore ?? DEFAULT_MIN_SCORE2;
|
|
18711
18847
|
console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} userId=${userId} topK=${topK}`);
|
|
@@ -19027,7 +19163,9 @@ ${text}` : text });
|
|
|
19027
19163
|
} else {
|
|
19028
19164
|
console.log(`[pre-llm-debug] userMsgContent is string, len=${String(userMsgContent).length}`);
|
|
19029
19165
|
}
|
|
19030
|
-
const
|
|
19166
|
+
const vcN = liveConfig.get("voiceChat.lightContextN") ?? 20;
|
|
19167
|
+
const lightHistory = channelName === "voice-chat" && history.length > vcN ? history.slice(-vcN) : history;
|
|
19168
|
+
const messages = [...lightHistory, msg.user(userMsgContent)];
|
|
19031
19169
|
if (deps.mcpManager && !deps.mcpManager.isMcpDeltaSent(sessionId)) {
|
|
19032
19170
|
const delta = deps.mcpManager.getMcpDelta();
|
|
19033
19171
|
if (delta && delta.addedBlocks.length > 0) {
|
|
@@ -19196,7 +19334,7 @@ ${text}` : text });
|
|
|
19196
19334
|
const queryAbortController = new AbortController();
|
|
19197
19335
|
engine.setExternalAbort(queryAbortController);
|
|
19198
19336
|
setActiveQueryEngine(sessionId, engine);
|
|
19199
|
-
const shouldSkipRecall = skipRecall ?? channelName === "cron";
|
|
19337
|
+
const shouldSkipRecall = skipRecall ?? (channelName === "cron" || channelName === "voice-chat");
|
|
19200
19338
|
if (getFeature("topic-recall") !== false && !shouldSkipRecall) {
|
|
19201
19339
|
try {
|
|
19202
19340
|
const memoryDir = getAutoMemPath(workspace);
|
|
@@ -19244,31 +19382,49 @@ ${text}` : text });
|
|
|
19244
19382
|
const recallP = deps.recallProvider;
|
|
19245
19383
|
const recallMode = topics?.recall?.mode || "llm";
|
|
19246
19384
|
let relevantMemories;
|
|
19247
|
-
|
|
19385
|
+
const effectiveText = textForMemory.replace(/\[图片来自[^\]]*\]|路径:\S+/g, "").trim();
|
|
19386
|
+
if (!effectiveText) {
|
|
19387
|
+
console.log("[handle-query] Memory recall skipped: image-only message (no effective text)");
|
|
19388
|
+
relevantMemories = [];
|
|
19389
|
+
} else if (recallMode === "everos") {
|
|
19248
19390
|
const everosCfg = deps?.everosCfg;
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
|
|
19254
|
-
|
|
19255
|
-
|
|
19256
|
-
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19391
|
+
if (everosCfg && everosCfg.everosUrl && everosCfg.userId) {
|
|
19392
|
+
relevantMemories = await findRelevantMemoriesEveros(
|
|
19393
|
+
effectiveText,
|
|
19394
|
+
memoryDir,
|
|
19395
|
+
surfaced.paths,
|
|
19396
|
+
{
|
|
19397
|
+
everosUrl: everosCfg.everosUrl,
|
|
19398
|
+
userId: everosCfg.userId,
|
|
19399
|
+
rerankUrl: everosCfg.rerank?.baseUrl,
|
|
19400
|
+
rerankApiKey: everosCfg.rerank?.apiKey,
|
|
19401
|
+
rerankModel: everosCfg.rerank?.model,
|
|
19402
|
+
rerankProvider: everosCfg.rerank?.provider,
|
|
19403
|
+
minScore: topics?.recall?.minScore
|
|
19404
|
+
}
|
|
19405
|
+
);
|
|
19406
|
+
} else {
|
|
19407
|
+
console.log("[handle-query] everos recall skipped: everosCfg not configured, falling back to LLM recall");
|
|
19408
|
+
relevantMemories = await findRelevantMemories(
|
|
19409
|
+
effectiveText,
|
|
19410
|
+
memoryDir,
|
|
19411
|
+
recallP?.provider || provider,
|
|
19412
|
+
recallP?.model || model,
|
|
19413
|
+
queryAbortController.signal,
|
|
19414
|
+
surfaced.paths,
|
|
19415
|
+
recallP?.disableThinking,
|
|
19416
|
+
topics?.maxScanFiles
|
|
19417
|
+
);
|
|
19418
|
+
}
|
|
19263
19419
|
} else if (recallMode === "vector") {
|
|
19264
19420
|
relevantMemories = await findRelevantMemoriesVector(
|
|
19265
|
-
|
|
19421
|
+
effectiveText,
|
|
19266
19422
|
memoryDir,
|
|
19267
19423
|
surfaced.paths
|
|
19268
19424
|
);
|
|
19269
19425
|
} else {
|
|
19270
19426
|
relevantMemories = await findRelevantMemories(
|
|
19271
|
-
|
|
19427
|
+
effectiveText,
|
|
19272
19428
|
memoryDir,
|
|
19273
19429
|
recallP?.provider || provider,
|
|
19274
19430
|
recallP?.model || model,
|
|
@@ -20500,6 +20656,10 @@ var NudgePlugin = class {
|
|
|
20500
20656
|
registerCallbackHook("Stop", {
|
|
20501
20657
|
type: "callback",
|
|
20502
20658
|
callback: async (input, _toolUseID, _signal) => {
|
|
20659
|
+
if (this.cfg.stopHookEnabled === false) {
|
|
20660
|
+
console.log("[stop-hook] disabled via /stophook off \u2014 skipping");
|
|
20661
|
+
return { outcome: { outcome: "success" } };
|
|
20662
|
+
}
|
|
20503
20663
|
const mode = this.cfg.stopHookMode || "sync";
|
|
20504
20664
|
if (mode === "async") {
|
|
20505
20665
|
console.log("[stop-hook] async mode \u2014 firing judge in background, not blocking");
|
|
@@ -20774,26 +20934,28 @@ var NudgePlugin = class {
|
|
|
20774
20934
|
console.log(`[nudge] ${due.length} stop-hook notification(s) due: ${due.map((n) => n.id).join(", ")}`);
|
|
20775
20935
|
const items = due.map((n) => `[\u901A\u77E5ID: ${n.id}]
|
|
20776
20936
|
\u4E0A\u6B21\u8BF4\uFF1A${n.description}`).join("\n\n");
|
|
20937
|
+
const hint = `\uFF08\u6E05\u7406\u5173\u952E\u5B57\u53EF\u7528\uFF1A\u8FC7\u671F\u4E86 / \u5DF2\u843D\u76D8 / resolved / \u5DF2\u5904\u7406 / \u5DF2\u95ED\u73AF\uFF0C\u5982 "${due[0].id} \u5DF2\u5904\u7406"\uFF09`;
|
|
20777
20938
|
const desc = due.length === 1 ? `\u4F60\u4E4B\u524D\u5728\u7B49\u5F85\u67D0\u4E2A\u5916\u90E8\u6761\u4EF6\uFF0C\u65F6\u95F4\u5230\u4E86\uFF0C\u56DE\u53BB\u68C0\u67E5\uFF01
|
|
20778
20939
|
|
|
20779
20940
|
${items}
|
|
20780
20941
|
|
|
20781
|
-
\u68C0\u67E5\u6761\u4EF6\u662F\u5426\u6EE1\u8DB3\uFF0C\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D"${due[0].id} \u8FC7\u671F\u4E86"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u8FD9\u6761\u3002` : `\u4F60\u4E4B\u524D\u6709 ${due.length} \u4E2A\u7B49\u5F85\u4E2D\u7684\u5916\u90E8\u6761\u4EF6\u90FD\u5230\u671F\u4E86\uFF0C\u56DE\u53BB\u9010\u4E2A\u68C0\u67E5\uFF01
|
|
20942
|
+
\u68C0\u67E5\u6761\u4EF6\u662F\u5426\u6EE1\u8DB3\uFF0C\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D"${due[0].id} \u8FC7\u671F\u4E86"\u6216"${due[0].id} \u5DF2\u5904\u7406"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u8FD9\u6761\u3002${hint}` : `\u4F60\u4E4B\u524D\u6709 ${due.length} \u4E2A\u7B49\u5F85\u4E2D\u7684\u5916\u90E8\u6761\u4EF6\u90FD\u5230\u671F\u4E86\uFF0C\u56DE\u53BB\u9010\u4E2A\u68C0\u67E5\uFF01
|
|
20782
20943
|
|
|
20783
20944
|
${items}
|
|
20784
20945
|
|
|
20785
|
-
\u5BF9\u6BCF\u4E00\u6761\uFF1A\u6761\u4EF6\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D\u5BF9\u5E94\u7684"<\u901A\u77E5ID> \u8FC7\u671F\u4E86"\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u3002`;
|
|
20946
|
+
\u5BF9\u6BCF\u4E00\u6761\uFF1A\u6761\u4EF6\u6EE1\u8DB3\u5C31\u7EE7\u7EED\u5E72\u6D3B\uFF0C\u4E0D\u6EE1\u8DB3\u5C31\u56DE\u590D\u5BF9\u5E94\u7684"<\u901A\u77E5ID> \u8FC7\u671F\u4E86"\uFF08\u6216 \u5DF2\u5904\u7406/\u5DF2\u843D\u76D8/resolved/\u5DF2\u95ED\u73AF\uFF09\u544A\u8BC9 nudge \u7CBE\u786E\u6E05\u7406\u3002`;
|
|
20786
20947
|
return { message: buildNudgeNotification("wake", desc), ids: due.map((n) => n.id) };
|
|
20787
20948
|
} catch (e) {
|
|
20788
20949
|
console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
|
|
20789
20950
|
return null;
|
|
20790
20951
|
}
|
|
20791
20952
|
}
|
|
20792
|
-
/** 从回复文本里提取 "<id> 过期了" 的 wake id(一条回复可能处置多个)
|
|
20953
|
+
/** 从回复文本里提取 "<id> 过期了" 的 wake id(一条回复可能处置多个)
|
|
20954
|
+
* 0816 翀哥:关键字多样化——过期了/已落盘/resolved/已处理/已闭环/已归档 都认 */
|
|
20793
20955
|
extractWakeReplyIds(text) {
|
|
20794
20956
|
if (!text) return [];
|
|
20795
20957
|
const ids = [];
|
|
20796
|
-
const re = /(wake-\d+-[a-z0-9]+)\s
|
|
20958
|
+
const re = /(wake-\d+-[a-z0-9]+)\s*(?:过期了|已落盘|resolved|已处理|已闭环|已归档|已完成|done)/g;
|
|
20797
20959
|
let m;
|
|
20798
20960
|
while ((m = re.exec(text)) !== null) {
|
|
20799
20961
|
if (!ids.includes(m[1])) ids.push(m[1]);
|
|
@@ -23236,8 +23398,9 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
|
|
|
23236
23398
|
- \u4FDD\u6301\u7B80\u77ED\u53E3\u8BED\u5316\uFF08\u901A\u5E38 1-3 \u53E5\uFF09\uFF0C\u4E0D\u8981\u957F\u7BC7\u5927\u8BBA
|
|
23237
23399
|
- \u7B2C\u4E00\u53E5\u63A7\u5236\u5728 4-6 \u4E2A\u5B57\uFF0C\u5C3D\u5FEB\u89E6\u53D1 TTS \u5408\u6210
|
|
23238
23400
|
- \u8BF4\u8BDD\u50CF\u4EBA\u2014\u2014\u77ED\u53E5\uFF0C\u53BB\u670D\u52A1\u611F
|
|
23239
|
-
- \
|
|
23240
|
-
- \
|
|
23401
|
+
- \u3010\u786C\u89C4\u5219\u3011\u8BED\u97F3\u5BF9\u8BDD\u4E2D\u7981\u6B62\u8C03\u7528 exec / SSH / \u8BFB\u5927\u6587\u4EF6\u7B49\u6162\u5DE5\u5177\u2014\u2014\u67E5\u6570\u636E\u7C7B\u95EE\u9898\u76F4\u63A5\u7528\u5634\u7B54"\u8FD9\u4E2A\u6211\u98DE\u4E66\u91CC\u67E5\u4E86\u544A\u8BC9\u4F60"
|
|
23402
|
+
- \u8BED\u97F3\u91CC\u5141\u8BB8\u7684\u5DE5\u5177\uFF1Acalendar\u3001msg_send\u3001read\uFF08\u8BFB\u76F4\u64AD\u8349\u7A3F/\u811A\u672C\u7528\uFF0C\u672C\u5730\u6587\u4EF6\u5FEB\uFF09\u2014\u2014\u4E0D\u8981\u7528 my_voice\uFF08\u8BED\u97F3\u5BF9\u8BDD\u672C\u8EAB\u5C31\u5728\u51FA\u58F0\uFF0C\u518D\u751F\u6210\u662F\u5957\u5A03\uFF09
|
|
23403
|
+
- \u5982\u679C\u5B9E\u5728\u9700\u8981\u67E5\uFF1A\u5148\u8BF4"\u7B49\u6211\u67E5\u4E0B"\u5E76\u6781\u7B80\u8C03\u7528\uFF0C\u67E5\u5B8C\u7ACB\u523B\u603B\u7ED3\u6210\u4E00\u53E5\u8BDD`;
|
|
23241
23404
|
const engine = new QueryEngine(llmProvider, {
|
|
23242
23405
|
model: modelId,
|
|
23243
23406
|
systemPrompt: (ctx.deps.systemPrompt || "") + voiceChatRules,
|
|
@@ -23951,7 +24114,22 @@ var DEFAULTS4 = {
|
|
|
23951
24114
|
autoStart: true,
|
|
23952
24115
|
defaultMode: "hybrid_agentic"
|
|
23953
24116
|
};
|
|
23954
|
-
function
|
|
24117
|
+
function resolveProviderConfig(rawSection, providers, defaultModel, defaultApiKey, defaultBaseUrl) {
|
|
24118
|
+
if (!rawSection) {
|
|
24119
|
+
return { model: defaultModel, apiKey: defaultApiKey, baseUrl: defaultBaseUrl };
|
|
24120
|
+
}
|
|
24121
|
+
const providerName = rawSection.provider;
|
|
24122
|
+
const providerCfg = providerName ? providers?.[providerName] : void 0;
|
|
24123
|
+
return {
|
|
24124
|
+
provider: providerName,
|
|
24125
|
+
model: rawSection.model ?? defaultModel,
|
|
24126
|
+
// apiKey 从 provider 取(最易不一致,统一来源)
|
|
24127
|
+
// baseUrl/model 优先用 everos 节点自己的值(同一 provider 可能有不同 endpoint)
|
|
24128
|
+
apiKey: providerCfg?.apiKey ?? rawSection.apiKey ?? defaultApiKey,
|
|
24129
|
+
baseUrl: rawSection.baseUrl ?? providerCfg?.baseUrl ?? defaultBaseUrl
|
|
24130
|
+
};
|
|
24131
|
+
}
|
|
24132
|
+
function parseEverosConfig(raw, providers) {
|
|
23955
24133
|
if (!raw) {
|
|
23956
24134
|
return {
|
|
23957
24135
|
enabled: false,
|
|
@@ -23971,8 +24149,20 @@ function parseEverosConfig(raw) {
|
|
|
23971
24149
|
userId: raw.userId ?? "xiaomei",
|
|
23972
24150
|
autoStart: raw.autoStart !== false,
|
|
23973
24151
|
defaultMode: raw.defaultMode ?? DEFAULTS4.defaultMode,
|
|
23974
|
-
llm:
|
|
23975
|
-
|
|
24152
|
+
llm: resolveProviderConfig(
|
|
24153
|
+
raw.llm,
|
|
24154
|
+
providers,
|
|
24155
|
+
"glm-5.2",
|
|
24156
|
+
"",
|
|
24157
|
+
"https://open.bigmodel.cn/api/coding/paas/v4"
|
|
24158
|
+
),
|
|
24159
|
+
rerank: resolveProviderConfig(
|
|
24160
|
+
raw.rerank,
|
|
24161
|
+
providers,
|
|
24162
|
+
"Qwen/Qwen3-Reranker-4B",
|
|
24163
|
+
"",
|
|
24164
|
+
"https://api.deepinfra.com/v1/inference"
|
|
24165
|
+
),
|
|
23976
24166
|
lancedbPath: raw.lancedbPath ?? "",
|
|
23977
24167
|
sqlitePath: raw.sqlitePath ?? "",
|
|
23978
24168
|
minScore: raw.minScore
|
|
@@ -24023,14 +24213,14 @@ var EverosSearchClient = class {
|
|
|
24023
24213
|
const url = useAgentic ? `${this.agenticUrl}/api/v1/search` : `${this.everosUrl}/api/v1/memory/search`;
|
|
24024
24214
|
const body = useAgentic ? JSON.stringify({
|
|
24025
24215
|
query: params.query,
|
|
24026
|
-
user_id: params.userId
|
|
24216
|
+
user_id: params.userId,
|
|
24027
24217
|
mode,
|
|
24028
24218
|
top_k: params.topK ?? 5,
|
|
24029
24219
|
strategy: params.strategy || "multi_query"
|
|
24030
24220
|
}) : JSON.stringify({
|
|
24031
24221
|
query: params.query,
|
|
24032
|
-
user_id: params.userId
|
|
24033
|
-
app_id:
|
|
24222
|
+
user_id: params.userId,
|
|
24223
|
+
app_id: params.appId,
|
|
24034
24224
|
project_id: "default",
|
|
24035
24225
|
top_k: params.topK ?? 5
|
|
24036
24226
|
});
|
|
@@ -24068,8 +24258,8 @@ var EverosPlugin = class {
|
|
|
24068
24258
|
healthTimer = null;
|
|
24069
24259
|
weStartedAgentic = false;
|
|
24070
24260
|
// 我们拉起的才管
|
|
24071
|
-
constructor(rawConfig) {
|
|
24072
|
-
this.config = parseEverosConfig(rawConfig);
|
|
24261
|
+
constructor(rawConfig, providers) {
|
|
24262
|
+
this.config = parseEverosConfig(rawConfig, providers);
|
|
24073
24263
|
this.client = new EverosSearchClient(this.config.agenticUrl, this.config.everosUrl);
|
|
24074
24264
|
}
|
|
24075
24265
|
static shouldEnable(config) {
|
|
@@ -25206,6 +25396,47 @@ async function compressWav(wavPath) {
|
|
|
25206
25396
|
return wavPath;
|
|
25207
25397
|
}
|
|
25208
25398
|
}
|
|
25399
|
+
async function wavToOggOpus(wavPath) {
|
|
25400
|
+
const oggPath = wavPath.replace(/\.wav$/, ".ogg");
|
|
25401
|
+
await execFileAsync("ffmpeg", [
|
|
25402
|
+
"-y",
|
|
25403
|
+
"-i",
|
|
25404
|
+
wavPath,
|
|
25405
|
+
"-c:a",
|
|
25406
|
+
"libopus",
|
|
25407
|
+
"-b:a",
|
|
25408
|
+
"32k",
|
|
25409
|
+
"-ar",
|
|
25410
|
+
"24000",
|
|
25411
|
+
oggPath
|
|
25412
|
+
], { timeout: 3e4 });
|
|
25413
|
+
if (!fs30.existsSync(oggPath) || fs30.statSync(oggPath).size < 20) {
|
|
25414
|
+
throw new Error("ogg/opus encode produced empty output");
|
|
25415
|
+
}
|
|
25416
|
+
fs30.unlinkSync(wavPath);
|
|
25417
|
+
return oggPath;
|
|
25418
|
+
}
|
|
25419
|
+
async function wavToSilk(wavPath) {
|
|
25420
|
+
fs30.mkdirSync(VOICE_DIR, { recursive: true });
|
|
25421
|
+
const silkPath = wavPath.replace(/\.wav$/, ".silk");
|
|
25422
|
+
const script = `
|
|
25423
|
+
import sys, io, wave, pysilk
|
|
25424
|
+
src, dst = sys.argv[1], sys.argv[2]
|
|
25425
|
+
with wave.open(src, 'rb') as wf:
|
|
25426
|
+
sr = wf.getframerate()
|
|
25427
|
+
pcm = wf.readframes(wf.getnframes())
|
|
25428
|
+
out = io.BytesIO()
|
|
25429
|
+
pysilk.encode(io.BytesIO(pcm), out, sample_rate=sr, bit_rate=24000, tencent=True)
|
|
25430
|
+
with open(dst, 'wb') as f:
|
|
25431
|
+
f.write(out.getvalue())
|
|
25432
|
+
print('OK')
|
|
25433
|
+
`;
|
|
25434
|
+
await execFileAsync("python3", ["-c", script, wavPath, silkPath], { timeout: 3e4 });
|
|
25435
|
+
if (!fs30.existsSync(silkPath) || fs30.statSync(silkPath).size < 20) {
|
|
25436
|
+
throw new Error("silk encode produced empty output");
|
|
25437
|
+
}
|
|
25438
|
+
return silkPath;
|
|
25439
|
+
}
|
|
25209
25440
|
registry.register({
|
|
25210
25441
|
name: "my_voice",
|
|
25211
25442
|
description: "Generate and send a voice message. Use for: \u53D1\u8BED\u97F3/\u8BF4\u53E5\u8BDD/\u5F55\u4E00\u6BB5/\u53D1\u58F0\u97F3/\u60F3\u542C\u4F60\u7684\u58F0\u97F3/\u8BED\u97F3\u6D88\u606F/\u8BF4\u7ED9\u6211\u542C. Uses CosyVoice (\u914D\u7F6E voiceChat.local.tts) with edge-tts fallback. Sends to weixin or feishu.",
|
|
@@ -25243,9 +25474,6 @@ registry.register({
|
|
|
25243
25474
|
vc.workspaceId,
|
|
25244
25475
|
vc.instruction
|
|
25245
25476
|
);
|
|
25246
|
-
if (audioPath.endsWith(".wav")) {
|
|
25247
|
-
audioPath = await compressWav(audioPath);
|
|
25248
|
-
}
|
|
25249
25477
|
} catch (e) {
|
|
25250
25478
|
console.warn(`[my-voice] cosyvoice failed (${e.message}), falling back to edge-tts`);
|
|
25251
25479
|
audioPath = await ttsEdge(text);
|
|
@@ -25254,9 +25482,6 @@ registry.register({
|
|
|
25254
25482
|
} else if (useEngine === "gptsovits") {
|
|
25255
25483
|
try {
|
|
25256
25484
|
audioPath = await ttsGptsovits(text);
|
|
25257
|
-
if (audioPath.endsWith(".wav")) {
|
|
25258
|
-
audioPath = await compressWav(audioPath);
|
|
25259
|
-
}
|
|
25260
25485
|
} catch (e) {
|
|
25261
25486
|
console.warn(`[my-voice] gptsovits failed (${e.message}), falling back to edge-tts`);
|
|
25262
25487
|
audioPath = await ttsEdge(text);
|
|
@@ -25268,12 +25493,30 @@ registry.register({
|
|
|
25268
25493
|
} catch (e) {
|
|
25269
25494
|
return { content: `TTS failed: ${e.message}`, isError: true };
|
|
25270
25495
|
}
|
|
25496
|
+
const resolvedChannelRaw = args.channel || ctx.channel || "feishu";
|
|
25497
|
+
const resolvedChannel = resolvedChannelRaw === "weixin" ? "wechat" : resolvedChannelRaw;
|
|
25498
|
+
const target = ctx.channelTarget || ctx.from;
|
|
25499
|
+
if (audioPath.endsWith(".wav")) {
|
|
25500
|
+
if (resolvedChannel === "wechat") {
|
|
25501
|
+
try {
|
|
25502
|
+
audioPath = await wavToSilk(audioPath);
|
|
25503
|
+
} catch (e) {
|
|
25504
|
+
console.warn(`[my-voice] silk encode failed (${e.message}), falling back to m4a`);
|
|
25505
|
+
audioPath = await compressWav(audioPath);
|
|
25506
|
+
}
|
|
25507
|
+
} else if (resolvedChannel === "feishu") {
|
|
25508
|
+
try {
|
|
25509
|
+
audioPath = await wavToOggOpus(audioPath);
|
|
25510
|
+
} catch (e) {
|
|
25511
|
+
console.warn(`[my-voice] ogg/opus encode failed (${e.message}), falling back to m4a`);
|
|
25512
|
+
audioPath = await compressWav(audioPath);
|
|
25513
|
+
}
|
|
25514
|
+
}
|
|
25515
|
+
}
|
|
25271
25516
|
const ext = path31.extname(audioPath).toLowerCase();
|
|
25272
|
-
const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg" };
|
|
25517
|
+
const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg", ".silk": "audio/silk" };
|
|
25273
25518
|
const mimeType = mimeMap[ext] || "audio/mpeg";
|
|
25274
25519
|
const sizeKB = fs30.statSync(audioPath).size / 1024;
|
|
25275
|
-
const resolvedChannel = args.channel || ctx.channel || "feishu";
|
|
25276
|
-
const target = ctx.channelTarget || ctx.from;
|
|
25277
25520
|
try {
|
|
25278
25521
|
await mgr.sendFile(resolvedChannel, target, caption, {
|
|
25279
25522
|
path: audioPath,
|
|
@@ -25311,9 +25554,12 @@ function getProxyDispatcher2() {
|
|
|
25311
25554
|
return void 0;
|
|
25312
25555
|
}
|
|
25313
25556
|
}
|
|
25314
|
-
var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
|
|
25315
25557
|
var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
|
|
25316
25558
|
var DEFAULT_RESOLUTION = "1k";
|
|
25559
|
+
var FAL_KEY_FALLBACK = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
|
|
25560
|
+
function getFalKey(cfg) {
|
|
25561
|
+
return cfg?.providers?.fal?.apiKey || cfg?.my_selfie?.falKey || FAL_KEY_FALLBACK;
|
|
25562
|
+
}
|
|
25317
25563
|
var DEFAULT_REFERENCES = [
|
|
25318
25564
|
{ name: "default", p: "images/xiaomei_clean_v2.png" },
|
|
25319
25565
|
{ name: "v3", p: "images/xiaomei_clean_v3.png" },
|
|
@@ -25356,6 +25602,7 @@ function detectMode(input) {
|
|
|
25356
25602
|
return "direct";
|
|
25357
25603
|
}
|
|
25358
25604
|
async function generateWithFal(imageB64, prompt, resolution) {
|
|
25605
|
+
const cfg = liveConfig.all();
|
|
25359
25606
|
let aspectRatio;
|
|
25360
25607
|
try {
|
|
25361
25608
|
const refBuf = Buffer.from(imageB64, "base64");
|
|
@@ -25380,8 +25627,7 @@ async function generateWithFal(imageB64, prompt, resolution) {
|
|
|
25380
25627
|
}
|
|
25381
25628
|
}
|
|
25382
25629
|
if (w > 0 && h > 0) {
|
|
25383
|
-
|
|
25384
|
-
console.log(`[my-selfie] ref image ${w}x${h}, aspect_ratio=${aspectRatio}`);
|
|
25630
|
+
console.log(`[my-selfie] ref image ${w}x${h}, aspect_ratio skipped`);
|
|
25385
25631
|
}
|
|
25386
25632
|
} catch (e) {
|
|
25387
25633
|
console.warn(`[my-selfie] Failed to read ref dimensions: ${e.message}`);
|
|
@@ -25396,7 +25642,7 @@ async function generateWithFal(imageB64, prompt, resolution) {
|
|
|
25396
25642
|
if (aspectRatio) body.aspect_ratio = aspectRatio;
|
|
25397
25643
|
const res = await fetch(FAL_ENDPOINT, {
|
|
25398
25644
|
method: "POST",
|
|
25399
|
-
headers: { "Authorization": `Key ${
|
|
25645
|
+
headers: { "Authorization": `Key ${getFalKey(cfg)}`, "Content-Type": "application/json" },
|
|
25400
25646
|
body: JSON.stringify(body)
|
|
25401
25647
|
});
|
|
25402
25648
|
if (!res.ok) {
|
|
@@ -28098,7 +28344,12 @@ ${content}`
|
|
|
28098
28344
|
// tool 读自己配置用
|
|
28099
28345
|
recallProvider: memoryRecallProvider || void 0,
|
|
28100
28346
|
extractProvider: memoryExtractProvider || void 0,
|
|
28101
|
-
everosCfg: config.everos
|
|
28347
|
+
everosCfg: config.everos ? {
|
|
28348
|
+
...config.everos,
|
|
28349
|
+
// resolve provider 引用:从 providers 取 apiKey(rerank 在 handle-query.ts 里直接读 raw config)
|
|
28350
|
+
llm: config.everos.llm?.provider && config.providers?.[config.everos.llm.provider] ? { ...config.everos.llm, apiKey: config.providers[config.everos.llm.provider].apiKey } : config.everos.llm,
|
|
28351
|
+
rerank: config.everos.rerank?.provider && config.providers?.[config.everos.rerank.provider] ? { ...config.everos.rerank, apiKey: config.providers[config.everos.rerank.provider].apiKey } : config.everos.rerank
|
|
28352
|
+
} : void 0,
|
|
28102
28353
|
mcpManager
|
|
28103
28354
|
};
|
|
28104
28355
|
if (visionEngine && visionConfig) {
|
|
@@ -28118,7 +28369,13 @@ ${content}`
|
|
|
28118
28369
|
config,
|
|
28119
28370
|
// tool 读自己配置用
|
|
28120
28371
|
recallProvider: memoryRecallProvider || void 0,
|
|
28121
|
-
extractProvider: memoryExtractProvider || void 0
|
|
28372
|
+
extractProvider: memoryExtractProvider || void 0,
|
|
28373
|
+
everosCfg: config.everos ? {
|
|
28374
|
+
...config.everos,
|
|
28375
|
+
// resolve provider 引用:从 providers 取 apiKey(跟主 deps 同逻辑)
|
|
28376
|
+
llm: config.everos.llm?.provider && config.providers?.[config.everos.llm.provider] ? { ...config.everos.llm, apiKey: config.providers[config.everos.llm.provider].apiKey } : config.everos.llm,
|
|
28377
|
+
rerank: config.everos.rerank?.provider && config.providers?.[config.everos.rerank.provider] ? { ...config.everos.rerank, apiKey: config.providers[config.everos.rerank.provider].apiKey } : config.everos.rerank
|
|
28378
|
+
} : void 0
|
|
28122
28379
|
};
|
|
28123
28380
|
}
|
|
28124
28381
|
let modelOverride = null;
|
|
@@ -28167,7 +28424,8 @@ ${content}`
|
|
|
28167
28424
|
channels: config.channels,
|
|
28168
28425
|
config,
|
|
28169
28426
|
recallProvider: memoryRecallProvider || void 0,
|
|
28170
|
-
extractProvider: memoryExtractProvider || void 0
|
|
28427
|
+
extractProvider: memoryExtractProvider || void 0,
|
|
28428
|
+
everosCfg: deps?.everosCfg
|
|
28171
28429
|
};
|
|
28172
28430
|
}
|
|
28173
28431
|
};
|
|
@@ -28209,11 +28467,13 @@ ${content}`
|
|
|
28209
28467
|
systemPrompt,
|
|
28210
28468
|
channels: config.channels,
|
|
28211
28469
|
recallProvider: memoryRecallProvider || void 0,
|
|
28212
|
-
extractProvider: memoryExtractProvider || void 0
|
|
28470
|
+
extractProvider: memoryExtractProvider || void 0,
|
|
28471
|
+
everosCfg: deps?.everosCfg
|
|
28213
28472
|
};
|
|
28214
28473
|
}
|
|
28215
28474
|
const dispatcher = new MessageDispatcher();
|
|
28216
28475
|
deps.dispatcher = dispatcher;
|
|
28476
|
+
sessions.onIsSessionActive = (sid) => dispatcher.isActive(sid);
|
|
28217
28477
|
setNotificationCallback((notif, route) => {
|
|
28218
28478
|
const sid = route?.sessionId || sessions.getSessionId("scope:main") || "main";
|
|
28219
28479
|
const chan = route?.channel;
|
|
@@ -28584,6 +28844,10 @@ ${notifications}
|
|
|
28584
28844
|
{ name: "session-memory", description: "\u67E5/\u5207 session-memory\uFF08on/off/\u7559\u7A7A=\u67E5\uFF09", options: [
|
|
28585
28845
|
{ name: "state", description: "on \u6216 off\uFF0C\u7559\u7A7A=\u53EA\u67E5\u72B6\u6001", type: "string", required: false }
|
|
28586
28846
|
] },
|
|
28847
|
+
// 8/17 stophook 总开关(翀哥要求)
|
|
28848
|
+
{ name: "stophook", description: "\u67E5/\u5207 stop-hook\uFF08on/off/\u7559\u7A7A=\u67E5\uFF09", options: [
|
|
28849
|
+
{ name: "state", description: "on \u6216 off\uFF0C\u7559\u7A7A=\u53EA\u67E5\u72B6\u6001", type: "string", required: false }
|
|
28850
|
+
] },
|
|
28587
28851
|
(() => {
|
|
28588
28852
|
const aliases = config.modelAliases || {};
|
|
28589
28853
|
const aliasByRef = /* @__PURE__ */ new Map();
|
|
@@ -29012,6 +29276,29 @@ ${result.changes.map((c) => `- ${c}`).join("\n")}` : `\u274C Reload failed: ${re
|
|
|
29012
29276
|
await ctx.reply(`\u2705 ${key}: **${cur}** \u2192 **${rawState}**`);
|
|
29013
29277
|
return;
|
|
29014
29278
|
}
|
|
29279
|
+
if (ctx.command === "stophook") {
|
|
29280
|
+
const rawState = (ctx.args.state || "").trim().toLowerCase();
|
|
29281
|
+
const getPath = "nudge.stopHookEnabled";
|
|
29282
|
+
const curVal = config?.nudge?.stopHookEnabled;
|
|
29283
|
+
const cur = curVal === false ? "off" : "on";
|
|
29284
|
+
if (rawState === "") {
|
|
29285
|
+
await ctx.reply(`\u{1F4CA} stop-hook: **${cur}**\uFF08nudge \u63D2\u4EF6 stopHookEnabled=${String(curVal ?? "\u672A\u8BBE(\u9ED8\u8BA4on)")}\uFF09`);
|
|
29286
|
+
return;
|
|
29287
|
+
}
|
|
29288
|
+
if (rawState !== "on" && rawState !== "off") {
|
|
29289
|
+
await ctx.reply(`\u274C \u53EA\u63A5\u53D7 on \u6216 off
|
|
29290
|
+
\u5F53\u524D\uFF1Astop-hook = **${cur}**`);
|
|
29291
|
+
return;
|
|
29292
|
+
}
|
|
29293
|
+
if (rawState === cur) {
|
|
29294
|
+
await ctx.reply(`\u2139\uFE0F stop-hook \u5DF2\u7ECF\u662F **${cur}**`);
|
|
29295
|
+
return;
|
|
29296
|
+
}
|
|
29297
|
+
await liveConfig.set(getPath, rawState === "on");
|
|
29298
|
+
console.log(`[stophook] stop-hook ${cur} \u2192 ${rawState} (live + persisted)`);
|
|
29299
|
+
await ctx.reply(`\u2705 stop-hook: **${cur}** \u2192 **${rawState}**${rawState === "off" ? "\\n\uFF08\u8FDE\u73AF nudge \u9759\u9ED8\u4E2D\u2014\u2014wake-up/corrective \u5168\u505C\uFF0C\u8981\u7528\u518D /stophook on\uFF09" : ""}`);
|
|
29300
|
+
return;
|
|
29301
|
+
}
|
|
29015
29302
|
if (ctx.command === "model") {
|
|
29016
29303
|
const input = (ctx.args.model || "").trim();
|
|
29017
29304
|
const aliases = config.modelAliases || {};
|
|
@@ -29737,7 +30024,7 @@ ${pathStr}` }];
|
|
|
29737
30024
|
}
|
|
29738
30025
|
}
|
|
29739
30026
|
if (config.everos?.enabled) {
|
|
29740
|
-
pluginManager.register(new EverosPlugin(config.everos));
|
|
30027
|
+
pluginManager.register(new EverosPlugin(config.everos, config.providers));
|
|
29741
30028
|
console.log("[everos] Plugin registered");
|
|
29742
30029
|
}
|
|
29743
30030
|
globalThis.__pluginManager = pluginManager;
|