engine7 7.1.37 → 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 +423 -144
- package/dist/main.mjs +344 -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();
|
|
@@ -16916,6 +17038,8 @@ var SessionManager = class {
|
|
|
16916
17038
|
platformMapPath;
|
|
16917
17039
|
/** 外部 hook:每次创建新 writer 时回调(如 EverOS sync 注入 onMessageWritten) */
|
|
16918
17040
|
onWriterCreated = null;
|
|
17041
|
+
/** 外部 hook:判断 session 是否有活跃 query(idle cleanup 据此跳过,防止误关 query 正持有的 writer) */
|
|
17042
|
+
onIsSessionActive = null;
|
|
16919
17043
|
histories = /* @__PURE__ */ new Map();
|
|
16920
17044
|
restoredRecallPaths = /* @__PURE__ */ new Map();
|
|
16921
17045
|
writers = /* @__PURE__ */ new Map();
|
|
@@ -17362,6 +17486,7 @@ var SessionManager = class {
|
|
|
17362
17486
|
const now = Date.now();
|
|
17363
17487
|
for (const [id, lastUsed] of this.lastUsed) {
|
|
17364
17488
|
if (now - lastUsed > idleMs) {
|
|
17489
|
+
if (this.onIsSessionActive?.(id)) continue;
|
|
17365
17490
|
const writer = this.writers.get(id);
|
|
17366
17491
|
if (writer) {
|
|
17367
17492
|
writer.close();
|
|
@@ -18712,8 +18837,11 @@ async function deepinfraRerank(query, episodes, rerankUrl, rerankApiKey, rerankM
|
|
|
18712
18837
|
}
|
|
18713
18838
|
var DEFAULT_MIN_SCORE2 = 0.5;
|
|
18714
18839
|
async function findRelevantMemoriesEveros(query, _memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
|
|
18715
|
-
const everosUrl = options?.everosUrl
|
|
18716
|
-
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
|
+
}
|
|
18717
18845
|
const topK = options?.topK ?? 3;
|
|
18718
18846
|
const minScore = options?.minScore ?? DEFAULT_MIN_SCORE2;
|
|
18719
18847
|
console.log(`[memdir] everos recall: query="${query.slice(0, 50)}..." url=${everosUrl} userId=${userId} topK=${topK}`);
|
|
@@ -19035,7 +19163,9 @@ ${text}` : text });
|
|
|
19035
19163
|
} else {
|
|
19036
19164
|
console.log(`[pre-llm-debug] userMsgContent is string, len=${String(userMsgContent).length}`);
|
|
19037
19165
|
}
|
|
19038
|
-
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)];
|
|
19039
19169
|
if (deps.mcpManager && !deps.mcpManager.isMcpDeltaSent(sessionId)) {
|
|
19040
19170
|
const delta = deps.mcpManager.getMcpDelta();
|
|
19041
19171
|
if (delta && delta.addedBlocks.length > 0) {
|
|
@@ -19204,7 +19334,7 @@ ${text}` : text });
|
|
|
19204
19334
|
const queryAbortController = new AbortController();
|
|
19205
19335
|
engine.setExternalAbort(queryAbortController);
|
|
19206
19336
|
setActiveQueryEngine(sessionId, engine);
|
|
19207
|
-
const shouldSkipRecall = skipRecall ?? channelName === "cron";
|
|
19337
|
+
const shouldSkipRecall = skipRecall ?? (channelName === "cron" || channelName === "voice-chat");
|
|
19208
19338
|
if (getFeature("topic-recall") !== false && !shouldSkipRecall) {
|
|
19209
19339
|
try {
|
|
19210
19340
|
const memoryDir = getAutoMemPath(workspace);
|
|
@@ -19252,31 +19382,49 @@ ${text}` : text });
|
|
|
19252
19382
|
const recallP = deps.recallProvider;
|
|
19253
19383
|
const recallMode = topics?.recall?.mode || "llm";
|
|
19254
19384
|
let relevantMemories;
|
|
19255
|
-
|
|
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") {
|
|
19256
19390
|
const everosCfg = deps?.everosCfg;
|
|
19257
|
-
|
|
19258
|
-
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
|
|
19262
|
-
|
|
19263
|
-
|
|
19264
|
-
|
|
19265
|
-
|
|
19266
|
-
|
|
19267
|
-
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
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
|
+
}
|
|
19271
19419
|
} else if (recallMode === "vector") {
|
|
19272
19420
|
relevantMemories = await findRelevantMemoriesVector(
|
|
19273
|
-
|
|
19421
|
+
effectiveText,
|
|
19274
19422
|
memoryDir,
|
|
19275
19423
|
surfaced.paths
|
|
19276
19424
|
);
|
|
19277
19425
|
} else {
|
|
19278
19426
|
relevantMemories = await findRelevantMemories(
|
|
19279
|
-
|
|
19427
|
+
effectiveText,
|
|
19280
19428
|
memoryDir,
|
|
19281
19429
|
recallP?.provider || provider,
|
|
19282
19430
|
recallP?.model || model,
|
|
@@ -20508,6 +20656,10 @@ var NudgePlugin = class {
|
|
|
20508
20656
|
registerCallbackHook("Stop", {
|
|
20509
20657
|
type: "callback",
|
|
20510
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
|
+
}
|
|
20511
20663
|
const mode = this.cfg.stopHookMode || "sync";
|
|
20512
20664
|
if (mode === "async") {
|
|
20513
20665
|
console.log("[stop-hook] async mode \u2014 firing judge in background, not blocking");
|
|
@@ -20782,26 +20934,28 @@ var NudgePlugin = class {
|
|
|
20782
20934
|
console.log(`[nudge] ${due.length} stop-hook notification(s) due: ${due.map((n) => n.id).join(", ")}`);
|
|
20783
20935
|
const items = due.map((n) => `[\u901A\u77E5ID: ${n.id}]
|
|
20784
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`;
|
|
20785
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
|
|
20786
20939
|
|
|
20787
20940
|
${items}
|
|
20788
20941
|
|
|
20789
|
-
\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
|
|
20790
20943
|
|
|
20791
20944
|
${items}
|
|
20792
20945
|
|
|
20793
|
-
\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`;
|
|
20794
20947
|
return { message: buildNudgeNotification("wake", desc), ids: due.map((n) => n.id) };
|
|
20795
20948
|
} catch (e) {
|
|
20796
20949
|
console.warn(`[nudge] collectDueStopHookNotifications error: ${e.message}`);
|
|
20797
20950
|
return null;
|
|
20798
20951
|
}
|
|
20799
20952
|
}
|
|
20800
|
-
/** 从回复文本里提取 "<id> 过期了" 的 wake id(一条回复可能处置多个)
|
|
20953
|
+
/** 从回复文本里提取 "<id> 过期了" 的 wake id(一条回复可能处置多个)
|
|
20954
|
+
* 0816 翀哥:关键字多样化——过期了/已落盘/resolved/已处理/已闭环/已归档 都认 */
|
|
20801
20955
|
extractWakeReplyIds(text) {
|
|
20802
20956
|
if (!text) return [];
|
|
20803
20957
|
const ids = [];
|
|
20804
|
-
const re = /(wake-\d+-[a-z0-9]+)\s
|
|
20958
|
+
const re = /(wake-\d+-[a-z0-9]+)\s*(?:过期了|已落盘|resolved|已处理|已闭环|已归档|已完成|done)/g;
|
|
20805
20959
|
let m;
|
|
20806
20960
|
while ((m = re.exec(text)) !== null) {
|
|
20807
20961
|
if (!ids.includes(m[1])) ids.push(m[1]);
|
|
@@ -23244,8 +23398,9 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
|
|
|
23244
23398
|
- \u4FDD\u6301\u7B80\u77ED\u53E3\u8BED\u5316\uFF08\u901A\u5E38 1-3 \u53E5\uFF09\uFF0C\u4E0D\u8981\u957F\u7BC7\u5927\u8BBA
|
|
23245
23399
|
- \u7B2C\u4E00\u53E5\u63A7\u5236\u5728 4-6 \u4E2A\u5B57\uFF0C\u5C3D\u5FEB\u89E6\u53D1 TTS \u5408\u6210
|
|
23246
23400
|
- \u8BF4\u8BDD\u50CF\u4EBA\u2014\u2014\u77ED\u53E5\uFF0C\u53BB\u670D\u52A1\u611F
|
|
23247
|
-
- \
|
|
23248
|
-
- \
|
|
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`;
|
|
23249
23404
|
const engine = new QueryEngine(llmProvider, {
|
|
23250
23405
|
model: modelId,
|
|
23251
23406
|
systemPrompt: (ctx.deps.systemPrompt || "") + voiceChatRules,
|
|
@@ -23959,7 +24114,22 @@ var DEFAULTS4 = {
|
|
|
23959
24114
|
autoStart: true,
|
|
23960
24115
|
defaultMode: "hybrid_agentic"
|
|
23961
24116
|
};
|
|
23962
|
-
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) {
|
|
23963
24133
|
if (!raw) {
|
|
23964
24134
|
return {
|
|
23965
24135
|
enabled: false,
|
|
@@ -23979,8 +24149,20 @@ function parseEverosConfig(raw) {
|
|
|
23979
24149
|
userId: raw.userId ?? "xiaomei",
|
|
23980
24150
|
autoStart: raw.autoStart !== false,
|
|
23981
24151
|
defaultMode: raw.defaultMode ?? DEFAULTS4.defaultMode,
|
|
23982
|
-
llm:
|
|
23983
|
-
|
|
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
|
+
),
|
|
23984
24166
|
lancedbPath: raw.lancedbPath ?? "",
|
|
23985
24167
|
sqlitePath: raw.sqlitePath ?? "",
|
|
23986
24168
|
minScore: raw.minScore
|
|
@@ -24031,14 +24213,14 @@ var EverosSearchClient = class {
|
|
|
24031
24213
|
const url = useAgentic ? `${this.agenticUrl}/api/v1/search` : `${this.everosUrl}/api/v1/memory/search`;
|
|
24032
24214
|
const body = useAgentic ? JSON.stringify({
|
|
24033
24215
|
query: params.query,
|
|
24034
|
-
user_id: params.userId
|
|
24216
|
+
user_id: params.userId,
|
|
24035
24217
|
mode,
|
|
24036
24218
|
top_k: params.topK ?? 5,
|
|
24037
24219
|
strategy: params.strategy || "multi_query"
|
|
24038
24220
|
}) : JSON.stringify({
|
|
24039
24221
|
query: params.query,
|
|
24040
|
-
user_id: params.userId
|
|
24041
|
-
app_id:
|
|
24222
|
+
user_id: params.userId,
|
|
24223
|
+
app_id: params.appId,
|
|
24042
24224
|
project_id: "default",
|
|
24043
24225
|
top_k: params.topK ?? 5
|
|
24044
24226
|
});
|
|
@@ -24076,8 +24258,8 @@ var EverosPlugin = class {
|
|
|
24076
24258
|
healthTimer = null;
|
|
24077
24259
|
weStartedAgentic = false;
|
|
24078
24260
|
// 我们拉起的才管
|
|
24079
|
-
constructor(rawConfig) {
|
|
24080
|
-
this.config = parseEverosConfig(rawConfig);
|
|
24261
|
+
constructor(rawConfig, providers) {
|
|
24262
|
+
this.config = parseEverosConfig(rawConfig, providers);
|
|
24081
24263
|
this.client = new EverosSearchClient(this.config.agenticUrl, this.config.everosUrl);
|
|
24082
24264
|
}
|
|
24083
24265
|
static shouldEnable(config) {
|
|
@@ -25214,6 +25396,47 @@ async function compressWav(wavPath) {
|
|
|
25214
25396
|
return wavPath;
|
|
25215
25397
|
}
|
|
25216
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
|
+
}
|
|
25217
25440
|
registry.register({
|
|
25218
25441
|
name: "my_voice",
|
|
25219
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.",
|
|
@@ -25251,9 +25474,6 @@ registry.register({
|
|
|
25251
25474
|
vc.workspaceId,
|
|
25252
25475
|
vc.instruction
|
|
25253
25476
|
);
|
|
25254
|
-
if (audioPath.endsWith(".wav")) {
|
|
25255
|
-
audioPath = await compressWav(audioPath);
|
|
25256
|
-
}
|
|
25257
25477
|
} catch (e) {
|
|
25258
25478
|
console.warn(`[my-voice] cosyvoice failed (${e.message}), falling back to edge-tts`);
|
|
25259
25479
|
audioPath = await ttsEdge(text);
|
|
@@ -25262,9 +25482,6 @@ registry.register({
|
|
|
25262
25482
|
} else if (useEngine === "gptsovits") {
|
|
25263
25483
|
try {
|
|
25264
25484
|
audioPath = await ttsGptsovits(text);
|
|
25265
|
-
if (audioPath.endsWith(".wav")) {
|
|
25266
|
-
audioPath = await compressWav(audioPath);
|
|
25267
|
-
}
|
|
25268
25485
|
} catch (e) {
|
|
25269
25486
|
console.warn(`[my-voice] gptsovits failed (${e.message}), falling back to edge-tts`);
|
|
25270
25487
|
audioPath = await ttsEdge(text);
|
|
@@ -25276,12 +25493,30 @@ registry.register({
|
|
|
25276
25493
|
} catch (e) {
|
|
25277
25494
|
return { content: `TTS failed: ${e.message}`, isError: true };
|
|
25278
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
|
+
}
|
|
25279
25516
|
const ext = path31.extname(audioPath).toLowerCase();
|
|
25280
|
-
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" };
|
|
25281
25518
|
const mimeType = mimeMap[ext] || "audio/mpeg";
|
|
25282
25519
|
const sizeKB = fs30.statSync(audioPath).size / 1024;
|
|
25283
|
-
const resolvedChannel = args.channel || ctx.channel || "feishu";
|
|
25284
|
-
const target = ctx.channelTarget || ctx.from;
|
|
25285
25520
|
try {
|
|
25286
25521
|
await mgr.sendFile(resolvedChannel, target, caption, {
|
|
25287
25522
|
path: audioPath,
|
|
@@ -25319,9 +25554,12 @@ function getProxyDispatcher2() {
|
|
|
25319
25554
|
return void 0;
|
|
25320
25555
|
}
|
|
25321
25556
|
}
|
|
25322
|
-
var FAL_KEY = "3b848fc6-bee5-46e5-8db7-ae81ac16dc28:2f1f15cf394db2d32c9bb9a4f23f3bee";
|
|
25323
25557
|
var FAL_ENDPOINT = "https://fal.run/xai/grok-imagine-image/edit";
|
|
25324
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
|
+
}
|
|
25325
25563
|
var DEFAULT_REFERENCES = [
|
|
25326
25564
|
{ name: "default", p: "images/xiaomei_clean_v2.png" },
|
|
25327
25565
|
{ name: "v3", p: "images/xiaomei_clean_v3.png" },
|
|
@@ -25364,6 +25602,7 @@ function detectMode(input) {
|
|
|
25364
25602
|
return "direct";
|
|
25365
25603
|
}
|
|
25366
25604
|
async function generateWithFal(imageB64, prompt, resolution) {
|
|
25605
|
+
const cfg = liveConfig.all();
|
|
25367
25606
|
let aspectRatio;
|
|
25368
25607
|
try {
|
|
25369
25608
|
const refBuf = Buffer.from(imageB64, "base64");
|
|
@@ -25388,8 +25627,7 @@ async function generateWithFal(imageB64, prompt, resolution) {
|
|
|
25388
25627
|
}
|
|
25389
25628
|
}
|
|
25390
25629
|
if (w > 0 && h > 0) {
|
|
25391
|
-
|
|
25392
|
-
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`);
|
|
25393
25631
|
}
|
|
25394
25632
|
} catch (e) {
|
|
25395
25633
|
console.warn(`[my-selfie] Failed to read ref dimensions: ${e.message}`);
|
|
@@ -25404,7 +25642,7 @@ async function generateWithFal(imageB64, prompt, resolution) {
|
|
|
25404
25642
|
if (aspectRatio) body.aspect_ratio = aspectRatio;
|
|
25405
25643
|
const res = await fetch(FAL_ENDPOINT, {
|
|
25406
25644
|
method: "POST",
|
|
25407
|
-
headers: { "Authorization": `Key ${
|
|
25645
|
+
headers: { "Authorization": `Key ${getFalKey(cfg)}`, "Content-Type": "application/json" },
|
|
25408
25646
|
body: JSON.stringify(body)
|
|
25409
25647
|
});
|
|
25410
25648
|
if (!res.ok) {
|
|
@@ -28106,7 +28344,12 @@ ${content}`
|
|
|
28106
28344
|
// tool 读自己配置用
|
|
28107
28345
|
recallProvider: memoryRecallProvider || void 0,
|
|
28108
28346
|
extractProvider: memoryExtractProvider || void 0,
|
|
28109
|
-
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,
|
|
28110
28353
|
mcpManager
|
|
28111
28354
|
};
|
|
28112
28355
|
if (visionEngine && visionConfig) {
|
|
@@ -28126,7 +28369,13 @@ ${content}`
|
|
|
28126
28369
|
config,
|
|
28127
28370
|
// tool 读自己配置用
|
|
28128
28371
|
recallProvider: memoryRecallProvider || void 0,
|
|
28129
|
-
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
|
|
28130
28379
|
};
|
|
28131
28380
|
}
|
|
28132
28381
|
let modelOverride = null;
|
|
@@ -28175,7 +28424,8 @@ ${content}`
|
|
|
28175
28424
|
channels: config.channels,
|
|
28176
28425
|
config,
|
|
28177
28426
|
recallProvider: memoryRecallProvider || void 0,
|
|
28178
|
-
extractProvider: memoryExtractProvider || void 0
|
|
28427
|
+
extractProvider: memoryExtractProvider || void 0,
|
|
28428
|
+
everosCfg: deps?.everosCfg
|
|
28179
28429
|
};
|
|
28180
28430
|
}
|
|
28181
28431
|
};
|
|
@@ -28217,11 +28467,13 @@ ${content}`
|
|
|
28217
28467
|
systemPrompt,
|
|
28218
28468
|
channels: config.channels,
|
|
28219
28469
|
recallProvider: memoryRecallProvider || void 0,
|
|
28220
|
-
extractProvider: memoryExtractProvider || void 0
|
|
28470
|
+
extractProvider: memoryExtractProvider || void 0,
|
|
28471
|
+
everosCfg: deps?.everosCfg
|
|
28221
28472
|
};
|
|
28222
28473
|
}
|
|
28223
28474
|
const dispatcher = new MessageDispatcher();
|
|
28224
28475
|
deps.dispatcher = dispatcher;
|
|
28476
|
+
sessions.onIsSessionActive = (sid) => dispatcher.isActive(sid);
|
|
28225
28477
|
setNotificationCallback((notif, route) => {
|
|
28226
28478
|
const sid = route?.sessionId || sessions.getSessionId("scope:main") || "main";
|
|
28227
28479
|
const chan = route?.channel;
|
|
@@ -28592,6 +28844,10 @@ ${notifications}
|
|
|
28592
28844
|
{ name: "session-memory", description: "\u67E5/\u5207 session-memory\uFF08on/off/\u7559\u7A7A=\u67E5\uFF09", options: [
|
|
28593
28845
|
{ name: "state", description: "on \u6216 off\uFF0C\u7559\u7A7A=\u53EA\u67E5\u72B6\u6001", type: "string", required: false }
|
|
28594
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
|
+
] },
|
|
28595
28851
|
(() => {
|
|
28596
28852
|
const aliases = config.modelAliases || {};
|
|
28597
28853
|
const aliasByRef = /* @__PURE__ */ new Map();
|
|
@@ -29020,6 +29276,29 @@ ${result.changes.map((c) => `- ${c}`).join("\n")}` : `\u274C Reload failed: ${re
|
|
|
29020
29276
|
await ctx.reply(`\u2705 ${key}: **${cur}** \u2192 **${rawState}**`);
|
|
29021
29277
|
return;
|
|
29022
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
|
+
}
|
|
29023
29302
|
if (ctx.command === "model") {
|
|
29024
29303
|
const input = (ctx.args.model || "").trim();
|
|
29025
29304
|
const aliases = config.modelAliases || {};
|
|
@@ -29745,7 +30024,7 @@ ${pathStr}` }];
|
|
|
29745
30024
|
}
|
|
29746
30025
|
}
|
|
29747
30026
|
if (config.everos?.enabled) {
|
|
29748
|
-
pluginManager.register(new EverosPlugin(config.everos));
|
|
30027
|
+
pluginManager.register(new EverosPlugin(config.everos, config.providers));
|
|
29749
30028
|
console.log("[everos] Plugin registered");
|
|
29750
30029
|
}
|
|
29751
30030
|
globalThis.__pluginManager = pluginManager;
|