engine7 7.1.107 → 7.1.112
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 +3193 -923
- package/dist/engine-startup.mjs +1669 -1351
- package/dist/main.mjs +1599 -1289
- package/package.json +10 -4
- package/templates/config.template.json +1 -1
package/dist/main.mjs
CHANGED
|
@@ -141,14 +141,14 @@ var init_live = __esm({
|
|
|
141
141
|
}
|
|
142
142
|
/** 把改动写回 config 文件对应段(找不到文件路径则只内存生效) */
|
|
143
143
|
async persistToFile(dotPath, val) {
|
|
144
|
-
const
|
|
144
|
+
const fs58 = await import("node:fs");
|
|
145
145
|
const cfgPath = this.current?._configFilePath;
|
|
146
|
-
if (!cfgPath || !
|
|
146
|
+
if (!cfgPath || !fs58.existsSync(cfgPath)) {
|
|
147
147
|
console.warn(`[liveConfig] set: no config file path, in-memory only (${dotPath})`);
|
|
148
148
|
return;
|
|
149
149
|
}
|
|
150
150
|
try {
|
|
151
|
-
const raw = JSON.parse(
|
|
151
|
+
const raw = JSON.parse(fs58.readFileSync(cfgPath, "utf-8"));
|
|
152
152
|
const keys = dotPath.split(".");
|
|
153
153
|
let o = raw;
|
|
154
154
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
@@ -156,7 +156,7 @@ var init_live = __esm({
|
|
|
156
156
|
o = o[keys[i]];
|
|
157
157
|
}
|
|
158
158
|
o[keys[keys.length - 1]] = val;
|
|
159
|
-
|
|
159
|
+
fs58.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
|
|
160
160
|
console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
|
|
161
161
|
markSelfWrite();
|
|
162
162
|
} catch (e) {
|
|
@@ -407,6 +407,7 @@ var init_clock = __esm({
|
|
|
407
407
|
var loader_exports = {};
|
|
408
408
|
__export(loader_exports, {
|
|
409
409
|
configSummary: () => configSummary,
|
|
410
|
+
isUnresolvedEnvRef: () => isUnresolvedEnvRef,
|
|
410
411
|
loadConfig: () => loadConfig,
|
|
411
412
|
parseModelRef: () => parseModelRef,
|
|
412
413
|
resolveTimezone: () => resolveTimezone,
|
|
@@ -449,6 +450,9 @@ function resolveTimezone(raw) {
|
|
|
449
450
|
return fallback();
|
|
450
451
|
}
|
|
451
452
|
}
|
|
453
|
+
function isUnresolvedEnvRef(value) {
|
|
454
|
+
return typeof value === "string" && ENV_REF_RE.test(value);
|
|
455
|
+
}
|
|
452
456
|
function loadConfig(configPath2) {
|
|
453
457
|
const resolvedPath = path.resolve(
|
|
454
458
|
configPath2 || process.env.ENGINE_CONFIG || "engine-config.json"
|
|
@@ -460,7 +464,7 @@ function loadConfig(configPath2) {
|
|
|
460
464
|
const missingEnvKeys = /* @__PURE__ */ new Set();
|
|
461
465
|
const resolveEnv = (node) => {
|
|
462
466
|
if (typeof node === "string") {
|
|
463
|
-
const m =
|
|
467
|
+
const m = ENV_REF_RE.exec(node);
|
|
464
468
|
if (m) {
|
|
465
469
|
if (process.env[m[1]] !== void 0) return process.env[m[1]];
|
|
466
470
|
if (node.startsWith("env:")) missingEnvKeys.add(m[1]);
|
|
@@ -672,7 +676,7 @@ function configSummary(config2) {
|
|
|
672
676
|
lines.push(`Interrupt: ${config2.profile.features.interrupt}`);
|
|
673
677
|
return lines.join("\n");
|
|
674
678
|
}
|
|
675
|
-
var loadWarnings, loadWarningsFlushed;
|
|
679
|
+
var loadWarnings, loadWarningsFlushed, ENV_REF_RE;
|
|
676
680
|
var init_loader = __esm({
|
|
677
681
|
"src/config/loader.ts"() {
|
|
678
682
|
"use strict";
|
|
@@ -681,6 +685,7 @@ var init_loader = __esm({
|
|
|
681
685
|
init_clock();
|
|
682
686
|
loadWarnings = [];
|
|
683
687
|
loadWarningsFlushed = false;
|
|
688
|
+
ENV_REF_RE = /^(?:env:|{{\s*)(?=[A-Z0-9_]*[A-Z])([A-Z0-9_]+)(?:\s*}})?$/;
|
|
684
689
|
}
|
|
685
690
|
});
|
|
686
691
|
|
|
@@ -1522,6 +1527,18 @@ function toHookContext(ctx) {
|
|
|
1522
1527
|
cwd: ctx.workspace
|
|
1523
1528
|
};
|
|
1524
1529
|
}
|
|
1530
|
+
function noteCallSignature(sig) {
|
|
1531
|
+
if (sig === _lastCallSig) {
|
|
1532
|
+
_lastCallCount++;
|
|
1533
|
+
return _lastCallCount;
|
|
1534
|
+
}
|
|
1535
|
+
_lastCallSig = sig;
|
|
1536
|
+
_lastCallCount = 1;
|
|
1537
|
+
return 1;
|
|
1538
|
+
}
|
|
1539
|
+
function loopSig(tc) {
|
|
1540
|
+
return `${tc.function.name}::${tc.function.arguments ?? ""}`;
|
|
1541
|
+
}
|
|
1525
1542
|
async function executeOne(tc, ctx, signal) {
|
|
1526
1543
|
if (signal?.aborted) {
|
|
1527
1544
|
return { call: tc, result: { content: "[interrupted by user /stop]", isError: false }, summary: null };
|
|
@@ -1530,6 +1547,23 @@ async function executeOne(tc, ctx, signal) {
|
|
|
1530
1547
|
if (!tool) {
|
|
1531
1548
|
return { call: tc, result: { content: `Unknown tool: ${tc.function.name}`, isError: true }, summary: null };
|
|
1532
1549
|
}
|
|
1550
|
+
const repeatN = noteCallSignature(loopSig(tc));
|
|
1551
|
+
if (repeatN >= LOOP_HARD_AT) {
|
|
1552
|
+
console.warn(`[executor] \u5FAA\u73AF\u5239\u8F66\u89E6\u53D1\uFF1A${tc.function.name} \u8FDE\u7EED\u7B2C ${repeatN} \u6B21\uFF08\u540C\u4E00\u53C2\u6570\uFF09\uFF0C\u5DF2\u62D2\u7EDD\u6267\u884C`);
|
|
1553
|
+
return {
|
|
1554
|
+
call: tc,
|
|
1555
|
+
result: {
|
|
1556
|
+
content: `\u26D4 \u5FAA\u73AF\u5239\u8F66\uFF1A\u4F60\u5DF2\u8FDE\u7EED ${repeatN} \u6B21\u7528**\u5B8C\u5168\u76F8\u540C\u7684\u53C2\u6570**\u8C03\u7528 \`${tc.function.name}\`\uFF0C\u672C\u5DE5\u5177\u88AB\u62D2\u7EDD\u6267\u884C\u3002
|
|
1557
|
+
|
|
1558
|
+
\u540C\u4E00\u4E2A\u8C03\u7528\u91CD\u590D\u518D\u591A\u904D\uFF0C\u8FD4\u56DE\u4E5F\u53EA\u4F1A\u662F\u540C\u4E00\u4EFD\u7ED3\u679C\u3002**\u8BF7\u7ACB\u523B\u505C\u6B62\u8C03\u7528\u672C\u5DE5\u5177**\uFF0C\u6539\u7528\u5176\u4E2D\u4E00\u6761\uFF1A
|
|
1559
|
+
1. \u7528\u73B0\u6709\u4FE1\u606F\u76F4\u63A5\u4F5C\u7B54\uFF08\u5E76\u8BF4\u660E\u54EA\u4E9B\u5730\u65B9\u56E0\u4FE1\u606F\u4E0D\u8DB3\u800C\u65E0\u6CD5\u786E\u5B9A\uFF09
|
|
1560
|
+
2. \u6362\u4E00\u4E2A**\u4E0D\u540C\u53C2\u6570**\u6216**\u4E0D\u540C\u5DE5\u5177**\u518D\u8BD5
|
|
1561
|
+
3. \u5982\u679C\u786E\u5B9E\u62FF\u4E0D\u5230\u6570\u636E\uFF0C\u76F4\u63A5\u544A\u8BC9\u7528\u6237"\u8BE5\u9879\u6570\u636E\u4E0D\u53EF\u7528"\uFF0C**\u4E0D\u8981\u7EE7\u7EED\u91CD\u8BD5**`,
|
|
1562
|
+
isError: true
|
|
1563
|
+
},
|
|
1564
|
+
summary: null
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1533
1567
|
if (isPlanMode() && PLAN_BLOCKED_TOOLS.has(tc.function.name)) {
|
|
1534
1568
|
return {
|
|
1535
1569
|
call: tc,
|
|
@@ -1574,6 +1608,13 @@ async function executeOne(tc, ctx, signal) {
|
|
|
1574
1608
|
}
|
|
1575
1609
|
const result = await tool.handler(args2, ctx, signal);
|
|
1576
1610
|
await executePostToolUseHooks(tc.function.name, args2, result.content, hookCtx, signal);
|
|
1611
|
+
if (repeatN >= LOOP_SOFT_AT) {
|
|
1612
|
+
const prefix = `\u26A0\uFE0F \u4F60\u5DF2\u8FDE\u7EED ${repeatN} \u6B21\u7528**\u5B8C\u5168\u76F8\u540C\u7684\u53C2\u6570**\u8C03\u7528 \`${tc.function.name}\`\u3002\u91CD\u590D\u4E0D\u4F1A\u5F97\u5230\u65B0\u7ED3\u679C\u2014\u2014\u5982\u679C\u4E0B\u9762\u8FD9\u4EFD\u4FE1\u606F\u4E0D\u591F\u7528\uFF0C\u8BF7\u6362\u53C2\u6570/\u6362\u5DE5\u5177\uFF0C\u6216\u76F4\u63A5\u7528\u73B0\u6709\u4FE1\u606F\u4F5C\u7B54\u5E76\u8BF4\u660E\u54EA\u91CC\u4E0D\u786E\u5B9A\u3002
|
|
1613
|
+
|
|
1614
|
+
`;
|
|
1615
|
+
const body = typeof result.content === "string" ? result.content : JSON.stringify(result.content);
|
|
1616
|
+
return { call: tc, result: { ...result, content: prefix + body }, summary: null };
|
|
1617
|
+
}
|
|
1577
1618
|
return { call: tc, result, summary: null };
|
|
1578
1619
|
} catch (err) {
|
|
1579
1620
|
await executePostToolUseFailureHooks(tc.function.name, args2, err.message, hookCtx, signal);
|
|
@@ -1626,7 +1667,7 @@ async function executeTools(toolCalls, signal, context) {
|
|
|
1626
1667
|
}
|
|
1627
1668
|
return { messages, summaries };
|
|
1628
1669
|
}
|
|
1629
|
-
var PLAN_BLOCKED_TOOLS;
|
|
1670
|
+
var PLAN_BLOCKED_TOOLS, LOOP_SOFT_AT, LOOP_HARD_AT, _lastCallSig, _lastCallCount;
|
|
1630
1671
|
var init_executor2 = __esm({
|
|
1631
1672
|
"src/tools/BashTool/executor.ts"() {
|
|
1632
1673
|
"use strict";
|
|
@@ -1636,6 +1677,10 @@ var init_executor2 = __esm({
|
|
|
1636
1677
|
init_constants2();
|
|
1637
1678
|
init_hooks();
|
|
1638
1679
|
PLAN_BLOCKED_TOOLS = /* @__PURE__ */ new Set(["edit", "write"]);
|
|
1680
|
+
LOOP_SOFT_AT = 2;
|
|
1681
|
+
LOOP_HARD_AT = 5;
|
|
1682
|
+
_lastCallSig = "";
|
|
1683
|
+
_lastCallCount = 0;
|
|
1639
1684
|
}
|
|
1640
1685
|
});
|
|
1641
1686
|
|
|
@@ -4554,9 +4599,9 @@ function isAutoMemPath(absolutePath, workspace) {
|
|
|
4554
4599
|
return normalizedPath.startsWith(getAutoMemPath(workspace));
|
|
4555
4600
|
}
|
|
4556
4601
|
async function ensureMemoryDirExists(memoryDir) {
|
|
4557
|
-
const
|
|
4602
|
+
const fs58 = await import("node:fs");
|
|
4558
4603
|
try {
|
|
4559
|
-
await
|
|
4604
|
+
await fs58.promises.mkdir(memoryDir, { recursive: true });
|
|
4560
4605
|
} catch (e) {
|
|
4561
4606
|
const code = e?.code;
|
|
4562
4607
|
if (code !== "EEXIST") {
|
|
@@ -5203,8 +5248,30 @@ var init_context_analyzer = __esm({
|
|
|
5203
5248
|
// src/core/query.ts
|
|
5204
5249
|
var query_exports = {};
|
|
5205
5250
|
__export(query_exports, {
|
|
5206
|
-
QueryEngine: () => QueryEngine
|
|
5251
|
+
QueryEngine: () => QueryEngine,
|
|
5252
|
+
namesFromHistory: () => namesFromHistory
|
|
5207
5253
|
});
|
|
5254
|
+
function namesFromHistory(messages, deferredNames) {
|
|
5255
|
+
const out = /* @__PURE__ */ new Set();
|
|
5256
|
+
const searchCallIds = /* @__PURE__ */ new Set();
|
|
5257
|
+
for (const m of messages) {
|
|
5258
|
+
for (const tc of m?.tool_calls ?? []) {
|
|
5259
|
+
const name = tc?.function?.name;
|
|
5260
|
+
if (!name) continue;
|
|
5261
|
+
out.add(name);
|
|
5262
|
+
if (name === TOOL_SEARCH_TOOL_NAME && tc.id) searchCallIds.add(tc.id);
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
5265
|
+
if (searchCallIds.size > 0 && deferredNames.size > 0) {
|
|
5266
|
+
for (const m of messages) {
|
|
5267
|
+
if (m?.role !== "tool" || !m.tool_call_id || !searchCallIds.has(m.tool_call_id)) continue;
|
|
5268
|
+
const text = typeof m.content === "string" ? m.content : Array.isArray(m.content) ? m.content.map((b) => b && typeof b === "object" && "text" in b ? String(b.text ?? "") : "").join("") : "";
|
|
5269
|
+
if (!text) continue;
|
|
5270
|
+
for (const n of deferredNames) if (text.includes(n)) out.add(n);
|
|
5271
|
+
}
|
|
5272
|
+
}
|
|
5273
|
+
return out;
|
|
5274
|
+
}
|
|
5208
5275
|
var QueryEngine;
|
|
5209
5276
|
var init_query = __esm({
|
|
5210
5277
|
"src/core/query.ts"() {
|
|
@@ -5398,7 +5465,7 @@ var init_query = __esm({
|
|
|
5398
5465
|
const { roughTokenCountEstimation: roughTokenCountEstimation6 } = await Promise.resolve().then(() => (init_tokenEstimate(), tokenEstimate_exports));
|
|
5399
5466
|
const systemText = this.resolveOpt(this.options.systemStable) || this.resolveOpt(this.options.systemPrompt) || "";
|
|
5400
5467
|
const systemTokens = roughTokenCountEstimation6(systemText);
|
|
5401
|
-
const initialToolDefs = useDynamicTools ? this.buildActiveTools(allToolDefs) : allToolDefs;
|
|
5468
|
+
const initialToolDefs = useDynamicTools ? this.buildActiveTools(allToolDefs, currentMessages) : allToolDefs;
|
|
5402
5469
|
const toolTokens = estimateToolDefinitionTokens2(initialToolDefs);
|
|
5403
5470
|
let memoryTokens = 0;
|
|
5404
5471
|
try {
|
|
@@ -5522,7 +5589,7 @@ var init_query = __esm({
|
|
|
5522
5589
|
let turnAborted = false;
|
|
5523
5590
|
try {
|
|
5524
5591
|
const apiMessages = normalizeMessagesForAPI(currentMessages);
|
|
5525
|
-
const toolDefs = useDynamicTools ? this.buildActiveTools(allToolDefs) : allToolDefs;
|
|
5592
|
+
const toolDefs = useDynamicTools ? this.buildActiveTools(allToolDefs, currentMessages) : allToolDefs;
|
|
5526
5593
|
const deferredHint = deferredNamesForHint.length > 0 ? `<available-deferred-tools>
|
|
5527
5594
|
${deferredNamesForHint.join("\n")}
|
|
5528
5595
|
</available-deferred-tools>
|
|
@@ -5939,11 +6006,20 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
|
|
|
5939
6006
|
}
|
|
5940
6007
|
/**
|
|
5941
6008
|
* 组装本轮 active tools(对齐 CC toolSearch 动态加载):
|
|
5942
|
-
* 非 defer 工具全 schema + 已加载的 deferred 工具 schema
|
|
6009
|
+
* 非 defer 工具全 schema + 已加载的 deferred 工具 schema。
|
|
5943
6010
|
* 去掉 shouldDefer 字段(发 provider 不能带,对齐 CC:智谱不认 defer_loading)。
|
|
6011
|
+
*
|
|
6012
|
+
* 「已加载」有**两个真源,取并集**(0927,见 namesFromHistory 的注释):
|
|
6013
|
+
* ① this.loadedDeferredTools —— 本进程内 ToolSearch 的累积(内存,重启即空)
|
|
6014
|
+
* ② namesFromHistory(messages) —— 这个会话历史上出现过的(从对话推导,重启后仍在)
|
|
6015
|
+
* 缺①则本进程刚加载的会丢;缺②则重启/换引擎后历史里用过的会丢。
|
|
5944
6016
|
*/
|
|
5945
|
-
buildActiveTools(allDefs) {
|
|
5946
|
-
const
|
|
6017
|
+
buildActiveTools(allDefs, messages) {
|
|
6018
|
+
const deferredNames = new Set(
|
|
6019
|
+
allDefs.filter((d) => d.shouldDefer).map((d) => d.function.name)
|
|
6020
|
+
);
|
|
6021
|
+
const fromHistory = namesFromHistory(messages, deferredNames);
|
|
6022
|
+
const active = allDefs.filter((d) => !d.shouldDefer || this.loadedDeferredTools.has(d.function.name) || fromHistory.has(d.function.name)).map((d) => {
|
|
5947
6023
|
if (!d.shouldDefer) return d;
|
|
5948
6024
|
const { shouldDefer: _drop, ...rest2 } = d;
|
|
5949
6025
|
return rest2;
|
|
@@ -6717,16 +6793,23 @@ var init_openai_responses_provider = __esm({
|
|
|
6717
6793
|
// 官方:store:false / ZDR 场景靠 encrypted_content 才能把推理跨轮带过去
|
|
6718
6794
|
include: ["reasoning.encrypted_content"],
|
|
6719
6795
|
...system ? { instructions: system } : {},
|
|
6720
|
-
|
|
6721
|
-
|
|
6722
|
-
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
6729
|
-
|
|
6796
|
+
// 工具 = 服务端内建工具(若有) + 引擎的 function 工具。
|
|
6797
|
+
// 顺序:内建在前——它们更"声明式",放前面读日志时一眼能看到。
|
|
6798
|
+
// 判据用两者任一非空:只配了 serverTools、一个 function 都没有时也要发 tools,
|
|
6799
|
+
// 否则内建搜索会被整段省略(这正是"为什么她不触发内建搜索"的症结)。
|
|
6800
|
+
...params.tools && params.tools.length > 0 || (this.config.serverTools?.length ?? 0) > 0 ? {
|
|
6801
|
+
tools: [
|
|
6802
|
+
...this.config.serverTools ?? [],
|
|
6803
|
+
...(params.tools ?? []).map((t) => ({
|
|
6804
|
+
type: "function",
|
|
6805
|
+
name: t.function.name,
|
|
6806
|
+
description: t.function.description,
|
|
6807
|
+
parameters: t.function.parameters,
|
|
6808
|
+
// 官方 migrate §5:Responses 省略 strict 会**尝试** strict 模式(schema 不兼容时
|
|
6809
|
+
// 回退 best-effort)。显式 false 保持引擎既有的非严格行为。
|
|
6810
|
+
strict: false
|
|
6811
|
+
}))
|
|
6812
|
+
]
|
|
6730
6813
|
} : {},
|
|
6731
6814
|
...params.maxTokens ? { max_output_tokens: params.maxTokens } : {},
|
|
6732
6815
|
// disableThinking → reasoning.effort='none'。**必须实现**:Responses 里 reasoning token
|
|
@@ -6737,7 +6820,8 @@ var init_openai_responses_provider = __esm({
|
|
|
6737
6820
|
// 不发 temperature:推理模型不支持(gpt-6-astra 默认 medium effort)。
|
|
6738
6821
|
// 官方 reasoning 指南:reasoning 模型忽略/拒绝 temperature、top_p、penalty。
|
|
6739
6822
|
};
|
|
6740
|
-
|
|
6823
|
+
const st = this.config.serverTools ?? [];
|
|
6824
|
+
console.log(`[openai-responses] \u2192 model=${params.model} items=${inputItems.length} tools=${params.tools?.length ?? 0}${st.length ? ` +serverTools=[${st.map((x) => x?.type).join(",")}]` : ""} maxTokens=${body.max_output_tokens ?? "unset"} reasoning=${body.reasoning?.effort ?? "default"} store=false`);
|
|
6741
6825
|
const retryGen = fetchWithRetry(url, {
|
|
6742
6826
|
method: "POST",
|
|
6743
6827
|
headers: {
|
|
@@ -6868,6 +6952,9 @@ var init_openai_responses_provider = __esm({
|
|
|
6868
6952
|
};
|
|
6869
6953
|
if (item.name) tc.function.name = item.name;
|
|
6870
6954
|
if (typeof item.arguments === "string") tc.function.arguments = item.arguments;
|
|
6955
|
+
if (item.call_id && tc.id && tc.id !== item.call_id) {
|
|
6956
|
+
console.warn(`[openai-responses] output_index=${idx} \u649E\u8F66: \u5DF2\u6709 call_id=${tc.id}\uFF0C\u65B0\u5230 call_id=${item.call_id} \u2014\u2014 \u53EF\u80FD\u662F\u7F51\u5173\u4E71\u5E8F/\u590D\u7528 index\uFF0C\u524D\u8005\u5DF2\u88AB\u8986\u76D6`);
|
|
6957
|
+
}
|
|
6871
6958
|
if (item.call_id) tc.id = item.call_id;
|
|
6872
6959
|
if (!tc.id) tc.id = item.id ?? `call_${idx}`;
|
|
6873
6960
|
toolCallsByIndex.set(idx, tc);
|
|
@@ -6927,6 +7014,9 @@ ${rawSample.join("\n")}`);
|
|
|
6927
7014
|
yield { type: "error", error: `OpenAI Responses: response incomplete (${incompleteReason})` };
|
|
6928
7015
|
return;
|
|
6929
7016
|
}
|
|
7017
|
+
if (incompleteReason && toolCallsByIndex.size > 0) {
|
|
7018
|
+
console.warn(`[openai-responses] response incomplete (${incompleteReason}) \u4E14\u5E26 ${toolCallsByIndex.size} \u4E2A tool_call \u2014\u2014 **arguments \u53EF\u80FD\u662F\u6B8B\u7F3A JSON**\uFF0C\u4E0B\u6E38 parse \u5931\u8D25\u5C5E\u9884\u671F\uFF0C\u91CD\u8BD5\u5373\u53EF\uFF1B\u82E5\u9891\u7E41\u51FA\u73B0\u5E94\u8C03\u5927 max_output_tokens`);
|
|
7019
|
+
}
|
|
6930
7020
|
doneYielded = true;
|
|
6931
7021
|
yield { type: "done", usage: pendingUsage };
|
|
6932
7022
|
}
|
|
@@ -7775,7 +7865,9 @@ function createProvider(config2) {
|
|
|
7775
7865
|
baseUrl: config2.baseUrl,
|
|
7776
7866
|
apiKey: config2.apiKey,
|
|
7777
7867
|
proxy,
|
|
7778
|
-
wafSingleQuoteWorkaround: config2.wafSingleQuoteWorkaround
|
|
7868
|
+
wafSingleQuoteWorkaround: config2.wafSingleQuoteWorkaround,
|
|
7869
|
+
serverTools: config2.serverTools
|
|
7870
|
+
// 0927 服务端内建工具(如方舟联网搜索)
|
|
7779
7871
|
});
|
|
7780
7872
|
case "anthropic":
|
|
7781
7873
|
return new AnthropicProvider({
|
|
@@ -8167,7 +8259,7 @@ __export(teamHelpers_exports, {
|
|
|
8167
8259
|
writeTeamFile: () => writeTeamFile,
|
|
8168
8260
|
writeTeamFileAsync: () => writeTeamFileAsync
|
|
8169
8261
|
});
|
|
8170
|
-
import { mkdirSync as
|
|
8262
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8171
8263
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
8172
8264
|
import { join as join16 } from "node:path";
|
|
8173
8265
|
function initTeamDirs(stateDir) {
|
|
@@ -8210,7 +8302,7 @@ async function readTeamFileAsync(teamName) {
|
|
|
8210
8302
|
}
|
|
8211
8303
|
function writeTeamFile(teamName, teamFile) {
|
|
8212
8304
|
const teamDir = getTeamDir(teamName);
|
|
8213
|
-
|
|
8305
|
+
mkdirSync8(teamDir, { recursive: true });
|
|
8214
8306
|
writeFileSync5(getTeamFilePath(teamName), JSON.stringify(teamFile, null, 2));
|
|
8215
8307
|
}
|
|
8216
8308
|
async function writeTeamFileAsync(teamName, teamFile) {
|
|
@@ -8301,7 +8393,7 @@ async function cleanupTeamDirectories(teamName) {
|
|
|
8301
8393
|
}
|
|
8302
8394
|
function ensureTasksDir(taskListId) {
|
|
8303
8395
|
const dir = join16(getTasksDir(), taskListId);
|
|
8304
|
-
|
|
8396
|
+
mkdirSync8(dir, { recursive: true });
|
|
8305
8397
|
}
|
|
8306
8398
|
function setLeaderTeamName(name) {
|
|
8307
8399
|
_leaderTeamName = name;
|
|
@@ -8399,7 +8491,7 @@ async function acquireLock(inboxPath) {
|
|
|
8399
8491
|
for (let i = 0; i < LOCK_RETRIES; i++) {
|
|
8400
8492
|
if (await isLockStale(lockPath2)) {
|
|
8401
8493
|
try {
|
|
8402
|
-
await import("node:fs/promises").then((
|
|
8494
|
+
await import("node:fs/promises").then((fs58) => fs58.rm(lockPath2, { force: true }));
|
|
8403
8495
|
} catch {
|
|
8404
8496
|
}
|
|
8405
8497
|
}
|
|
@@ -8407,7 +8499,7 @@ async function acquireLock(inboxPath) {
|
|
|
8407
8499
|
await writeFile2(lockPath2, `${process.pid}-${Date.now()}`, { encoding: "utf-8", flag: "wx" });
|
|
8408
8500
|
return async () => {
|
|
8409
8501
|
try {
|
|
8410
|
-
await import("node:fs/promises").then((
|
|
8502
|
+
await import("node:fs/promises").then((fs58) => fs58.rm(lockPath2, { force: true }));
|
|
8411
8503
|
} catch {
|
|
8412
8504
|
}
|
|
8413
8505
|
};
|
|
@@ -8774,10 +8866,10 @@ function parseDurationString(s, defaultMs) {
|
|
|
8774
8866
|
const m = String(s).trim().toLowerCase().match(/^([\d.]+)\s*([a-z]+)?$/);
|
|
8775
8867
|
if (!m) return defaultMs;
|
|
8776
8868
|
const n = parseFloat(m[1]);
|
|
8777
|
-
const
|
|
8778
|
-
const mult = DURATION_UNITS[
|
|
8779
|
-
if (!Number.isFinite(n) || !mult &&
|
|
8780
|
-
return Math.round(
|
|
8869
|
+
const unit2 = m[2] || "ms";
|
|
8870
|
+
const mult = DURATION_UNITS[unit2];
|
|
8871
|
+
if (!Number.isFinite(n) || !mult && unit2 !== "ms") return defaultMs;
|
|
8872
|
+
return Math.round(unit2 === "ms" ? n : n * mult);
|
|
8781
8873
|
}
|
|
8782
8874
|
function recordInboundBotFlag(userId, isBot) {
|
|
8783
8875
|
if (!userId || !isBot) return;
|
|
@@ -9653,7 +9745,7 @@ var init_filters = __esm({
|
|
|
9653
9745
|
|
|
9654
9746
|
// src/mem7/core/vector-store.ts
|
|
9655
9747
|
import { Schema, Field, Utf8, Float32, Int64, FixedSizeList, TimestampMicrosecond, List } from "apache-arrow";
|
|
9656
|
-
import { mkdirSync as
|
|
9748
|
+
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
9657
9749
|
import { join as join23 } from "node:path";
|
|
9658
9750
|
function lanceSpecifierFor(platform2, arch) {
|
|
9659
9751
|
return platform2 === "darwin" && arch === "x64" ? "@engine7/lancedb-intel" : "@lancedb/lancedb";
|
|
@@ -9756,7 +9848,7 @@ var init_vector_store = __esm({
|
|
|
9756
9848
|
if (!this.db) {
|
|
9757
9849
|
const lance = await loadLance();
|
|
9758
9850
|
if (!lance) throw new Mem7VectorUnavailableError();
|
|
9759
|
-
|
|
9851
|
+
mkdirSync9(this.dir, { recursive: true });
|
|
9760
9852
|
this.db = await lance.connect(this.dir);
|
|
9761
9853
|
}
|
|
9762
9854
|
return this.db;
|
|
@@ -10045,7 +10137,7 @@ Follow these principles when generating episodic memories:
|
|
|
10045
10137
|
|
|
10046
10138
|
// src/mem7/core/store.ts
|
|
10047
10139
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
10048
|
-
import { mkdirSync as
|
|
10140
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync7, existsSync as existsSync15, readdirSync as readdirSync2 } from "node:fs";
|
|
10049
10141
|
import { join as join24 } from "node:path";
|
|
10050
10142
|
import { createHash } from "node:crypto";
|
|
10051
10143
|
function contentHash(text) {
|
|
@@ -10088,7 +10180,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10088
10180
|
constructor(stateDir) {
|
|
10089
10181
|
this.stateDir = stateDir;
|
|
10090
10182
|
this.root = mem7Root(stateDir);
|
|
10091
|
-
|
|
10183
|
+
mkdirSync10(join24(this.root, "index"), { recursive: true });
|
|
10092
10184
|
this.db = new DatabaseSync2(join24(this.root, "index", "mem7.db"));
|
|
10093
10185
|
this.db.exec("PRAGMA journal_mode=WAL");
|
|
10094
10186
|
this.db.exec(SCHEMA);
|
|
@@ -10221,12 +10313,12 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10221
10313
|
appendMd(ownerId, dirName, filePrefix, type2, dateStr, entries2, render) {
|
|
10222
10314
|
if (entries2.length === 0) return;
|
|
10223
10315
|
const dir = join24(this.root, "users", ownerId, dirName);
|
|
10224
|
-
|
|
10316
|
+
mkdirSync10(dir, { recursive: true });
|
|
10225
10317
|
const file2 = join24(dir, `${filePrefix}-${dateStr}.md`);
|
|
10226
10318
|
let head;
|
|
10227
10319
|
let existingBlocks = "";
|
|
10228
10320
|
let count;
|
|
10229
|
-
if (
|
|
10321
|
+
if (existsSync15(file2)) {
|
|
10230
10322
|
const text = readFileSync16(file2, "utf8");
|
|
10231
10323
|
const fmEnd = text.indexOf("\n---", 3);
|
|
10232
10324
|
const split = fmEnd >= 0 ? fmEnd + 4 : text.length;
|
|
@@ -10280,7 +10372,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10280
10372
|
const [dirName, filePrefix] = dirByPrefix[prefix];
|
|
10281
10373
|
const file2 = join24(this.root, "users", ownerId, dirName, `${filePrefix}-${dateStr}.md`);
|
|
10282
10374
|
let max = 0;
|
|
10283
|
-
if (
|
|
10375
|
+
if (existsSync15(file2)) {
|
|
10284
10376
|
const re = new RegExp(`${prefix}_${dateStr.replace(/-/g, "")}_(\\d+)`, "g");
|
|
10285
10377
|
for (const m of readFileSync16(file2, "utf8").matchAll(re)) max = Math.max(max, Number(m[1]));
|
|
10286
10378
|
}
|
|
@@ -10347,14 +10439,14 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10347
10439
|
}
|
|
10348
10440
|
// ── md 读(proactive 消费):开窗预测(全 owner 扫描,end>=今天)──
|
|
10349
10441
|
getOpenPredictions(now, limit = 50) {
|
|
10350
|
-
const
|
|
10442
|
+
const today3 = todayKey(now);
|
|
10351
10443
|
const out = [];
|
|
10352
10444
|
const usersDir = join24(this.root, "users");
|
|
10353
|
-
if (!
|
|
10445
|
+
if (!existsSync15(usersDir)) return out;
|
|
10354
10446
|
for (const owner of readdirSync2(usersDir, { withFileTypes: true })) {
|
|
10355
10447
|
if (!owner.isDirectory()) continue;
|
|
10356
10448
|
const dir = join24(usersDir, owner.name, ".foresights");
|
|
10357
|
-
if (!
|
|
10449
|
+
if (!existsSync15(dir)) continue;
|
|
10358
10450
|
for (const f of readdirSync2(dir)) {
|
|
10359
10451
|
if (!/^foresight-\d{4}-\d{2}-\d{2}\.md$/.test(f)) continue;
|
|
10360
10452
|
let content;
|
|
@@ -10370,7 +10462,7 @@ CREATE TABLE IF NOT EXISTS watermarks (
|
|
|
10370
10462
|
const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
|
|
10371
10463
|
const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
|
|
10372
10464
|
if (!end || !start || !text) continue;
|
|
10373
|
-
if (end <
|
|
10465
|
+
if (end < today3) continue;
|
|
10374
10466
|
out.push({ ownerId: owner.name, startDate: start, endDate: end, content: text.replace(/\n+/g, " ").slice(0, 200), evidence: evidence.replace(/\n+/g, " ").slice(0, 120) });
|
|
10375
10467
|
if (out.length >= limit) return out;
|
|
10376
10468
|
}
|
|
@@ -11314,7 +11406,7 @@ var init_graph_store = __esm({
|
|
|
11314
11406
|
|
|
11315
11407
|
// src/memory/proactive/persistence.ts
|
|
11316
11408
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
11317
|
-
import { mkdirSync as
|
|
11409
|
+
import { mkdirSync as mkdirSync11 } from "node:fs";
|
|
11318
11410
|
import { dirname as dirname8 } from "node:path";
|
|
11319
11411
|
function rowToAction(r) {
|
|
11320
11412
|
return {
|
|
@@ -11416,7 +11508,7 @@ CREATE INDEX IF NOT EXISTS idx_proactive_decisions_created ON proactive_decision
|
|
|
11416
11508
|
Persistence = class {
|
|
11417
11509
|
db;
|
|
11418
11510
|
constructor(dbPath) {
|
|
11419
|
-
|
|
11511
|
+
mkdirSync11(dirname8(dbPath), { recursive: true });
|
|
11420
11512
|
this.db = new DatabaseSync3(dbPath);
|
|
11421
11513
|
this.db.exec("PRAGMA journal_mode=WAL");
|
|
11422
11514
|
this.db.exec(SCHEMA2);
|
|
@@ -13298,7 +13390,7 @@ var init_helpers = __esm({
|
|
|
13298
13390
|
});
|
|
13299
13391
|
|
|
13300
13392
|
// src/calendar/commands.ts
|
|
13301
|
-
import { existsSync as
|
|
13393
|
+
import { existsSync as existsSync16, statSync as statSync4 } from "node:fs";
|
|
13302
13394
|
import { isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
|
|
13303
13395
|
function fmtEnd(start, durationMin) {
|
|
13304
13396
|
const [hh, mm] = start.split(":").map(Number);
|
|
@@ -13467,7 +13559,7 @@ function addTask(db2, args2) {
|
|
|
13467
13559
|
}
|
|
13468
13560
|
try {
|
|
13469
13561
|
const absPath = isAbsolute4(docPath) ? docPath : resolve6(process.cwd(), docPath);
|
|
13470
|
-
if (!
|
|
13562
|
+
if (!existsSync16(absPath)) {
|
|
13471
13563
|
return `\u274C \u521B\u5EFA\u5931\u8D25\uFF1Adoc_path \u6307\u5411\u7684\u6587\u4EF6\u4E0D\u5B58\u5728
|
|
13472
13564
|
\u8DEF\u5F84: ${docPath}
|
|
13473
13565
|
\u89E3\u6790\u540E: ${absPath}
|
|
@@ -13900,7 +13992,7 @@ var init_recall_score = __esm({
|
|
|
13900
13992
|
});
|
|
13901
13993
|
|
|
13902
13994
|
// src/memory/proactive/opportunity.ts
|
|
13903
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as
|
|
13995
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync17 } from "node:fs";
|
|
13904
13996
|
import { join as join31 } from "node:path";
|
|
13905
13997
|
function normalizeOpportunity(raw) {
|
|
13906
13998
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -13958,8 +14050,8 @@ function enqueueOpportunity(persist, opp, anchor, planningContext, previousTitle
|
|
|
13958
14050
|
function isProfilePrediction(text) {
|
|
13959
14051
|
return PROFILE_RE.test(text);
|
|
13960
14052
|
}
|
|
13961
|
-
function capForesightEntries(entries2,
|
|
13962
|
-
const isOpen = (e) => e.start <=
|
|
14053
|
+
function capForesightEntries(entries2, today3, openCap = 12, upcomingCap = 8) {
|
|
14054
|
+
const isOpen = (e) => e.start <= today3;
|
|
13963
14055
|
const sorted = entries2.slice().sort((a, b) => Number(isOpen(b)) - Number(isOpen(a)) || (isOpen(a) ? a.end.localeCompare(b.end) : a.start.localeCompare(b.start)));
|
|
13964
14056
|
const open = sorted.filter(isOpen).slice(0, openCap);
|
|
13965
14057
|
const upcoming = sorted.filter((e) => !isOpen(e)).slice(0, upcomingCap);
|
|
@@ -13983,11 +14075,11 @@ function loadForesightEntries(everosRoot, now, userId, ownerFilter) {
|
|
|
13983
14075
|
return entries2.slice();
|
|
13984
14076
|
}
|
|
13985
14077
|
function parseForesightEntries(everosRoot, now, userId) {
|
|
13986
|
-
const
|
|
14078
|
+
const today3 = todayKey(now);
|
|
13987
14079
|
let files;
|
|
13988
14080
|
if (userId) {
|
|
13989
14081
|
const ownDir = join31(everosRoot, userId, "default_project", "users", userId, ".foresights");
|
|
13990
|
-
files =
|
|
14082
|
+
files = existsSync17(ownDir) ? listForesightMds(ownDir) : findForesightFiles(everosRoot);
|
|
13991
14083
|
} else {
|
|
13992
14084
|
files = findForesightFiles(everosRoot);
|
|
13993
14085
|
}
|
|
@@ -14008,7 +14100,7 @@ function parseForesightEntries(everosRoot, now, userId) {
|
|
|
14008
14100
|
const end = (block.match(/\*\*end_time\*\*:\s*(\d{4}-\d{2}-\d{2})/) ?? [])[1];
|
|
14009
14101
|
const text = ((block.match(/### Foresight\s*\n+([\s\S]*?)(?:\n### Evidence|$)/) ?? [])[1] ?? "").trim();
|
|
14010
14102
|
if (!start || !end || !text) continue;
|
|
14011
|
-
if (end <
|
|
14103
|
+
if (end < today3) continue;
|
|
14012
14104
|
const evidence = ((block.match(/### Evidence\s*\n+([\s\S]*?)$/) ?? [])[1] ?? "").trim();
|
|
14013
14105
|
const blockOwner = (block.match(/\*\*owner_id\*\*:\s*(\S+)/) ?? [])[1] ?? fileOwner;
|
|
14014
14106
|
const key = text.replace(/\s+/g, "");
|
|
@@ -15420,14 +15512,14 @@ ${JSON.stringify(payload, null, 2)}
|
|
|
15420
15512
|
});
|
|
15421
15513
|
|
|
15422
15514
|
// src/memory/proactive/proactive-tools.ts
|
|
15423
|
-
import { existsSync as
|
|
15515
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
15424
15516
|
function setProactiveWorkspace(workspace) {
|
|
15425
15517
|
toolWorkspace = workspace;
|
|
15426
15518
|
}
|
|
15427
15519
|
function withStore(fn) {
|
|
15428
15520
|
if (!toolWorkspace) return null;
|
|
15429
15521
|
const p = proactiveDbPath(toolWorkspace);
|
|
15430
|
-
if (!
|
|
15522
|
+
if (!existsSync18(p)) return null;
|
|
15431
15523
|
const store = new Persistence(p);
|
|
15432
15524
|
try {
|
|
15433
15525
|
return fn(store);
|
|
@@ -15681,7 +15773,7 @@ __export(plugin_exports, {
|
|
|
15681
15773
|
feedMem7Cell: () => feedMem7Cell
|
|
15682
15774
|
});
|
|
15683
15775
|
import { join as join32 } from "node:path";
|
|
15684
|
-
import { existsSync as
|
|
15776
|
+
import { existsSync as existsSync19, readFileSync as readFileSync24, appendFileSync as appendFileSync3 } from "node:fs";
|
|
15685
15777
|
function isFiller(text) {
|
|
15686
15778
|
const t = text.trim();
|
|
15687
15779
|
if (t.length >= 5) return false;
|
|
@@ -15904,7 +15996,7 @@ var init_plugin = __esm({
|
|
|
15904
15996
|
this.actionAgent = new ActionAgent(this.provider, this.config.modelId, persona);
|
|
15905
15997
|
this.ingestWorkspace = workspace;
|
|
15906
15998
|
const calDbPath = join32(workspace, ".calendar", "calendar.db");
|
|
15907
|
-
this.calDb =
|
|
15999
|
+
this.calDb = existsSync19(calDbPath) ? openDb(workspace) : null;
|
|
15908
16000
|
registerCallbackHook("PostToolUse", {
|
|
15909
16001
|
type: "callback",
|
|
15910
16002
|
id: "proactive-completion-evidence",
|
|
@@ -16304,10 +16396,10 @@ ${String(input.tool_output ?? "")}`
|
|
|
16304
16396
|
|
|
16305
16397
|
// src/memory/sessionMemory/sessionMemoryUtils.ts
|
|
16306
16398
|
import { join as join33 } from "node:path";
|
|
16307
|
-
import { mkdirSync as
|
|
16399
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync25 } from "node:fs";
|
|
16308
16400
|
function setSessionMemoryStateDir(dir) {
|
|
16309
16401
|
_stateDir2 = dir;
|
|
16310
|
-
|
|
16402
|
+
mkdirSync12(dir, { recursive: true });
|
|
16311
16403
|
}
|
|
16312
16404
|
function getSessionMemoryDir() {
|
|
16313
16405
|
return _stateDir2;
|
|
@@ -16579,7 +16671,7 @@ __export(sessionMemory_exports, {
|
|
|
16579
16671
|
resetLastMemoryMessageUuid: () => resetLastMemoryMessageUuid,
|
|
16580
16672
|
shouldExtractMemory: () => shouldExtractMemory
|
|
16581
16673
|
});
|
|
16582
|
-
import { readFileSync as readFileSync26, mkdirSync as
|
|
16674
|
+
import { readFileSync as readFileSync26, mkdirSync as mkdirSync13 } from "node:fs";
|
|
16583
16675
|
import { writeFile as writeFile3 } from "node:fs/promises";
|
|
16584
16676
|
function initSessionMemory(deps) {
|
|
16585
16677
|
_deps = deps;
|
|
@@ -16638,7 +16730,7 @@ function shouldExtractMemory(estimatedTokens, messages) {
|
|
|
16638
16730
|
}
|
|
16639
16731
|
async function setupSessionMemoryFile() {
|
|
16640
16732
|
const memoryDir = getSessionMemoryDir();
|
|
16641
|
-
|
|
16733
|
+
mkdirSync13(memoryDir, { recursive: true });
|
|
16642
16734
|
const memoryPath = getSessionMemoryPath();
|
|
16643
16735
|
let currentMemory;
|
|
16644
16736
|
try {
|
|
@@ -16765,11 +16857,11 @@ async function readLastConsolidatedAt(memoryDir) {
|
|
|
16765
16857
|
}
|
|
16766
16858
|
}
|
|
16767
16859
|
async function tryAcquireConsolidationLock(memoryDir) {
|
|
16768
|
-
const
|
|
16860
|
+
const path60 = lockPath(memoryDir);
|
|
16769
16861
|
let mtimeMs;
|
|
16770
16862
|
let holderPid;
|
|
16771
16863
|
try {
|
|
16772
|
-
const [s, raw] = await Promise.all([stat3(
|
|
16864
|
+
const [s, raw] = await Promise.all([stat3(path60), readFile5(path60, "utf8")]);
|
|
16773
16865
|
mtimeMs = s.mtimeMs;
|
|
16774
16866
|
const parsed = parseInt(raw.trim(), 10);
|
|
16775
16867
|
holderPid = Number.isFinite(parsed) ? parsed : void 0;
|
|
@@ -16782,10 +16874,10 @@ async function tryAcquireConsolidationLock(memoryDir) {
|
|
|
16782
16874
|
}
|
|
16783
16875
|
}
|
|
16784
16876
|
await mkdir3(memoryDir, { recursive: true });
|
|
16785
|
-
await writeFile4(
|
|
16877
|
+
await writeFile4(path60, String(process.pid));
|
|
16786
16878
|
let verify2;
|
|
16787
16879
|
try {
|
|
16788
|
-
verify2 = await readFile5(
|
|
16880
|
+
verify2 = await readFile5(path60, "utf8");
|
|
16789
16881
|
} catch {
|
|
16790
16882
|
return null;
|
|
16791
16883
|
}
|
|
@@ -16793,15 +16885,15 @@ async function tryAcquireConsolidationLock(memoryDir) {
|
|
|
16793
16885
|
return mtimeMs ?? 0;
|
|
16794
16886
|
}
|
|
16795
16887
|
async function rollbackConsolidationLock(memoryDir, priorMtime) {
|
|
16796
|
-
const
|
|
16888
|
+
const path60 = lockPath(memoryDir);
|
|
16797
16889
|
try {
|
|
16798
16890
|
if (priorMtime === 0) {
|
|
16799
|
-
await unlink(
|
|
16891
|
+
await unlink(path60);
|
|
16800
16892
|
return;
|
|
16801
16893
|
}
|
|
16802
|
-
await writeFile4(
|
|
16894
|
+
await writeFile4(path60, "");
|
|
16803
16895
|
const t = priorMtime / 1e3;
|
|
16804
|
-
await utimes(
|
|
16896
|
+
await utimes(path60, t, t);
|
|
16805
16897
|
} catch (e) {
|
|
16806
16898
|
console.log(`[autoDream] rollback failed: ${e.message} \u2014 next trigger delayed to minHours`);
|
|
16807
16899
|
}
|
|
@@ -17403,12 +17495,12 @@ async function executeAndDeliver(task, now, deps) {
|
|
|
17403
17495
|
if (promptText.startsWith("@")) {
|
|
17404
17496
|
let filePath = promptText.slice(1).trim();
|
|
17405
17497
|
try {
|
|
17406
|
-
const
|
|
17407
|
-
const
|
|
17408
|
-
if (!
|
|
17409
|
-
filePath =
|
|
17498
|
+
const fs58 = await import("fs");
|
|
17499
|
+
const path60 = await import("path");
|
|
17500
|
+
if (!path60.isAbsolute(filePath)) {
|
|
17501
|
+
filePath = path60.join(deps.sessions["config"].stateDir, filePath);
|
|
17410
17502
|
}
|
|
17411
|
-
promptText =
|
|
17503
|
+
promptText = fs58.readFileSync(filePath, "utf-8");
|
|
17412
17504
|
console.log(`[cron] Loaded prompt from ${filePath} (${promptText.length} chars)`);
|
|
17413
17505
|
} catch (err) {
|
|
17414
17506
|
throw new Error(`Prompt file not found: ${filePath}: ${err.message}`);
|
|
@@ -17446,19 +17538,19 @@ async function executeAndDeliver(task, now, deps) {
|
|
|
17446
17538
|
let finalResult = result;
|
|
17447
17539
|
if (task.postProcess) {
|
|
17448
17540
|
try {
|
|
17449
|
-
const
|
|
17450
|
-
const
|
|
17541
|
+
const path60 = await import("path");
|
|
17542
|
+
const fs58 = await import("fs");
|
|
17451
17543
|
let scriptPath = task.postProcess;
|
|
17452
|
-
if (!
|
|
17453
|
-
scriptPath =
|
|
17454
|
-
}
|
|
17455
|
-
const resultsDirTmp =
|
|
17456
|
-
|
|
17457
|
-
const inputFile =
|
|
17458
|
-
|
|
17459
|
-
const { execFile:
|
|
17544
|
+
if (!path60.isAbsolute(scriptPath)) {
|
|
17545
|
+
scriptPath = path60.join(deps.sessions["config"].stateDir, scriptPath);
|
|
17546
|
+
}
|
|
17547
|
+
const resultsDirTmp = path60.join(getStorageDir(), "results");
|
|
17548
|
+
fs58.mkdirSync(resultsDirTmp, { recursive: true });
|
|
17549
|
+
const inputFile = path60.join(resultsDirTmp, `${task.id}.input.txt`);
|
|
17550
|
+
fs58.writeFileSync(inputFile, result, "utf-8");
|
|
17551
|
+
const { execFile: execFile5 } = await import("child_process");
|
|
17460
17552
|
await new Promise((resolve12) => {
|
|
17461
|
-
|
|
17553
|
+
execFile5("python", [scriptPath, "main", "--file", inputFile], {
|
|
17462
17554
|
cwd: deps.sessions["config"].stateDir,
|
|
17463
17555
|
timeout: 3e4,
|
|
17464
17556
|
maxBuffer: 1024 * 1024,
|
|
@@ -17482,12 +17574,12 @@ async function executeAndDeliver(task, now, deps) {
|
|
|
17482
17574
|
}
|
|
17483
17575
|
}
|
|
17484
17576
|
try {
|
|
17485
|
-
const
|
|
17486
|
-
const
|
|
17487
|
-
const resultsDir =
|
|
17488
|
-
|
|
17489
|
-
const resultFile =
|
|
17490
|
-
|
|
17577
|
+
const fs58 = await import("fs");
|
|
17578
|
+
const path60 = await import("path");
|
|
17579
|
+
const resultsDir = path60.join(getStorageDir(), "results");
|
|
17580
|
+
fs58.mkdirSync(resultsDir, { recursive: true });
|
|
17581
|
+
const resultFile = path60.join(resultsDir, `${task.id}.json`);
|
|
17582
|
+
fs58.writeFileSync(resultFile, JSON.stringify({
|
|
17491
17583
|
taskId: task.id,
|
|
17492
17584
|
description: task.description,
|
|
17493
17585
|
executedAt: now.toISOString(),
|
|
@@ -17817,30 +17909,30 @@ function registerCronTools() {
|
|
|
17817
17909
|
}
|
|
17818
17910
|
},
|
|
17819
17911
|
handler: async (args2) => {
|
|
17820
|
-
const
|
|
17821
|
-
const
|
|
17822
|
-
const resultsDir =
|
|
17823
|
-
if (!
|
|
17912
|
+
const fs58 = await import("fs");
|
|
17913
|
+
const path60 = await import("path");
|
|
17914
|
+
const resultsDir = path60.join(getStorageDir(), "results");
|
|
17915
|
+
if (!fs58.existsSync(resultsDir)) {
|
|
17824
17916
|
return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
|
|
17825
17917
|
}
|
|
17826
17918
|
if (args2.task_id) {
|
|
17827
|
-
const file2 =
|
|
17828
|
-
if (!
|
|
17919
|
+
const file2 = path60.join(resultsDir, `${args2.task_id}.json`);
|
|
17920
|
+
if (!fs58.existsSync(file2)) {
|
|
17829
17921
|
return { content: `\u4EFB\u52A1 ${args2.task_id} \u6682\u65E0\u6267\u884C\u7ED3\u679C`, isError: true };
|
|
17830
17922
|
}
|
|
17831
|
-
const data = JSON.parse(
|
|
17923
|
+
const data = JSON.parse(fs58.readFileSync(file2, "utf-8"));
|
|
17832
17924
|
return { content: `## ${data.description}
|
|
17833
17925
|
\u6267\u884C\u65F6\u95F4: ${data.executedAt}
|
|
17834
17926
|
\u7B2C${data.runCount}\u6B21\u6267\u884C
|
|
17835
17927
|
|
|
17836
17928
|
${data.result}` };
|
|
17837
17929
|
}
|
|
17838
|
-
const files =
|
|
17930
|
+
const files = fs58.readdirSync(resultsDir).filter((f) => f.endsWith(".json"));
|
|
17839
17931
|
if (files.length === 0) {
|
|
17840
17932
|
return { content: "\u6682\u65E0cron\u6267\u884C\u7ED3\u679C" };
|
|
17841
17933
|
}
|
|
17842
17934
|
const results = files.map((f) => {
|
|
17843
|
-
const data = JSON.parse(
|
|
17935
|
+
const data = JSON.parse(fs58.readFileSync(path60.join(resultsDir, f), "utf-8"));
|
|
17844
17936
|
return `### ${data.description} (${data.taskId.slice(0, 8)})
|
|
17845
17937
|
\u6267\u884C: ${data.executedAt} | \u7B2C${data.runCount}\u6B21
|
|
17846
17938
|
${data.result.slice(0, 500)}${data.result.length > 500 ? "..." : ""}`;
|
|
@@ -18383,10 +18475,13 @@ function readEngineVersion() {
|
|
|
18383
18475
|
}
|
|
18384
18476
|
return "unknown";
|
|
18385
18477
|
}
|
|
18478
|
+
function hasUsable(v) {
|
|
18479
|
+
return !!v && !isUnresolvedEnvRef(v);
|
|
18480
|
+
}
|
|
18386
18481
|
function channelHasCredentials(name, conf) {
|
|
18387
|
-
if (name === "feishu") return
|
|
18388
|
-
if (name === "wechat") return
|
|
18389
|
-
if (name === "discord") return
|
|
18482
|
+
if (name === "feishu") return hasUsable(conf?.appId) && hasUsable(conf?.appSecret);
|
|
18483
|
+
if (name === "wechat") return hasUsable(conf?.token) && hasUsable(conf?.accountId);
|
|
18484
|
+
if (name === "discord") return hasUsable(conf?.accounts?.default?.token);
|
|
18390
18485
|
return true;
|
|
18391
18486
|
}
|
|
18392
18487
|
function buildEngineStatus(config2, uptimeSec, version) {
|
|
@@ -18401,6 +18496,7 @@ function buildEngineStatus(config2, uptimeSec, version) {
|
|
|
18401
18496
|
var init_api_status = __esm({
|
|
18402
18497
|
"src/startup/api-status.ts"() {
|
|
18403
18498
|
"use strict";
|
|
18499
|
+
init_loader();
|
|
18404
18500
|
}
|
|
18405
18501
|
});
|
|
18406
18502
|
|
|
@@ -18502,7 +18598,7 @@ var reply_blocklist_exports = {};
|
|
|
18502
18598
|
__export(reply_blocklist_exports, {
|
|
18503
18599
|
isUserBlocked: () => isUserBlocked
|
|
18504
18600
|
});
|
|
18505
|
-
import { readFileSync as readFileSync33, writeFileSync as writeFileSync11, existsSync as
|
|
18601
|
+
import { readFileSync as readFileSync33, writeFileSync as writeFileSync11, existsSync as existsSync23 } from "node:fs";
|
|
18506
18602
|
import { join as join44 } from "node:path";
|
|
18507
18603
|
function ensureLoaded(workspace, configIds) {
|
|
18508
18604
|
if (loaded) return;
|
|
@@ -18511,10 +18607,10 @@ function ensureLoaded(workspace, configIds) {
|
|
|
18511
18607
|
if (!state.blockedUserIds.includes(id)) state.blockedUserIds.push(id);
|
|
18512
18608
|
}
|
|
18513
18609
|
}
|
|
18514
|
-
const
|
|
18610
|
+
const path60 = join44(workspace, ".reply-blocklist.json");
|
|
18515
18611
|
try {
|
|
18516
|
-
if (
|
|
18517
|
-
const raw = readFileSync33(
|
|
18612
|
+
if (existsSync23(path60)) {
|
|
18613
|
+
const raw = readFileSync33(path60, "utf-8");
|
|
18518
18614
|
const parsed = JSON.parse(raw);
|
|
18519
18615
|
if (parsed.blockedUserIds) {
|
|
18520
18616
|
for (const id of parsed.blockedUserIds) {
|
|
@@ -18530,9 +18626,9 @@ function ensureLoaded(workspace, configIds) {
|
|
|
18530
18626
|
loaded = true;
|
|
18531
18627
|
}
|
|
18532
18628
|
function save(workspace) {
|
|
18533
|
-
const
|
|
18629
|
+
const path60 = join44(workspace, ".reply-blocklist.json");
|
|
18534
18630
|
try {
|
|
18535
|
-
writeFileSync11(
|
|
18631
|
+
writeFileSync11(path60, JSON.stringify(state, null, 2), "utf-8");
|
|
18536
18632
|
} catch (err) {
|
|
18537
18633
|
console.warn(`[reply-blocklist] Failed to save: ${err.message}`);
|
|
18538
18634
|
}
|
|
@@ -18975,16 +19071,16 @@ __export(voice_chat_tts_daemon_exports, {
|
|
|
18975
19071
|
startTtsDaemon: () => startTtsDaemon,
|
|
18976
19072
|
stopTtsDaemon: () => stopTtsDaemon
|
|
18977
19073
|
});
|
|
18978
|
-
import { spawn as
|
|
18979
|
-
import
|
|
18980
|
-
import
|
|
18981
|
-
import
|
|
19074
|
+
import { spawn as spawn5 } from "child_process";
|
|
19075
|
+
import net2 from "net";
|
|
19076
|
+
import path39 from "path";
|
|
19077
|
+
import fs39 from "fs";
|
|
18982
19078
|
function _log(msg2) {
|
|
18983
19079
|
console.log(`[tts-daemon] ${msg2}`);
|
|
18984
19080
|
}
|
|
18985
19081
|
function _healthOnce() {
|
|
18986
19082
|
return new Promise((resolve12) => {
|
|
18987
|
-
const sock =
|
|
19083
|
+
const sock = net2.connect(SOCK_PATH);
|
|
18988
19084
|
sock.setTimeout(2e3);
|
|
18989
19085
|
sock.on("connect", () => {
|
|
18990
19086
|
sock.write(JSON.stringify({ action: "health" }) + "\n");
|
|
@@ -19023,14 +19119,14 @@ async function startTtsDaemon(warmupCfg) {
|
|
|
19023
19119
|
return;
|
|
19024
19120
|
}
|
|
19025
19121
|
const script = (() => {
|
|
19026
|
-
const distDir =
|
|
19122
|
+
const distDir = path39.dirname(new URL(import.meta.url).pathname);
|
|
19027
19123
|
const candidates = [
|
|
19028
|
-
|
|
19029
|
-
|
|
19124
|
+
path39.join(distDir, "..", "python", "tts_daemon.py"),
|
|
19125
|
+
path39.join(process.cwd(), "engine/src/python/tts_daemon.py"),
|
|
19030
19126
|
"/Users/chongzhang/work/twinsun-hearth/engine/src/python/tts_daemon.py"
|
|
19031
19127
|
];
|
|
19032
19128
|
for (const c of candidates) {
|
|
19033
|
-
if (
|
|
19129
|
+
if (fs39.existsSync(c)) return c;
|
|
19034
19130
|
}
|
|
19035
19131
|
return candidates[0];
|
|
19036
19132
|
})();
|
|
@@ -19045,7 +19141,7 @@ async function startTtsDaemon(warmupCfg) {
|
|
|
19045
19141
|
...warmupCfg?.model ? { TTS_MODEL: warmupCfg.model } : {},
|
|
19046
19142
|
...warmupCfg?.workspaceId ? { TTS_WORKSPACE_ID: warmupCfg.workspaceId } : {}
|
|
19047
19143
|
};
|
|
19048
|
-
daemonProcess =
|
|
19144
|
+
daemonProcess = spawn5("python3", [script], {
|
|
19049
19145
|
stdio: ["pipe", "pipe", "pipe"],
|
|
19050
19146
|
env
|
|
19051
19147
|
});
|
|
@@ -19102,15 +19198,15 @@ var read_exports = {};
|
|
|
19102
19198
|
__export(read_exports, {
|
|
19103
19199
|
readFileState: () => readFileState
|
|
19104
19200
|
});
|
|
19105
|
-
import * as
|
|
19106
|
-
import * as
|
|
19201
|
+
import * as fs51 from "node:fs";
|
|
19202
|
+
import * as path50 from "node:path";
|
|
19107
19203
|
function isBlockedDevicePath(filePath) {
|
|
19108
19204
|
if (BLOCKED_DEVICE_PATHS.has(filePath)) return true;
|
|
19109
19205
|
if (filePath.startsWith("/proc/") && (filePath.endsWith("/fd/0") || filePath.endsWith("/fd/1") || filePath.endsWith("/fd/2"))) return true;
|
|
19110
19206
|
return false;
|
|
19111
19207
|
}
|
|
19112
19208
|
function checkReadLoop(filePath, offset, limit) {
|
|
19113
|
-
const stat4 =
|
|
19209
|
+
const stat4 = fs51.statSync(filePath);
|
|
19114
19210
|
const mtimeMs = stat4.mtimeMs;
|
|
19115
19211
|
const prev = readHistory.get(filePath);
|
|
19116
19212
|
if (prev && prev.offset === offset && prev.limit === limit && prev.mtimeMs === mtimeMs) {
|
|
@@ -19124,19 +19220,19 @@ function checkReadLoop(filePath, offset, limit) {
|
|
|
19124
19220
|
return null;
|
|
19125
19221
|
}
|
|
19126
19222
|
function readFileContent(filePath) {
|
|
19127
|
-
const fd =
|
|
19223
|
+
const fd = fs51.openSync(filePath, "r");
|
|
19128
19224
|
const bom = Buffer.alloc(2);
|
|
19129
|
-
|
|
19130
|
-
|
|
19225
|
+
fs51.readSync(fd, bom, 0, 2, 0);
|
|
19226
|
+
fs51.closeSync(fd);
|
|
19131
19227
|
let encoding = "utf8";
|
|
19132
19228
|
if (bom[0] === 255 && bom[1] === 254) {
|
|
19133
19229
|
encoding = "utf16le";
|
|
19134
19230
|
}
|
|
19135
|
-
const stat4 =
|
|
19231
|
+
const stat4 = fs51.statSync(filePath);
|
|
19136
19232
|
if (stat4.size > MAX_FILE_SIZE) {
|
|
19137
19233
|
throw new Error(`\u6587\u4EF6\u592A\u5927 (${(stat4.size / 1024).toFixed(1)}KB)\uFF0C\u8D85\u8FC7 ${MAX_FILE_SIZE / 1024}KB \u9650\u5236\u3002\u8BF7\u4F7F\u7528 offset + limit \u5206\u6BB5\u8BFB\u53D6\u3002`);
|
|
19138
19234
|
}
|
|
19139
|
-
const raw =
|
|
19235
|
+
const raw = fs51.readFileSync(filePath, encoding);
|
|
19140
19236
|
const content = raw.toString().replaceAll("\r\n", "\n");
|
|
19141
19237
|
return { content, encoding };
|
|
19142
19238
|
}
|
|
@@ -19278,11 +19374,11 @@ Usage:
|
|
|
19278
19374
|
} catch (e) {
|
|
19279
19375
|
return { content: e.message, isError: true };
|
|
19280
19376
|
}
|
|
19281
|
-
if (!
|
|
19377
|
+
if (!fs51.existsSync(filePath)) {
|
|
19282
19378
|
return { content: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, isError: true };
|
|
19283
19379
|
}
|
|
19284
|
-
const stat4 =
|
|
19285
|
-
const baseName =
|
|
19380
|
+
const stat4 = fs51.statSync(filePath);
|
|
19381
|
+
const baseName = path50.basename(filePath).toUpperCase();
|
|
19286
19382
|
if (BLOCKED_BASENAMES.has(baseName)) {
|
|
19287
19383
|
return { content: `\u8BBE\u5907\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6: ${filePath}`, isError: true };
|
|
19288
19384
|
}
|
|
@@ -19290,11 +19386,11 @@ Usage:
|
|
|
19290
19386
|
return { content: `\u8BBE\u5907\u6587\u4EF6\u4F1A\u963B\u585E\u6216\u4EA7\u751F\u65E0\u9650\u8F93\u51FA: ${filePath}`, isError: true };
|
|
19291
19387
|
}
|
|
19292
19388
|
if (stat4.isDirectory()) {
|
|
19293
|
-
const entries2 =
|
|
19389
|
+
const entries2 = fs51.readdirSync(filePath);
|
|
19294
19390
|
const items = entries2.map((e) => {
|
|
19295
|
-
const full =
|
|
19391
|
+
const full = path50.join(filePath, e);
|
|
19296
19392
|
try {
|
|
19297
|
-
const s =
|
|
19393
|
+
const s = fs51.statSync(full);
|
|
19298
19394
|
return s.isDirectory() ? `${e}/` : e;
|
|
19299
19395
|
} catch {
|
|
19300
19396
|
return e;
|
|
@@ -19303,7 +19399,7 @@ Usage:
|
|
|
19303
19399
|
return { content: `\u76EE\u5F55 (${entries2.length} \u9879):
|
|
19304
19400
|
${items.join("\n")}` };
|
|
19305
19401
|
}
|
|
19306
|
-
const ext =
|
|
19402
|
+
const ext = path50.extname(filePath).toLowerCase();
|
|
19307
19403
|
if (BINARY_EXTENSIONS.has(ext)) {
|
|
19308
19404
|
return { content: `\u4E8C\u8FDB\u5236\u6587\u4EF6\u4E0D\u652F\u6301\u8BFB\u53D6 (${ext}): ${filePath}`, isError: true };
|
|
19309
19405
|
}
|
|
@@ -19351,22 +19447,22 @@ ${result}` : result };
|
|
|
19351
19447
|
|
|
19352
19448
|
// src/tools/write.ts
|
|
19353
19449
|
var write_exports = {};
|
|
19354
|
-
import * as
|
|
19355
|
-
import * as
|
|
19450
|
+
import * as fs52 from "node:fs";
|
|
19451
|
+
import * as path51 from "node:path";
|
|
19356
19452
|
function isBlockedPath(filePath) {
|
|
19357
19453
|
return BLOCKED_PATTERNS.some((p) => p.test(filePath));
|
|
19358
19454
|
}
|
|
19359
19455
|
function atomicWrite(filePath, content) {
|
|
19360
19456
|
const tmpPath = filePath + ".tmp." + Date.now() + ".write";
|
|
19361
|
-
|
|
19457
|
+
fs52.writeFileSync(tmpPath, content, "utf-8");
|
|
19362
19458
|
try {
|
|
19363
|
-
|
|
19459
|
+
fs52.renameSync(tmpPath, filePath);
|
|
19364
19460
|
} catch (e) {
|
|
19365
19461
|
try {
|
|
19366
|
-
|
|
19462
|
+
fs52.unlinkSync(tmpPath);
|
|
19367
19463
|
} catch {
|
|
19368
19464
|
}
|
|
19369
|
-
|
|
19465
|
+
fs52.writeFileSync(filePath, content, "utf-8");
|
|
19370
19466
|
}
|
|
19371
19467
|
}
|
|
19372
19468
|
function simpleDiff(oldContent, newContent) {
|
|
@@ -19460,15 +19556,15 @@ Usage:
|
|
|
19460
19556
|
}
|
|
19461
19557
|
const rawContent = args2.content;
|
|
19462
19558
|
const content = rawContent.replaceAll("\r\n", "\n");
|
|
19463
|
-
if (
|
|
19559
|
+
if (fs52.existsSync(filePath) && fs52.statSync(filePath).isDirectory()) {
|
|
19464
19560
|
return { content: `\u8DEF\u5F84\u662F\u76EE\u5F55\u4E0D\u662F\u6587\u4EF6: ${filePath}`, isError: true };
|
|
19465
19561
|
}
|
|
19466
19562
|
let oldContent = null;
|
|
19467
19563
|
let isCreate = true;
|
|
19468
|
-
if (
|
|
19564
|
+
if (fs52.existsSync(filePath)) {
|
|
19469
19565
|
isCreate = false;
|
|
19470
19566
|
try {
|
|
19471
|
-
oldContent =
|
|
19567
|
+
oldContent = fs52.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
|
|
19472
19568
|
} catch {
|
|
19473
19569
|
isCreate = true;
|
|
19474
19570
|
}
|
|
@@ -19484,10 +19580,10 @@ Usage:
|
|
|
19484
19580
|
isError: true
|
|
19485
19581
|
};
|
|
19486
19582
|
}
|
|
19487
|
-
const currentStat =
|
|
19583
|
+
const currentStat = fs52.statSync(filePath);
|
|
19488
19584
|
const lastWriteTime = Math.floor(currentStat.mtimeMs);
|
|
19489
19585
|
if (lastWriteTime > readState.timestamp) {
|
|
19490
|
-
const currentContent =
|
|
19586
|
+
const currentContent = fs52.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n");
|
|
19491
19587
|
if (currentContent !== oldContent) {
|
|
19492
19588
|
return {
|
|
19493
19589
|
content: `\u6587\u4EF6\u5728\u8BFB\u53D6\u540E\u88AB\u4FEE\u6539\u3002\u8BF7\u5148\u91CD\u65B0\u8BFB\u53D6\u6587\u4EF6\u518D\u5199\u5165: ${filePath}`,
|
|
@@ -19496,9 +19592,9 @@ Usage:
|
|
|
19496
19592
|
}
|
|
19497
19593
|
}
|
|
19498
19594
|
}
|
|
19499
|
-
const dir =
|
|
19595
|
+
const dir = path51.dirname(filePath);
|
|
19500
19596
|
try {
|
|
19501
|
-
|
|
19597
|
+
fs52.mkdirSync(dir, { recursive: true });
|
|
19502
19598
|
} catch (e) {
|
|
19503
19599
|
return { content: `\u65E0\u6CD5\u521B\u5EFA\u76EE\u5F55: ${dir} \u2014 ${e.message}`, isError: true };
|
|
19504
19600
|
}
|
|
@@ -19507,11 +19603,11 @@ Usage:
|
|
|
19507
19603
|
} catch (e) {
|
|
19508
19604
|
return { content: `\u5199\u5165\u5931\u8D25: ${e.message}`, isError: true };
|
|
19509
19605
|
}
|
|
19510
|
-
readFileState.set(filePath, { timestamp:
|
|
19606
|
+
readFileState.set(filePath, { timestamp: fs52.statSync(filePath).mtimeMs });
|
|
19511
19607
|
const action = isCreate ? "\u521B\u5EFA" : "\u66F4\u65B0";
|
|
19512
19608
|
const lines = content.split("\n").length;
|
|
19513
19609
|
const chars = content.length;
|
|
19514
|
-
const stat4 =
|
|
19610
|
+
const stat4 = fs52.statSync(filePath);
|
|
19515
19611
|
let diff = "";
|
|
19516
19612
|
if (!isCreate && oldContent !== null) {
|
|
19517
19613
|
diff = `
|
|
@@ -19531,8 +19627,8 @@ ${simpleDiff(oldContent, content)}`;
|
|
|
19531
19627
|
|
|
19532
19628
|
// src/tools/edit.ts
|
|
19533
19629
|
var edit_exports = {};
|
|
19534
|
-
import * as
|
|
19535
|
-
import * as
|
|
19630
|
+
import * as fs53 from "node:fs";
|
|
19631
|
+
import * as path52 from "node:path";
|
|
19536
19632
|
function normalizeQuotes(str) {
|
|
19537
19633
|
return str.replaceAll(LEFT_SINGLE_CURLY, "'").replaceAll(RIGHT_SINGLE_CURLY, "'").replaceAll(LEFT_DOUBLE_CURLY, '"').replaceAll(RIGHT_DOUBLE_CURLY, '"');
|
|
19538
19634
|
}
|
|
@@ -19685,7 +19781,7 @@ Usage:
|
|
|
19685
19781
|
}
|
|
19686
19782
|
let fileContent = null;
|
|
19687
19783
|
try {
|
|
19688
|
-
const stat4 =
|
|
19784
|
+
const stat4 = fs53.statSync(filePath);
|
|
19689
19785
|
if (stat4.isDirectory()) {
|
|
19690
19786
|
return { content: `\u8DEF\u5F84\u662F\u76EE\u5F55\u4E0D\u662F\u6587\u4EF6: ${filePath}`, isError: true };
|
|
19691
19787
|
}
|
|
@@ -19695,21 +19791,21 @@ Usage:
|
|
|
19695
19791
|
} catch (e) {
|
|
19696
19792
|
if (e.code === "ENOENT") {
|
|
19697
19793
|
if (oldString === "") {
|
|
19698
|
-
const dir =
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
readFileState.set(filePath, { timestamp:
|
|
19794
|
+
const dir = path52.dirname(filePath);
|
|
19795
|
+
fs53.mkdirSync(dir, { recursive: true });
|
|
19796
|
+
fs53.writeFileSync(filePath, newString, "utf-8");
|
|
19797
|
+
readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
|
|
19702
19798
|
return { content: `\u521B\u5EFA\u6587\u4EF6: ${filePath} (${newString.split("\n").length} \u884C)` };
|
|
19703
19799
|
}
|
|
19704
19800
|
return { content: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${filePath}`, isError: true };
|
|
19705
19801
|
}
|
|
19706
19802
|
throw e;
|
|
19707
19803
|
}
|
|
19708
|
-
const rawContent =
|
|
19804
|
+
const rawContent = fs53.readFileSync(filePath, "utf-8");
|
|
19709
19805
|
fileContent = rawContent.replaceAll("\r\n", "\n");
|
|
19710
19806
|
if (oldString === "" && fileContent.trim() === "") {
|
|
19711
|
-
|
|
19712
|
-
readFileState.set(filePath, { timestamp:
|
|
19807
|
+
fs53.writeFileSync(filePath, newString, "utf-8");
|
|
19808
|
+
readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
|
|
19713
19809
|
return { content: `\u5199\u5165\u7A7A\u6587\u4EF6: ${filePath} (${newString.split("\n").length} \u884C)` };
|
|
19714
19810
|
}
|
|
19715
19811
|
const readState = readFileState.get(filePath);
|
|
@@ -19719,11 +19815,11 @@ Usage:
|
|
|
19719
19815
|
isError: true
|
|
19720
19816
|
};
|
|
19721
19817
|
}
|
|
19722
|
-
const currentStat =
|
|
19818
|
+
const currentStat = fs53.statSync(filePath);
|
|
19723
19819
|
const lastWriteTime = Math.floor(currentStat.mtimeMs);
|
|
19724
19820
|
if (lastWriteTime > readState.timestamp) {
|
|
19725
19821
|
if (fileContent !== rawContent.replaceAll("\r\n", "\n")) {
|
|
19726
|
-
if (fileContent !==
|
|
19822
|
+
if (fileContent !== fs53.readFileSync(filePath, "utf-8").replaceAll("\r\n", "\n")) {
|
|
19727
19823
|
return {
|
|
19728
19824
|
content: `\u6587\u4EF6\u5728\u8BFB\u53D6\u540E\u88AB\u4FEE\u6539\u3002\u8BF7\u5148\u91CD\u65B0\u8BFB\u53D6\u6587\u4EF6\u518D\u7F16\u8F91: ${filePath}`,
|
|
19729
19825
|
isError: true
|
|
@@ -19755,8 +19851,8 @@ ${preview}
|
|
|
19755
19851
|
const actualNewString = preserveQuoteStyle(oldString, actualOldString, newString);
|
|
19756
19852
|
const diffView = generateEditDiff(fileContent, actualOldString, actualNewString);
|
|
19757
19853
|
const newContent = applyEditToFile(fileContent, actualOldString, actualNewString, replaceAll);
|
|
19758
|
-
|
|
19759
|
-
readFileState.set(filePath, { timestamp:
|
|
19854
|
+
fs53.writeFileSync(filePath, newContent, "utf-8");
|
|
19855
|
+
readFileState.set(filePath, { timestamp: fs53.statSync(filePath).mtimeMs });
|
|
19760
19856
|
const strategy = actualOldString === oldString ? "\u7CBE\u786E\u5339\u914D" : "\u5F15\u53F7\u89C4\u8303\u5316\u5339\u914D";
|
|
19761
19857
|
const count = replaceAll ? matchCount : 1;
|
|
19762
19858
|
const diff = `${oldString.length}\u2192${newString.length}\u5B57\u7B26`;
|
|
@@ -19774,8 +19870,8 @@ ${diffView}`
|
|
|
19774
19870
|
|
|
19775
19871
|
// src/tools/glob.ts
|
|
19776
19872
|
var glob_exports = {};
|
|
19777
|
-
import * as
|
|
19778
|
-
import * as
|
|
19873
|
+
import * as fs54 from "node:fs";
|
|
19874
|
+
import * as path53 from "node:path";
|
|
19779
19875
|
function globMatch(pattern, filename) {
|
|
19780
19876
|
const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
19781
19877
|
try {
|
|
@@ -19795,18 +19891,18 @@ function findFiles(dir, pattern, limit, baseDir) {
|
|
|
19795
19891
|
}
|
|
19796
19892
|
let entries2;
|
|
19797
19893
|
try {
|
|
19798
|
-
entries2 =
|
|
19894
|
+
entries2 = fs54.readdirSync(currentDir, { withFileTypes: true });
|
|
19799
19895
|
} catch {
|
|
19800
19896
|
return;
|
|
19801
19897
|
}
|
|
19802
19898
|
for (const entry of entries2) {
|
|
19803
19899
|
if (truncated) return;
|
|
19804
|
-
const fullPath =
|
|
19900
|
+
const fullPath = path53.join(currentDir, entry.name);
|
|
19805
19901
|
if (entry.isDirectory()) {
|
|
19806
19902
|
if (VCS_DIRS.has(entry.name)) continue;
|
|
19807
19903
|
walk(fullPath);
|
|
19808
19904
|
} else if (entry.isFile()) {
|
|
19809
|
-
const relativePath =
|
|
19905
|
+
const relativePath = path53.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
19810
19906
|
const patternsToTry = [pattern];
|
|
19811
19907
|
if (pattern.startsWith("**/")) {
|
|
19812
19908
|
patternsToTry.push(pattern.slice(3));
|
|
@@ -19816,7 +19912,7 @@ function findFiles(dir, pattern, limit, baseDir) {
|
|
|
19816
19912
|
);
|
|
19817
19913
|
if (matched) {
|
|
19818
19914
|
try {
|
|
19819
|
-
const stat4 =
|
|
19915
|
+
const stat4 = fs54.statSync(fullPath);
|
|
19820
19916
|
results.push({ path: fullPath, mtimeMs: stat4.mtimeMs });
|
|
19821
19917
|
} catch {
|
|
19822
19918
|
}
|
|
@@ -19833,7 +19929,7 @@ function findFiles(dir, pattern, limit, baseDir) {
|
|
|
19833
19929
|
};
|
|
19834
19930
|
}
|
|
19835
19931
|
function toRelativePath(absolutePath, cwd) {
|
|
19836
|
-
if (absolutePath.startsWith(cwd +
|
|
19932
|
+
if (absolutePath.startsWith(cwd + path53.sep)) {
|
|
19837
19933
|
return absolutePath.slice(cwd.length + 1);
|
|
19838
19934
|
}
|
|
19839
19935
|
return absolutePath;
|
|
@@ -19866,10 +19962,10 @@ var init_glob = __esm({
|
|
|
19866
19962
|
const searchPath = args2.path ? resolvePath(args2.path, ctx.workspace) : ctx.workspace;
|
|
19867
19963
|
const pattern = args2.pattern;
|
|
19868
19964
|
const limit = args2.limit || DEFAULT_LIMIT;
|
|
19869
|
-
if (!
|
|
19965
|
+
if (!fs54.existsSync(searchPath)) {
|
|
19870
19966
|
return { content: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${searchPath}`, isError: true };
|
|
19871
19967
|
}
|
|
19872
|
-
if (!
|
|
19968
|
+
if (!fs54.statSync(searchPath).isDirectory()) {
|
|
19873
19969
|
return { content: `\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${searchPath}`, isError: true };
|
|
19874
19970
|
}
|
|
19875
19971
|
const start = Date.now();
|
|
@@ -19893,12 +19989,12 @@ ${filenames.join("\n")}${truncatedNote}`
|
|
|
19893
19989
|
|
|
19894
19990
|
// src/tools/grep.ts
|
|
19895
19991
|
var grep_exports = {};
|
|
19896
|
-
import { execFile as
|
|
19897
|
-
import * as
|
|
19992
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
19993
|
+
import * as path54 from "node:path";
|
|
19898
19994
|
function ripGrep(args2, searchPath, signal) {
|
|
19899
19995
|
return new Promise((resolve12) => {
|
|
19900
19996
|
const fullArgs = [...args2, searchPath];
|
|
19901
|
-
const child =
|
|
19997
|
+
const child = execFile4("rg", fullArgs, {
|
|
19902
19998
|
maxBuffer: 50 * 1024 * 1024,
|
|
19903
19999
|
// 50MB
|
|
19904
20000
|
timeout: RG_TIMEOUT_MS,
|
|
@@ -19926,7 +20022,7 @@ function applyHeadLimit(items, limit, offset = 0) {
|
|
|
19926
20022
|
};
|
|
19927
20023
|
}
|
|
19928
20024
|
function toRelativePath2(absolutePath, cwd) {
|
|
19929
|
-
if (absolutePath.startsWith(cwd +
|
|
20025
|
+
if (absolutePath.startsWith(cwd + path54.sep)) {
|
|
19930
20026
|
return absolutePath.slice(cwd.length + 1);
|
|
19931
20027
|
}
|
|
19932
20028
|
if (absolutePath.startsWith(cwd)) {
|
|
@@ -22338,7 +22434,7 @@ var init_web_fetch = __esm({
|
|
|
22338
22434
|
|
|
22339
22435
|
// src/tools/wechat/wx-query.ts
|
|
22340
22436
|
var wx_query_exports = {};
|
|
22341
|
-
import { join as
|
|
22437
|
+
import { join as join60 } from "node:path";
|
|
22342
22438
|
function getDescription() {
|
|
22343
22439
|
return `\u67E5\u8BE2\u7FC0\u54E5\u7684\u5FAE\u4FE1\u6D88\u606F\uFF08\u7F13\u5B58\u89E3\u5BC6\u540E\u7684\u672C\u5730\u6570\u636E\u5E93\uFF09\u3002
|
|
22344
22440
|
|
|
@@ -22373,8 +22469,8 @@ var init_wx_query = __esm({
|
|
|
22373
22469
|
"use strict";
|
|
22374
22470
|
init_registry();
|
|
22375
22471
|
init_live();
|
|
22376
|
-
STATE_DIR = process.env.ENGINE7_STATE_DIR ||
|
|
22377
|
-
SCRIPT =
|
|
22472
|
+
STATE_DIR = process.env.ENGINE7_STATE_DIR || join60(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
22473
|
+
SCRIPT = join60("/Users/chongzhang/work/twinsun-hearth/engine", "src", "tools", "wechat", "wx_query.py");
|
|
22378
22474
|
PYTHON = "python3";
|
|
22379
22475
|
registry.register({
|
|
22380
22476
|
name: "wx_query",
|
|
@@ -22402,12 +22498,12 @@ var init_wx_query = __esm({
|
|
|
22402
22498
|
handler: async (args2, _context) => {
|
|
22403
22499
|
const { findShell: findShell2 } = await Promise.resolve().then(() => (init_BashTool(), BashTool_exports));
|
|
22404
22500
|
const shell = findShell2();
|
|
22405
|
-
const { spawn:
|
|
22501
|
+
const { spawn: spawn7 } = await import("node:child_process");
|
|
22406
22502
|
const cmd = args2.command.trim();
|
|
22407
22503
|
const timeout = args2.timeout || 12e4;
|
|
22408
22504
|
const fullCmd = `${PYTHON} "${SCRIPT}" ${cmd.replace(/^python3\s+.*wx_query\.py\s*/, "")}`;
|
|
22409
22505
|
return new Promise((resolve12) => {
|
|
22410
|
-
const child =
|
|
22506
|
+
const child = spawn7(shell.shell, [...shell.args, fullCmd], {
|
|
22411
22507
|
timeout,
|
|
22412
22508
|
env: { ...process.env }
|
|
22413
22509
|
});
|
|
@@ -22660,10 +22756,11 @@ var init_features2 = __esm({
|
|
|
22660
22756
|
// src/mcp/manager.ts
|
|
22661
22757
|
var manager_exports = {};
|
|
22662
22758
|
__export(manager_exports, {
|
|
22663
|
-
McpManager: () => McpManager
|
|
22759
|
+
McpManager: () => McpManager,
|
|
22760
|
+
convertInputSchema: () => convertInputSchema
|
|
22664
22761
|
});
|
|
22665
|
-
import * as
|
|
22666
|
-
import * as
|
|
22762
|
+
import * as fs57 from "node:fs";
|
|
22763
|
+
import * as path57 from "node:path";
|
|
22667
22764
|
import { Client as Client3 } from "@modelcontextprotocol/sdk/client/index.js";
|
|
22668
22765
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
22669
22766
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -22681,6 +22778,14 @@ function convertInputSchema(inputSchema) {
|
|
|
22681
22778
|
const properties = {};
|
|
22682
22779
|
if (inputSchema.properties && typeof inputSchema.properties === "object") {
|
|
22683
22780
|
for (const [key, prop] of Object.entries(inputSchema.properties)) {
|
|
22781
|
+
const union = prop.anyOf ?? prop.oneOf;
|
|
22782
|
+
if (union) {
|
|
22783
|
+
properties[key] = {
|
|
22784
|
+
anyOf: union,
|
|
22785
|
+
...prop.description ? { description: prop.description } : {}
|
|
22786
|
+
};
|
|
22787
|
+
continue;
|
|
22788
|
+
}
|
|
22684
22789
|
properties[key] = {
|
|
22685
22790
|
type: prop.type ?? "string",
|
|
22686
22791
|
...prop.description ? { description: prop.description } : {},
|
|
@@ -22696,12 +22801,12 @@ function convertInputSchema(inputSchema) {
|
|
|
22696
22801
|
}
|
|
22697
22802
|
function persistBinary(base64Data, mimeType, persistId) {
|
|
22698
22803
|
const ext = mimeType?.split("/")[1] || "bin";
|
|
22699
|
-
const dir =
|
|
22700
|
-
|
|
22701
|
-
const filepath =
|
|
22804
|
+
const dir = path57.join(process.env.ENGINE_STATE_DIR || ".engine", "mcp-blobs");
|
|
22805
|
+
fs57.mkdirSync(dir, { recursive: true });
|
|
22806
|
+
const filepath = path57.join(dir, `${persistId}.${ext}`);
|
|
22702
22807
|
try {
|
|
22703
22808
|
const buf = Buffer.from(base64Data, "base64");
|
|
22704
|
-
|
|
22809
|
+
fs57.writeFileSync(filepath, buf);
|
|
22705
22810
|
return { filepath, size: buf.length };
|
|
22706
22811
|
} catch (err) {
|
|
22707
22812
|
return { error: err.message };
|
|
@@ -23035,7 +23140,7 @@ __export(resources_exports, {
|
|
|
23035
23140
|
registerMcpResourceTools: () => registerMcpResourceTools,
|
|
23036
23141
|
unregisterMcpResourceTools: () => unregisterMcpResourceTools
|
|
23037
23142
|
});
|
|
23038
|
-
import * as
|
|
23143
|
+
import * as path58 from "node:path";
|
|
23039
23144
|
function registerMcpResourceTools(manager) {
|
|
23040
23145
|
mcpManagerRef = manager;
|
|
23041
23146
|
registry.register(listResourcesTool);
|
|
@@ -23053,7 +23158,7 @@ var init_resources = __esm({
|
|
|
23053
23158
|
"use strict";
|
|
23054
23159
|
init_registry();
|
|
23055
23160
|
MAX_RESULT_CHARS2 = 1e5;
|
|
23056
|
-
MEDIA_DIR = process.env.ENGINE_MEDIA_DIR ||
|
|
23161
|
+
MEDIA_DIR = process.env.ENGINE_MEDIA_DIR || path58.join(process.env.ENGINE_STATE_DIR || ".engine", "media", "inbound");
|
|
23057
23162
|
MCP_LIST_RESOURCES_TOOL = "mcp__list_resources";
|
|
23058
23163
|
MCP_READ_RESOURCE_TOOL = "mcp__read_resource";
|
|
23059
23164
|
mcpManagerRef = null;
|
|
@@ -23147,14 +23252,15 @@ import { basename, dirname, join as join2 } from "node:path";
|
|
|
23147
23252
|
function secretsHome() {
|
|
23148
23253
|
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
23149
23254
|
}
|
|
23150
|
-
function
|
|
23151
|
-
|
|
23152
|
-
|
|
23153
|
-
join2(home, ".engine7-secrets", `${configName}.env`),
|
|
23255
|
+
function secretsCandidates(stateDir, configName) {
|
|
23256
|
+
return [
|
|
23257
|
+
join2(secretsHome(), ".engine7-secrets", `${configName}.env`),
|
|
23154
23258
|
join2(stateDir, "configs", `.env.${configName}`),
|
|
23155
23259
|
join2(stateDir, "configs", ".env")
|
|
23156
23260
|
];
|
|
23157
|
-
|
|
23261
|
+
}
|
|
23262
|
+
function loadSecretsEnv(stateDir, configName) {
|
|
23263
|
+
for (const p of secretsCandidates(stateDir, configName)) {
|
|
23158
23264
|
if (!existsSync2(p)) continue;
|
|
23159
23265
|
for (const line of readFileSync2(p, "utf-8").split("\n")) {
|
|
23160
23266
|
const t = line.trim();
|
|
@@ -23170,17 +23276,21 @@ function loadSecretsEnv(stateDir, configName) {
|
|
|
23170
23276
|
}
|
|
23171
23277
|
return null;
|
|
23172
23278
|
}
|
|
23173
|
-
function
|
|
23279
|
+
function resolveConfigIdentity(configPath2) {
|
|
23174
23280
|
const cfgName = basename(configPath2, ".json");
|
|
23175
23281
|
const cfgDir = dirname(configPath2);
|
|
23176
23282
|
const stateDir = basename(cfgDir) === "configs" ? dirname(cfgDir) : cfgDir;
|
|
23177
|
-
return
|
|
23283
|
+
return { stateDir, configName: cfgName };
|
|
23284
|
+
}
|
|
23285
|
+
function loadSecretsEnvForConfig(configPath2) {
|
|
23286
|
+
const { stateDir, configName } = resolveConfigIdentity(configPath2);
|
|
23287
|
+
return loadSecretsEnv(stateDir, configName);
|
|
23178
23288
|
}
|
|
23179
23289
|
|
|
23180
23290
|
// src/engine-startup.ts
|
|
23181
23291
|
init_query();
|
|
23182
23292
|
init_live();
|
|
23183
|
-
import * as
|
|
23293
|
+
import * as path59 from "node:path";
|
|
23184
23294
|
|
|
23185
23295
|
// src/startup/providers.ts
|
|
23186
23296
|
init_provider_factory();
|
|
@@ -23792,79 +23902,101 @@ function startConfigWatcher(config2, deps, provider) {
|
|
|
23792
23902
|
}
|
|
23793
23903
|
return activeWatcher;
|
|
23794
23904
|
}
|
|
23795
|
-
function
|
|
23796
|
-
|
|
23905
|
+
function shouldHandleSecretsEvent(filename, configName, ownNames = [`${configName}.env`]) {
|
|
23906
|
+
if (!filename) return false;
|
|
23907
|
+
if (ownNames.includes(filename)) return true;
|
|
23908
|
+
if (filename.startsWith(".")) return false;
|
|
23909
|
+
if (filename.endsWith("~")) return false;
|
|
23910
|
+
if (/\.(swp|swx|swo|tmp)$/.test(filename)) return false;
|
|
23911
|
+
if (filename.includes(".tmp-")) return false;
|
|
23912
|
+
return filename === `${configName}.env`;
|
|
23913
|
+
}
|
|
23914
|
+
function startSecretsWatcher(config2, deps, provider, watchFn = fs10.watch) {
|
|
23797
23915
|
const cfgBase = path10.basename(config2._configFilePath || "", ".json");
|
|
23798
|
-
const secretCandidates =
|
|
23799
|
-
|
|
23800
|
-
|
|
23801
|
-
path10.
|
|
23802
|
-
|
|
23803
|
-
|
|
23804
|
-
|
|
23805
|
-
if (fs10.existsSync(p)) {
|
|
23806
|
-
secretsPath2 = p;
|
|
23807
|
-
break;
|
|
23808
|
-
}
|
|
23809
|
-
}
|
|
23810
|
-
if (!secretsPath2) {
|
|
23811
|
-
console.log("[secrets-watch] no .env file found, watcher disabled");
|
|
23812
|
-
return null;
|
|
23916
|
+
const secretCandidates = secretsCandidates(config2.stateDir, cfgBase);
|
|
23917
|
+
const watchTargets = /* @__PURE__ */ new Map();
|
|
23918
|
+
for (const c of secretCandidates) {
|
|
23919
|
+
const dir = path10.dirname(c);
|
|
23920
|
+
const names = watchTargets.get(dir);
|
|
23921
|
+
if (names) names.push(path10.basename(c));
|
|
23922
|
+
else watchTargets.set(dir, [path10.basename(c)]);
|
|
23813
23923
|
}
|
|
23814
23924
|
let debounceTimer = null;
|
|
23815
|
-
|
|
23816
|
-
|
|
23817
|
-
if (
|
|
23818
|
-
|
|
23819
|
-
|
|
23820
|
-
|
|
23821
|
-
|
|
23822
|
-
|
|
23823
|
-
} else {
|
|
23824
|
-
console.log("[secrets-watch] file deleted, watcher paused");
|
|
23825
|
-
return;
|
|
23826
|
-
}
|
|
23925
|
+
const onSecretsEvent = (eventType, filename, ownNames) => {
|
|
23926
|
+
if (!shouldHandleSecretsEvent(String(filename || ""), cfgBase, ownNames)) return;
|
|
23927
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
23928
|
+
debounceTimer = setTimeout(async () => {
|
|
23929
|
+
const p = secretCandidates.find((c) => fs10.existsSync(c));
|
|
23930
|
+
if (!p) {
|
|
23931
|
+
console.log("[secrets-watch] \u4E8B\u4EF6\u8FC7\u540E\u6587\u4EF6\u4ECD\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7");
|
|
23932
|
+
return;
|
|
23827
23933
|
}
|
|
23828
|
-
|
|
23829
|
-
|
|
23830
|
-
|
|
23831
|
-
|
|
23832
|
-
|
|
23833
|
-
|
|
23834
|
-
|
|
23835
|
-
|
|
23836
|
-
|
|
23837
|
-
const
|
|
23838
|
-
|
|
23839
|
-
|
|
23840
|
-
|
|
23841
|
-
|
|
23842
|
-
process.env[k] = v;
|
|
23843
|
-
updated++;
|
|
23844
|
-
}
|
|
23934
|
+
console.log(`[secrets-watch] file changed (${eventType}), reloading secrets...`);
|
|
23935
|
+
try {
|
|
23936
|
+
const content = fs10.readFileSync(p, "utf-8");
|
|
23937
|
+
let updated = 0;
|
|
23938
|
+
for (const line of content.split("\n")) {
|
|
23939
|
+
const trimmed = line.trim();
|
|
23940
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
23941
|
+
const eq = trimmed.indexOf("=");
|
|
23942
|
+
if (eq > 0) {
|
|
23943
|
+
const k = trimmed.slice(0, eq).trim();
|
|
23944
|
+
const v = trimmed.slice(eq + 1).trim();
|
|
23945
|
+
if (k) {
|
|
23946
|
+
process.env[k] = v;
|
|
23947
|
+
updated++;
|
|
23845
23948
|
}
|
|
23846
23949
|
}
|
|
23847
|
-
|
|
23848
|
-
|
|
23849
|
-
|
|
23850
|
-
|
|
23851
|
-
|
|
23852
|
-
|
|
23950
|
+
}
|
|
23951
|
+
console.log(`[secrets-watch] ${updated} keys reloaded from ${p}`);
|
|
23952
|
+
console.log(`[secrets-watch] force reload\uFF08\u8DF3\u8FC7\u539F\u6587\u5BF9\u6BD4\uFF09`);
|
|
23953
|
+
const result = await doReloadConfig(config2, deps, provider, true);
|
|
23954
|
+
console.log(`[secrets-watch] config reloaded: ok=${result.ok} changes=${result.changes.join(",")}`);
|
|
23955
|
+
try {
|
|
23956
|
+
fs10.appendFileSync(path10.join(config2.stateDir, "logs", "engine-config-watch.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] SECRETS RELOAD: ok=${result.ok} keys=${updated}
|
|
23853
23957
|
`);
|
|
23854
|
-
|
|
23855
|
-
}
|
|
23856
|
-
} catch (err) {
|
|
23857
|
-
console.error(`[secrets-watch] reload failed: ${err.message}`);
|
|
23958
|
+
} catch {
|
|
23858
23959
|
}
|
|
23859
|
-
}
|
|
23860
|
-
|
|
23861
|
-
|
|
23960
|
+
} catch (err) {
|
|
23961
|
+
console.error(`[secrets-watch] reload failed: ${err.message}`);
|
|
23962
|
+
}
|
|
23963
|
+
}, 500);
|
|
23964
|
+
};
|
|
23965
|
+
const watchers = [];
|
|
23966
|
+
for (const [dir, ownNames] of watchTargets) {
|
|
23967
|
+
try {
|
|
23968
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
23969
|
+
} catch {
|
|
23970
|
+
}
|
|
23971
|
+
try {
|
|
23972
|
+
watchers.push(watchFn(dir, (eventType, filename) => onSecretsEvent(eventType, filename, ownNames)));
|
|
23973
|
+
} catch (err) {
|
|
23974
|
+
console.warn(`[secrets-watch] watch ${dir} failed: ${err.message}\uFF0C\u8BE5\u76EE\u5F55\u9000\u5316\u4E3A"\u6C38\u4E0D\u89E6\u53D1"`);
|
|
23975
|
+
}
|
|
23976
|
+
}
|
|
23977
|
+
if (watchers.length === 0) {
|
|
23978
|
+
console.warn("[secrets-watch] \u6CA1\u6709\u76D1\u542C\u4E0A\u7684 secrets \u76EE\u5F55\uFF0Cwatcher disabled");
|
|
23979
|
+
return null;
|
|
23980
|
+
}
|
|
23981
|
+
for (const w of watchers) {
|
|
23982
|
+
w.on("error", (err) => {
|
|
23862
23983
|
console.error(`[secrets-watch] error: ${err.message}`);
|
|
23863
23984
|
});
|
|
23864
|
-
}
|
|
23865
|
-
|
|
23866
|
-
|
|
23867
|
-
|
|
23985
|
+
}
|
|
23986
|
+
const primary = watchers[0];
|
|
23987
|
+
if (watchers.length > 1) {
|
|
23988
|
+
const closes = watchers.map((w) => w.close.bind(w));
|
|
23989
|
+
primary.close = () => {
|
|
23990
|
+
for (const c of closes) {
|
|
23991
|
+
try {
|
|
23992
|
+
c();
|
|
23993
|
+
} catch {
|
|
23994
|
+
}
|
|
23995
|
+
}
|
|
23996
|
+
};
|
|
23997
|
+
}
|
|
23998
|
+
console.log(`[secrets-watch] watching ${[...watchTargets.keys()].join(", ")} (per-event path resolution, profile=${cfgBase})`);
|
|
23999
|
+
return primary;
|
|
23868
24000
|
}
|
|
23869
24001
|
|
|
23870
24002
|
// src/startup/bootstrap.ts
|
|
@@ -24511,7 +24643,7 @@ ${formatted}` };
|
|
|
24511
24643
|
};
|
|
24512
24644
|
}
|
|
24513
24645
|
function createEverosGetTool() {
|
|
24514
|
-
const
|
|
24646
|
+
const fs58 = __require("node:fs/promises");
|
|
24515
24647
|
return {
|
|
24516
24648
|
name: "memory_get",
|
|
24517
24649
|
description: "Read a memory file by path.",
|
|
@@ -24527,7 +24659,7 @@ function createEverosGetTool() {
|
|
|
24527
24659
|
handler: async (args2) => {
|
|
24528
24660
|
try {
|
|
24529
24661
|
const filePath = args2.path;
|
|
24530
|
-
const content = await
|
|
24662
|
+
const content = await fs58.readFile(filePath, "utf-8");
|
|
24531
24663
|
const lines = content.split("\n");
|
|
24532
24664
|
const fromLine = args2.from ?? 1;
|
|
24533
24665
|
const numLines = args2.lines ?? lines.length;
|
|
@@ -24628,9 +24760,9 @@ async function checkMediaCapabilities() {
|
|
|
24628
24760
|
console.warn(`[opus] @discordjs/opus \u4E0D\u53EF\u7528 (${e.message}) \u2014 \u8BED\u97F3\u6761 opus \u8F6C\u6362\u56DE\u9000\u7CFB\u7EDF ffmpeg`);
|
|
24629
24761
|
}
|
|
24630
24762
|
try {
|
|
24631
|
-
const { execFile:
|
|
24632
|
-
const { promisify:
|
|
24633
|
-
await
|
|
24763
|
+
const { execFile: execFile5 } = await import("node:child_process");
|
|
24764
|
+
const { promisify: promisify5 } = await import("node:util");
|
|
24765
|
+
await promisify5(execFile5)("ffmpeg", ["-version"], { timeout: 5e3 });
|
|
24634
24766
|
console.log(`[ffmpeg] OK \u2014 mp3 \u5F52\u4E00\u5316 / opus&m4a \u56DE\u9000\u53EF\u7528`);
|
|
24635
24767
|
} catch {
|
|
24636
24768
|
;
|
|
@@ -26074,15 +26206,31 @@ ${ep.episode || ep.summary}`,
|
|
|
26074
26206
|
|
|
26075
26207
|
// src/handle-query.ts
|
|
26076
26208
|
init_paths();
|
|
26077
|
-
import { readFileSync as readFileSync27, existsSync as
|
|
26209
|
+
import { readFileSync as readFileSync27, existsSync as existsSync20, statSync as statSync5 } from "node:fs";
|
|
26078
26210
|
import { join as join36, resolve as resolve7 } from "node:path";
|
|
26079
26211
|
import * as path21 from "node:path";
|
|
26080
26212
|
|
|
26081
26213
|
// src/light-mode.ts
|
|
26082
26214
|
init_live();
|
|
26083
|
-
init_ruleCompact();
|
|
26084
26215
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
26216
|
+
|
|
26217
|
+
// src/config/persona-files.ts
|
|
26218
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
26085
26219
|
import { join as join20 } from "node:path";
|
|
26220
|
+
var LEGACY_RELATIVE = {
|
|
26221
|
+
"my-inner-voice.md": "prompts/my-inner-voice.md"
|
|
26222
|
+
};
|
|
26223
|
+
function resolvePersonaFile(workspace, persona, name) {
|
|
26224
|
+
if (persona) {
|
|
26225
|
+
const scoped = join20(workspace, "personas", persona, name);
|
|
26226
|
+
if (existsSync11(scoped)) return scoped;
|
|
26227
|
+
}
|
|
26228
|
+
const legacy = join20(workspace, LEGACY_RELATIVE[name] ?? name);
|
|
26229
|
+
return existsSync11(legacy) ? legacy : null;
|
|
26230
|
+
}
|
|
26231
|
+
|
|
26232
|
+
// src/light-mode.ts
|
|
26233
|
+
init_ruleCompact();
|
|
26086
26234
|
function isLightMode(chatMode, channelName) {
|
|
26087
26235
|
return channelName === "deskBuddy" || chatMode === "emotion";
|
|
26088
26236
|
}
|
|
@@ -26114,9 +26262,12 @@ function buildLightStablePrompt(workspace, mode, opts) {
|
|
|
26114
26262
|
const lp = liveConfig.get(`channels.${mode}.liveSystemPrompt`);
|
|
26115
26263
|
const parts = [];
|
|
26116
26264
|
const files = lp?.files ?? (mode === "emotion" ? ["SOUL.md", "EMOTION.md"] : ["SOUL.md"]);
|
|
26265
|
+
const persona = liveConfig.get("agents.defaults.persona");
|
|
26117
26266
|
for (const f of files) {
|
|
26267
|
+
const filePath = resolvePersonaFile(workspace, persona, f);
|
|
26268
|
+
if (!filePath) continue;
|
|
26118
26269
|
try {
|
|
26119
|
-
parts.push(readFileSync13(
|
|
26270
|
+
parts.push(readFileSync13(filePath, "utf-8").trim());
|
|
26120
26271
|
} catch {
|
|
26121
26272
|
}
|
|
26122
26273
|
}
|
|
@@ -26200,7 +26351,7 @@ function voiceFastView(history, opts = VOICE_FAST_DEFAULTS) {
|
|
|
26200
26351
|
|
|
26201
26352
|
// src/sender-context.ts
|
|
26202
26353
|
init_clock();
|
|
26203
|
-
import { readFileSync as readFileSync14, existsSync as
|
|
26354
|
+
import { readFileSync as readFileSync14, existsSync as existsSync13 } from "node:fs";
|
|
26204
26355
|
import { join as join21 } from "node:path";
|
|
26205
26356
|
var contactMap = null;
|
|
26206
26357
|
var externalChanWhitelist = null;
|
|
@@ -26210,8 +26361,8 @@ function loadContactMap(workspace) {
|
|
|
26210
26361
|
externalChanWhitelist = /* @__PURE__ */ new Set();
|
|
26211
26362
|
try {
|
|
26212
26363
|
const contactsPath = join21(workspace, "prompts", "contacts.md");
|
|
26213
|
-
console.log(`[meta] Loading contacts from ${contactsPath}, exists=${
|
|
26214
|
-
if (
|
|
26364
|
+
console.log(`[meta] Loading contacts from ${contactsPath}, exists=${existsSync13(contactsPath)}`);
|
|
26365
|
+
if (existsSync13(contactsPath)) {
|
|
26215
26366
|
const text = readFileSync14(contactsPath, "utf-8");
|
|
26216
26367
|
const lines = text.split("\n");
|
|
26217
26368
|
for (const line of lines) {
|
|
@@ -26301,18 +26452,18 @@ function resetSessionStartInjection(sessionId) {
|
|
|
26301
26452
|
}
|
|
26302
26453
|
var externalChanRulesCache = null;
|
|
26303
26454
|
function loadExternalChanRules(workspace) {
|
|
26304
|
-
const
|
|
26305
|
-
if (externalChanRulesCache && externalChanRulesCache.path ===
|
|
26455
|
+
const path60 = join36(workspace, "prompts", "external-chan-rules.md");
|
|
26456
|
+
if (externalChanRulesCache && externalChanRulesCache.path === path60) return externalChanRulesCache;
|
|
26306
26457
|
let content = "";
|
|
26307
|
-
if (
|
|
26458
|
+
if (existsSync20(path60)) {
|
|
26308
26459
|
try {
|
|
26309
|
-
content = readFileSync27(
|
|
26460
|
+
content = readFileSync27(path60, "utf-8").trim();
|
|
26310
26461
|
} catch (e) {
|
|
26311
26462
|
console.warn(`[external-chan-rules] Failed to load: ${e}`);
|
|
26312
26463
|
}
|
|
26313
26464
|
}
|
|
26314
|
-
externalChanRulesCache = { path:
|
|
26315
|
-
console.log(`[external-chan-rules] Loaded ${content.length} chars from ${
|
|
26465
|
+
externalChanRulesCache = { path: path60, content };
|
|
26466
|
+
console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path60}`);
|
|
26316
26467
|
return externalChanRulesCache;
|
|
26317
26468
|
}
|
|
26318
26469
|
function getExternalChanRulesBlock(inboundMeta, workspace) {
|
|
@@ -27199,15 +27350,15 @@ function registerCompactHooks(config2) {
|
|
|
27199
27350
|
const dailyDir = path22.join(workspace, "memory", "daily");
|
|
27200
27351
|
const dailyPath = path22.join(dailyDir, `${dateStr}.md`);
|
|
27201
27352
|
try {
|
|
27202
|
-
const
|
|
27203
|
-
if (!
|
|
27204
|
-
|
|
27353
|
+
const fs58 = await import("node:fs");
|
|
27354
|
+
if (!fs58.existsSync(dailyDir)) {
|
|
27355
|
+
fs58.mkdirSync(dailyDir, { recursive: true });
|
|
27205
27356
|
}
|
|
27206
27357
|
const sessionsDir = path22.join(config2.stateDir, "agents", "main", "sessions");
|
|
27207
27358
|
const sessionFile = path22.join(sessionsDir, `${sessionId}.jsonl`);
|
|
27208
27359
|
const recentLines = [];
|
|
27209
|
-
if (
|
|
27210
|
-
const content =
|
|
27360
|
+
if (fs58.existsSync(sessionFile)) {
|
|
27361
|
+
const content = fs58.readFileSync(sessionFile, "utf-8");
|
|
27211
27362
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
27212
27363
|
const userLines = lines.filter((l) => {
|
|
27213
27364
|
try {
|
|
@@ -27237,10 +27388,10 @@ function registerCompactHooks(config2) {
|
|
|
27237
27388
|
const entry = `${header}
|
|
27238
27389
|
${body}
|
|
27239
27390
|
`;
|
|
27240
|
-
if (
|
|
27241
|
-
|
|
27391
|
+
if (fs58.existsSync(dailyPath)) {
|
|
27392
|
+
fs58.appendFileSync(dailyPath, entry);
|
|
27242
27393
|
} else {
|
|
27243
|
-
|
|
27394
|
+
fs58.writeFileSync(dailyPath, `# ${dateStr} \u65E5\u5FD7
|
|
27244
27395
|
${entry}`);
|
|
27245
27396
|
}
|
|
27246
27397
|
console.log(`[hooks] PreCompact: saved ${recentLines.length} lines to ${dailyPath}`);
|
|
@@ -27256,16 +27407,16 @@ ${entry}`);
|
|
|
27256
27407
|
const workspace = input.cwd || input.workspace || "";
|
|
27257
27408
|
if (!workspace) return { continue: true };
|
|
27258
27409
|
try {
|
|
27259
|
-
const
|
|
27410
|
+
const fs58 = await import("node:fs");
|
|
27260
27411
|
const bufferPath = path22.join(workspace, "memory", "working-buffer.md");
|
|
27261
|
-
if (
|
|
27262
|
-
const stat4 =
|
|
27412
|
+
if (fs58.existsSync(bufferPath)) {
|
|
27413
|
+
const stat4 = fs58.statSync(bufferPath);
|
|
27263
27414
|
const ageMs = Date.now() - stat4.mtimeMs;
|
|
27264
27415
|
const ageMin = Math.round(ageMs / 6e4);
|
|
27265
27416
|
if (ageMin > 10) {
|
|
27266
27417
|
console.warn(`[hooks] PostCompact: \u26A0\uFE0F working-buffer.md is ${ageMin}min old (last modified ${stat4.mtime.toISOString()}) \u2014 content may be stale!`);
|
|
27267
27418
|
}
|
|
27268
|
-
const content =
|
|
27419
|
+
const content = fs58.readFileSync(bufferPath, "utf-8");
|
|
27269
27420
|
if (content.trim()) {
|
|
27270
27421
|
console.log(`[hooks] PostCompact: injecting working-buffer (${content.length} chars, ${ageMin}min old)`);
|
|
27271
27422
|
return {
|
|
@@ -27616,9 +27767,9 @@ function createCompactGetters(config2) {
|
|
|
27616
27767
|
const m = s.trim().toLowerCase().match(/^([\d.]+)\s*(mb|kb|gb|b)?$/);
|
|
27617
27768
|
if (!m) return defaultBytes;
|
|
27618
27769
|
const n = parseFloat(m[1]);
|
|
27619
|
-
const
|
|
27770
|
+
const unit2 = m[2] || "b";
|
|
27620
27771
|
const multipliers = { b: 1, kb: 1024, mb: 1024 * 1024, gb: 1024 * 1024 * 1024 };
|
|
27621
|
-
return Math.round(n * (multipliers[
|
|
27772
|
+
return Math.round(n * (multipliers[unit2] || 1));
|
|
27622
27773
|
}
|
|
27623
27774
|
const forceFlushTranscriptBytes = parseSizeString(
|
|
27624
27775
|
config2.compaction?.memoryFlush?.forceFlushTranscriptBytes,
|
|
@@ -29427,6 +29578,7 @@ ${items}
|
|
|
29427
29578
|
};
|
|
29428
29579
|
|
|
29429
29580
|
// src/inner-voice/plugin.ts
|
|
29581
|
+
init_live();
|
|
29430
29582
|
import fs32 from "node:fs";
|
|
29431
29583
|
import path32 from "node:path";
|
|
29432
29584
|
|
|
@@ -29894,10 +30046,10 @@ var US_HALF_LIFE_DAYS = 10;
|
|
|
29894
30046
|
var US_MAX_LINES = 60;
|
|
29895
30047
|
function readRecentMemory(workspace) {
|
|
29896
30048
|
const dir = path31.join(workspace, "memory");
|
|
29897
|
-
const
|
|
30049
|
+
const today3 = dailyFileKeyFor();
|
|
29898
30050
|
const yesterday = dailyFileKeyFor(Date.now() - 864e5);
|
|
29899
30051
|
return {
|
|
29900
|
-
today: readIfExists(path31.join(dir, `${
|
|
30052
|
+
today: readIfExists(path31.join(dir, `${today3}.md`)),
|
|
29901
30053
|
yesterday: readIfExists(path31.join(dir, `${yesterday}.md`))
|
|
29902
30054
|
};
|
|
29903
30055
|
}
|
|
@@ -29982,7 +30134,6 @@ var InnerVoicePlugin = class {
|
|
|
29982
30134
|
this.provider = provider;
|
|
29983
30135
|
this.model = model;
|
|
29984
30136
|
this.workspace = workspace;
|
|
29985
|
-
this.customPrompt = this.loadPrompt(workspace);
|
|
29986
30137
|
}
|
|
29987
30138
|
cfg;
|
|
29988
30139
|
provider;
|
|
@@ -29991,23 +30142,31 @@ var InnerVoicePlugin = class {
|
|
|
29991
30142
|
running = false;
|
|
29992
30143
|
workspace;
|
|
29993
30144
|
dispatcher;
|
|
29994
|
-
/** 从 my-inner-voice.md 加载的定制 prompt */
|
|
29995
|
-
customPrompt;
|
|
29996
30145
|
static shouldEnable(cfg) {
|
|
29997
30146
|
return !!cfg.enabled;
|
|
29998
30147
|
}
|
|
29999
|
-
/**
|
|
30000
|
-
|
|
30001
|
-
|
|
30002
|
-
|
|
30003
|
-
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30148
|
+
/**
|
|
30149
|
+
* 读定制 prompt:workspace/personas/<persona>/my-inner-voice.md 优先,
|
|
30150
|
+
* 回退 workspace/prompts/my-inner-voice.md,两处都没有用 DEFAULT_PROMPT。
|
|
30151
|
+
*
|
|
30152
|
+
* ⚠️ 每次取用**现读**,不在构造期固化:agents.defaults.persona 是热加载字段
|
|
30153
|
+
* (registry effect=hot),固化意味着切换 persona 后内心戏一直念旧人设的 prompt,
|
|
30154
|
+
* 而旧文件那时已被迁移 rename 走。成本是每 tick 一次 readFileSync(默认 30min 一次)。
|
|
30155
|
+
*/
|
|
30156
|
+
loadPrompt() {
|
|
30157
|
+
const persona = liveConfig.get("agents.defaults.persona");
|
|
30158
|
+
const promptPath = resolvePersonaFile(this.workspace, persona, "my-inner-voice.md");
|
|
30159
|
+
if (promptPath) {
|
|
30160
|
+
try {
|
|
30161
|
+
const content = fs32.readFileSync(promptPath, "utf-8").trim();
|
|
30162
|
+
if (content) {
|
|
30163
|
+
console.log(`[inner-voice] Loaded custom prompt from ${promptPath}`);
|
|
30164
|
+
return content;
|
|
30165
|
+
}
|
|
30166
|
+
} catch {
|
|
30007
30167
|
}
|
|
30008
|
-
} catch {
|
|
30009
|
-
console.log(`[inner-voice] No custom prompt at ${promptPath}, using default`);
|
|
30010
30168
|
}
|
|
30169
|
+
console.log(`[inner-voice] No custom prompt (persona=${persona || "(unset)"}), using default`);
|
|
30011
30170
|
return DEFAULT_PROMPT4;
|
|
30012
30171
|
}
|
|
30013
30172
|
start(sessions, deps, dispatcher) {
|
|
@@ -30138,7 +30297,7 @@ var InnerVoicePlugin = class {
|
|
|
30138
30297
|
async generateThought(context) {
|
|
30139
30298
|
const stream = this.provider.streamChat({
|
|
30140
30299
|
model: this.model,
|
|
30141
|
-
systemPrompt: this.
|
|
30300
|
+
systemPrompt: this.loadPrompt(),
|
|
30142
30301
|
messages: [{ role: "user", content: context }],
|
|
30143
30302
|
maxTokens: 512,
|
|
30144
30303
|
temperature: 0.8,
|
|
@@ -31009,12 +31168,6 @@ async function startHttpApi(ctx) {
|
|
|
31009
31168
|
return httpServer;
|
|
31010
31169
|
}
|
|
31011
31170
|
|
|
31012
|
-
// src/optional/voice-chat/plugin.ts
|
|
31013
|
-
import { spawn as spawn4, exec } from "node:child_process";
|
|
31014
|
-
import net from "node:net";
|
|
31015
|
-
import path36 from "node:path";
|
|
31016
|
-
import fs36 from "node:fs";
|
|
31017
|
-
|
|
31018
31171
|
// src/optional/voice-chat/bridge.ts
|
|
31019
31172
|
init_live();
|
|
31020
31173
|
init_well_formed();
|
|
@@ -31257,8 +31410,9 @@ function parseVoiceChatConfig(raw) {
|
|
|
31257
31410
|
if (!raw) return { ...DEFAULTS3 };
|
|
31258
31411
|
return {
|
|
31259
31412
|
enabled: raw.enabled === true,
|
|
31413
|
+
// 0925 起惰性:引擎不启动 python(Python 是外部依赖,见 optional/voice-chat/plugin.ts 顶部说明)。
|
|
31414
|
+
// 仍然解析只为让老 config 里的 spawnPython 键原样通过、不报错——字段已 @deprecated,零读者。
|
|
31260
31415
|
spawnPython: raw.spawnPython !== false,
|
|
31261
|
-
// 默认 true,配 false 只注册 webhook
|
|
31262
31416
|
pythonPort: raw.pythonPort ?? DEFAULTS3.pythonPort,
|
|
31263
31417
|
webhookPath: raw.webhookPath ?? DEFAULTS3.webhookPath,
|
|
31264
31418
|
callbackPath: raw.callbackPath ?? DEFAULTS3.callbackPath,
|
|
@@ -31284,7 +31438,9 @@ function parseVoiceChatConfig(raw) {
|
|
|
31284
31438
|
provider: raw.avatar.provider || "none",
|
|
31285
31439
|
tts: raw.avatar.tts,
|
|
31286
31440
|
image: raw.avatar.image,
|
|
31287
|
-
|
|
31441
|
+
// 0925:默认留空,由使用方按自己的 workspace 解析——引擎不该知道任何人的盘
|
|
31442
|
+
// (此前默认值是 'D:/xiaoke/workspace/selfie',把一台机器的路径写进了引擎源码)
|
|
31443
|
+
selfieDir: raw.avatar.selfieDir || ""
|
|
31288
31444
|
} : void 0,
|
|
31289
31445
|
// local 分组:本地 TTS/avatar(以后本地能跑 avatar 时往这加)
|
|
31290
31446
|
local: raw.local ? {
|
|
@@ -31299,24 +31455,20 @@ function parseVoiceChatConfig(raw) {
|
|
|
31299
31455
|
provider: raw.local.avatar.provider || "none",
|
|
31300
31456
|
tts: raw.local.avatar.tts,
|
|
31301
31457
|
image: raw.local.avatar.image,
|
|
31302
|
-
|
|
31458
|
+
// 0925:同上——不留任何机器专属默认路径
|
|
31459
|
+
selfieDir: raw.local.avatar.selfieDir || ""
|
|
31303
31460
|
} : void 0
|
|
31304
31461
|
} : void 0
|
|
31305
31462
|
};
|
|
31306
31463
|
}
|
|
31307
31464
|
|
|
31308
31465
|
// src/optional/voice-chat/plugin.ts
|
|
31309
|
-
init_BashTool();
|
|
31310
31466
|
init_provider_factory();
|
|
31311
31467
|
init_query();
|
|
31312
|
-
var VoiceChatPlugin = class
|
|
31468
|
+
var VoiceChatPlugin = class {
|
|
31313
31469
|
name = "voice-chat";
|
|
31314
|
-
pythonProcess = null;
|
|
31315
|
-
restartTimer = null;
|
|
31316
31470
|
config;
|
|
31317
31471
|
workspace = "";
|
|
31318
|
-
restartCount = 0;
|
|
31319
|
-
static MAX_RESTARTS = 3;
|
|
31320
31472
|
// 0908:deps 改懒建+失效(对齐 deskBuddy quick deps 模式)——start() 不再固化
|
|
31321
31473
|
// provider/QueryEngine 实例。改 voiceChat.model 热加载后,invalidateVoiceDeps()
|
|
31322
31474
|
// 清缓存,下一条语音消息用新配置重建。此前 start() 一次性建死进 bridge 闭包,
|
|
@@ -31345,13 +31497,6 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
|
|
|
31345
31497
|
() => this.resolveVoiceDeps()
|
|
31346
31498
|
);
|
|
31347
31499
|
console.log(`[voice-chat] Bridge registered at ${this.config.webhookPath ?? "/webhook/voice-chat"}${this.config.model ? ` (model: ${this.config.model}, thinking: ${this.config.thinking ?? false})` : ""}`);
|
|
31348
|
-
if (this.config.spawnPython === false) {
|
|
31349
|
-
console.log("[voice-chat] spawnPython=false, skipping Python subprocess (webhook-only mode)");
|
|
31350
|
-
return;
|
|
31351
|
-
}
|
|
31352
|
-
const port = this.config.pythonPort ?? 8011;
|
|
31353
|
-
await this.killProcessOnPort(port);
|
|
31354
|
-
this.pythonProcess = this.startPython();
|
|
31355
31500
|
}
|
|
31356
31501
|
async stop() {
|
|
31357
31502
|
if (this.bridgeCleanup) {
|
|
@@ -31361,29 +31506,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
|
|
|
31361
31506
|
}
|
|
31362
31507
|
this.bridgeCleanup = void 0;
|
|
31363
31508
|
}
|
|
31364
|
-
if (this.restartTimer) {
|
|
31365
|
-
clearTimeout(this.restartTimer);
|
|
31366
|
-
this.restartTimer = null;
|
|
31367
|
-
}
|
|
31368
|
-
if (this.pythonProcess) {
|
|
31369
|
-
this.pythonProcess.kill("SIGTERM");
|
|
31370
|
-
const proc = this.pythonProcess;
|
|
31371
|
-
const killTimer = setTimeout(() => {
|
|
31372
|
-
if (!proc.killed) {
|
|
31373
|
-
proc.kill("SIGKILL");
|
|
31374
|
-
}
|
|
31375
|
-
}, 5e3);
|
|
31376
|
-
this.pythonProcess = null;
|
|
31377
|
-
}
|
|
31378
31509
|
}
|
|
31379
31510
|
getStatus() {
|
|
31380
31511
|
return {
|
|
31381
|
-
pythonRunning: this.pythonProcess !== null,
|
|
31382
|
-
pythonPid: this.pythonProcess?.pid,
|
|
31383
31512
|
webrtcConnected: false
|
|
31384
31513
|
};
|
|
31385
31514
|
}
|
|
31386
|
-
/** 热加载:刷新 config 中可热变的字段(model/thinking
|
|
31515
|
+
/** 热加载:刷新 config 中可热变的字段(model/thinking 等),不碰外部 Python 服务 */
|
|
31387
31516
|
reloadConfig(newConfig) {
|
|
31388
31517
|
const vcNew = newConfig?.voiceChat;
|
|
31389
31518
|
if (!vcNew) return;
|
|
@@ -31491,159 +31620,13 @@ var VoiceChatPlugin = class _VoiceChatPlugin {
|
|
|
31491
31620
|
modelInputs: modelDef.input || ["text"]
|
|
31492
31621
|
};
|
|
31493
31622
|
}
|
|
31494
|
-
async isPortAlive(port) {
|
|
31495
|
-
return new Promise((resolve12) => {
|
|
31496
|
-
const socket = new net.Socket();
|
|
31497
|
-
socket.setTimeout(2e3);
|
|
31498
|
-
socket.on("connect", () => {
|
|
31499
|
-
socket.destroy();
|
|
31500
|
-
resolve12(true);
|
|
31501
|
-
});
|
|
31502
|
-
socket.on("timeout", () => {
|
|
31503
|
-
socket.destroy();
|
|
31504
|
-
resolve12(false);
|
|
31505
|
-
});
|
|
31506
|
-
socket.on("error", () => {
|
|
31507
|
-
socket.destroy();
|
|
31508
|
-
resolve12(false);
|
|
31509
|
-
});
|
|
31510
|
-
socket.connect(port, "127.0.0.1");
|
|
31511
|
-
});
|
|
31512
|
-
}
|
|
31513
|
-
async killProcessOnPort(port) {
|
|
31514
|
-
const run = (cmd) => new Promise((resolve12) => {
|
|
31515
|
-
exec(cmd, { timeout: 5e3 }, (err, stdout) => resolve12(err ? "" : stdout));
|
|
31516
|
-
});
|
|
31517
|
-
try {
|
|
31518
|
-
if (process.platform === "win32") {
|
|
31519
|
-
const out = await run(`netstat -ano | findstr ":${port}"`);
|
|
31520
|
-
const lines = out.split("\n").filter((l) => l.includes("LISTENING"));
|
|
31521
|
-
const pids = /* @__PURE__ */ new Set();
|
|
31522
|
-
for (const line of lines) {
|
|
31523
|
-
const pid = line.trim().split(/\s+/).pop();
|
|
31524
|
-
if (pid) pids.add(pid);
|
|
31525
|
-
}
|
|
31526
|
-
for (const pid of pids) {
|
|
31527
|
-
await run(`taskkill /PID ${pid} /F`);
|
|
31528
|
-
console.log(`[voice-chat] Killed orphan PID ${pid} on port ${port}`);
|
|
31529
|
-
}
|
|
31530
|
-
} else {
|
|
31531
|
-
const out = await run(`lsof -ti :${port}`);
|
|
31532
|
-
if (out.trim()) {
|
|
31533
|
-
await run(`kill -9 ${out.trim()}`);
|
|
31534
|
-
console.log(`[voice-chat] Killed orphan PID ${out.trim()} on port ${port}`);
|
|
31535
|
-
}
|
|
31536
|
-
}
|
|
31537
|
-
if (process.platform === "win32") {
|
|
31538
|
-
const stillAlive = await run(`netstat -ano | findstr ":${port}"`);
|
|
31539
|
-
if (stillAlive.includes("LISTENING")) {
|
|
31540
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
31541
|
-
}
|
|
31542
|
-
}
|
|
31543
|
-
} catch {
|
|
31544
|
-
}
|
|
31545
|
-
}
|
|
31546
|
-
findPython() {
|
|
31547
|
-
if (this.config.pythonPath && fs36.existsSync(this.config.pythonPath)) {
|
|
31548
|
-
return this.config.pythonPath;
|
|
31549
|
-
}
|
|
31550
|
-
return "python";
|
|
31551
|
-
}
|
|
31552
|
-
getPythonDir() {
|
|
31553
|
-
const dir = import.meta.dirname;
|
|
31554
|
-
const srcDir = path36.resolve(dir, "..", "src", "voice-chat", "python");
|
|
31555
|
-
const localDir = path36.join(dir, "python");
|
|
31556
|
-
return fs36.existsSync(srcDir) ? srcDir : localDir;
|
|
31557
|
-
}
|
|
31558
|
-
startPython() {
|
|
31559
|
-
const pythonDir = this.getPythonDir();
|
|
31560
|
-
const serverPy = path36.join(pythonDir, "server.py");
|
|
31561
|
-
const pythonBin = this.findPython();
|
|
31562
|
-
const args2 = [serverPy];
|
|
31563
|
-
if (this.config.pythonPort) args2.push("--port", String(this.config.pythonPort));
|
|
31564
|
-
if (this.config.vadModelPath) args2.push("--vad-model", this.config.vadModelPath);
|
|
31565
|
-
if (this.config.asrModelPath) args2.push("--asr-model", this.config.asrModelPath);
|
|
31566
|
-
if (this.config.asrLanguage) args2.push("--asr-lang", this.config.asrLanguage);
|
|
31567
|
-
if (this.config.vadThreshold) args2.push("--vad-threshold", String(this.config.vadThreshold));
|
|
31568
|
-
if (this.config.postEndMonitor) args2.push("--post-end-monitor", String(this.config.postEndMonitor));
|
|
31569
|
-
if (this.workspace) args2.push("--workspace", this.workspace);
|
|
31570
|
-
const ttsCfg = this.config.local?.tts || this.config.tts;
|
|
31571
|
-
if (ttsCfg) {
|
|
31572
|
-
args2.push("--tts-provider", ttsCfg.provider);
|
|
31573
|
-
if (ttsCfg.apiKey) args2.push("--tts-api-key", ttsCfg.apiKey);
|
|
31574
|
-
if (ttsCfg.model) args2.push("--tts-model", ttsCfg.model);
|
|
31575
|
-
if (ttsCfg.voice !== void 0) args2.push("--tts-voice", ttsCfg.voice);
|
|
31576
|
-
if (ttsCfg.workspaceId) args2.push("--tts-workspace-id", ttsCfg.workspaceId);
|
|
31577
|
-
}
|
|
31578
|
-
const avatarCfg = this.config.local?.avatar || this.config.avatar;
|
|
31579
|
-
if (avatarCfg) {
|
|
31580
|
-
args2.push("--avatar-provider", avatarCfg.provider);
|
|
31581
|
-
if (avatarCfg.tts) args2.push("--avatar-tts", avatarCfg.tts);
|
|
31582
|
-
if (avatarCfg.image) args2.push("--avatar-image", avatarCfg.image);
|
|
31583
|
-
if (avatarCfg.selfieDir) args2.push("--selfie-dir", avatarCfg.selfieDir);
|
|
31584
|
-
}
|
|
31585
|
-
if (this.config.playbackSpeed) {
|
|
31586
|
-
args2.push("--playback-speed", String(this.config.playbackSpeed));
|
|
31587
|
-
}
|
|
31588
|
-
console.log(`[voice-chat] Starting Python: ${pythonBin} ${args2.join(" ")}`);
|
|
31589
|
-
console.log(`[voice-chat] Python dir: ${pythonDir}`);
|
|
31590
|
-
if (!fs36.existsSync(pythonDir)) {
|
|
31591
|
-
console.error(`[voice-chat] FATAL: Python directory does not exist: ${pythonDir}`);
|
|
31592
|
-
throw new Error(`voice-chat: python dir not found: ${pythonDir}`);
|
|
31593
|
-
}
|
|
31594
|
-
let child;
|
|
31595
|
-
if (process.platform === "win32") {
|
|
31596
|
-
const { shell, args: shellArgs } = findShell();
|
|
31597
|
-
const cmd = `${pythonBin} ${args2.map((a) => `"${a}"`).join(" ")}`;
|
|
31598
|
-
child = spawn4(shell, [...shellArgs, cmd], {
|
|
31599
|
-
cwd: pythonDir,
|
|
31600
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
31601
|
-
env: { ...process.env, PYTHONUNBUFFERED: "1" }
|
|
31602
|
-
});
|
|
31603
|
-
} else {
|
|
31604
|
-
child = spawn4(pythonBin, args2, {
|
|
31605
|
-
cwd: pythonDir,
|
|
31606
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
31607
|
-
env: { ...process.env, PYTHONUNBUFFERED: "1" }
|
|
31608
|
-
});
|
|
31609
|
-
}
|
|
31610
|
-
child.on("error", (err) => {
|
|
31611
|
-
console.error(`[voice-chat] spawn error: ${err.message}`);
|
|
31612
|
-
console.error(`[voice-chat] shell=${pythonBin} cwd=${pythonDir}`);
|
|
31613
|
-
console.error(`[voice-chat] cwd exists=${fs36.existsSync(pythonDir)}`);
|
|
31614
|
-
});
|
|
31615
|
-
child.stdout?.on("data", (data) => {
|
|
31616
|
-
const lines = data.toString().trim().split("\n");
|
|
31617
|
-
for (const line of lines) console.log(`[voice-chat:py] ${line}`);
|
|
31618
|
-
});
|
|
31619
|
-
child.stderr?.on("data", (data) => {
|
|
31620
|
-
const lines = data.toString().trim().split("\n");
|
|
31621
|
-
for (const line of lines) console.error(`[voice-chat:py] ${line}`);
|
|
31622
|
-
});
|
|
31623
|
-
child.on("exit", (code, signal) => {
|
|
31624
|
-
console.log(`[voice-chat] Python exited (code=${code}, signal=${signal})`);
|
|
31625
|
-
this.pythonProcess = null;
|
|
31626
|
-
if (code !== 0 && code !== null) {
|
|
31627
|
-
if (this.restartCount >= _VoiceChatPlugin.MAX_RESTARTS) {
|
|
31628
|
-
console.error(`[voice-chat] Max restarts (${_VoiceChatPlugin.MAX_RESTARTS}) reached, giving up`);
|
|
31629
|
-
return;
|
|
31630
|
-
}
|
|
31631
|
-
this.restartCount++;
|
|
31632
|
-
console.log(`[voice-chat] Python crashed, restarting in 5s... (attempt ${this.restartCount}/${_VoiceChatPlugin.MAX_RESTARTS})`);
|
|
31633
|
-
this.restartTimer = setTimeout(() => {
|
|
31634
|
-
this.pythonProcess = this.startPython();
|
|
31635
|
-
}, 5e3);
|
|
31636
|
-
}
|
|
31637
|
-
});
|
|
31638
|
-
return child;
|
|
31639
|
-
}
|
|
31640
31623
|
};
|
|
31641
31624
|
|
|
31642
31625
|
// src/memory/everos/plugin.ts
|
|
31643
|
-
import { spawn as
|
|
31644
|
-
import
|
|
31645
|
-
import
|
|
31646
|
-
import
|
|
31626
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
31627
|
+
import net from "node:net";
|
|
31628
|
+
import path36 from "node:path";
|
|
31629
|
+
import fs36 from "node:fs";
|
|
31647
31630
|
|
|
31648
31631
|
// src/memory/everos/config.ts
|
|
31649
31632
|
var DEFAULTS4 = {
|
|
@@ -31877,8 +31860,8 @@ var EverosPlugin = class {
|
|
|
31877
31860
|
}, 3e5);
|
|
31878
31861
|
}
|
|
31879
31862
|
async startEveros() {
|
|
31880
|
-
const pythonDir =
|
|
31881
|
-
const configPath2 =
|
|
31863
|
+
const pythonDir = path36.dirname(this.config.lancedbPath);
|
|
31864
|
+
const configPath2 = path36.join(pythonDir, "config.toml");
|
|
31882
31865
|
await this.ensureFcntlCompat();
|
|
31883
31866
|
const venvPython = this.findVenvPython();
|
|
31884
31867
|
const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
|
|
@@ -31887,13 +31870,13 @@ var EverosPlugin = class {
|
|
|
31887
31870
|
console.log(`[everos] Starting EverOS: ${cmd}`);
|
|
31888
31871
|
console.log(`[everos] LLM config: ${this.config.llm.model} @ ${this.config.llm.baseUrl}`);
|
|
31889
31872
|
if (process.platform === "win32") {
|
|
31890
|
-
|
|
31873
|
+
spawn4(everosBin, args2, {
|
|
31891
31874
|
cwd: pythonDir,
|
|
31892
31875
|
stdio: "ignore",
|
|
31893
31876
|
env: { ...process.env, PYTHONUNBUFFERED: "1", NO_PROXY: "127.0.0.1,localhost", no_proxy: "127.0.0.1,localhost" }
|
|
31894
31877
|
});
|
|
31895
31878
|
} else {
|
|
31896
|
-
|
|
31879
|
+
spawn4(venvPython, args2, {
|
|
31897
31880
|
cwd: pythonDir,
|
|
31898
31881
|
stdio: "ignore",
|
|
31899
31882
|
env: { ...process.env, PYTHONUNBUFFERED: "1", NO_PROXY: "127.0.0.1,localhost", no_proxy: "127.0.0.1,localhost" }
|
|
@@ -31938,13 +31921,13 @@ var EverosPlugin = class {
|
|
|
31938
31921
|
console.log(`[everos] EVEROS_USER_ID=${childEnv.EVEROS_USER_ID}`);
|
|
31939
31922
|
let child;
|
|
31940
31923
|
if (process.platform === "win32") {
|
|
31941
|
-
child =
|
|
31924
|
+
child = spawn4(venvPython, args2, {
|
|
31942
31925
|
cwd: pythonDir,
|
|
31943
31926
|
stdio: ["ignore", "pipe", "pipe"],
|
|
31944
31927
|
env: childEnv
|
|
31945
31928
|
});
|
|
31946
31929
|
} else {
|
|
31947
|
-
child =
|
|
31930
|
+
child = spawn4(venvPython, args2, {
|
|
31948
31931
|
cwd: pythonDir,
|
|
31949
31932
|
stdio: ["ignore", "pipe", "pipe"],
|
|
31950
31933
|
env: childEnv
|
|
@@ -31968,19 +31951,19 @@ var EverosPlugin = class {
|
|
|
31968
31951
|
return child;
|
|
31969
31952
|
}
|
|
31970
31953
|
findVenvPython() {
|
|
31971
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31954
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
31972
31955
|
if (process.platform === "win32") {
|
|
31973
|
-
return
|
|
31956
|
+
return path36.join(stateDir, "everos-venv", "Scripts", "python.exe");
|
|
31974
31957
|
}
|
|
31975
|
-
return
|
|
31958
|
+
return path36.join(stateDir, "everos-venv", "bin", "python");
|
|
31976
31959
|
}
|
|
31977
31960
|
/** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
|
|
31978
31961
|
async ensureVenv() {
|
|
31979
31962
|
const venvPython = this.findVenvPython();
|
|
31980
|
-
if (
|
|
31981
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31982
|
-
const venvDir =
|
|
31983
|
-
const everosSrc =
|
|
31963
|
+
if (fs36.existsSync(venvPython)) return;
|
|
31964
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path36.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
31965
|
+
const venvDir = path36.join(stateDir, "everos-venv");
|
|
31966
|
+
const everosSrc = path36.join(stateDir, "workspace", "research", "EverOS");
|
|
31984
31967
|
console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
|
|
31985
31968
|
console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
|
|
31986
31969
|
const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
|
|
@@ -32002,9 +31985,9 @@ var EverosPlugin = class {
|
|
|
32002
31985
|
console.log(`[everos] Creating venv with ${sysPython}...`);
|
|
32003
31986
|
const { execSync: execSync3 } = await import("node:child_process");
|
|
32004
31987
|
execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
|
|
32005
|
-
const pip = process.platform === "win32" ?
|
|
32006
|
-
const everosReq =
|
|
32007
|
-
if (
|
|
31988
|
+
const pip = process.platform === "win32" ? path36.join(venvDir, "Scripts", "pip.exe") : path36.join(venvDir, "bin", "pip");
|
|
31989
|
+
const everosReq = path36.join(this.getPythonDir(), "requirements.txt");
|
|
31990
|
+
if (fs36.existsSync(everosReq)) {
|
|
32008
31991
|
console.log(`[everos] Installing from requirements.txt...`);
|
|
32009
31992
|
execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
|
|
32010
31993
|
} else {
|
|
@@ -32020,12 +32003,12 @@ var EverosPlugin = class {
|
|
|
32020
32003
|
getPythonDir() {
|
|
32021
32004
|
const dir = import.meta.dirname;
|
|
32022
32005
|
const candidates = [
|
|
32023
|
-
|
|
32024
|
-
|
|
32025
|
-
|
|
32006
|
+
path36.join(dir, "python"),
|
|
32007
|
+
path36.resolve(dir, "..", "src", "memory", "everos", "python"),
|
|
32008
|
+
path36.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
|
|
32026
32009
|
];
|
|
32027
32010
|
for (const candidate of candidates) {
|
|
32028
|
-
if (
|
|
32011
|
+
if (fs36.existsSync(path36.join(candidate, "agentic_server.py"))) {
|
|
32029
32012
|
return candidate;
|
|
32030
32013
|
}
|
|
32031
32014
|
}
|
|
@@ -32034,14 +32017,14 @@ var EverosPlugin = class {
|
|
|
32034
32017
|
async ensureFcntlCompat() {
|
|
32035
32018
|
if (process.platform !== "win32") return;
|
|
32036
32019
|
const venvPython = this.findVenvPython();
|
|
32037
|
-
const venvDir =
|
|
32038
|
-
const sitePackages =
|
|
32039
|
-
const target =
|
|
32040
|
-
if (
|
|
32041
|
-
const source =
|
|
32042
|
-
if (
|
|
32020
|
+
const venvDir = path36.dirname(path36.dirname(venvPython));
|
|
32021
|
+
const sitePackages = path36.join(venvDir, "Lib", "site-packages");
|
|
32022
|
+
const target = path36.join(sitePackages, "fcntl.py");
|
|
32023
|
+
if (fs36.existsSync(target)) return;
|
|
32024
|
+
const source = path36.join(this.getPythonDir(), "fcntl_compat.py");
|
|
32025
|
+
if (fs36.existsSync(source)) {
|
|
32043
32026
|
try {
|
|
32044
|
-
|
|
32027
|
+
fs36.copyFileSync(source, target);
|
|
32045
32028
|
console.log(`[everos] Installed fcntl compat shim to ${target}`);
|
|
32046
32029
|
} catch (err) {
|
|
32047
32030
|
console.warn(`[everos] Failed to install fcntl shim: ${err.message}`);
|
|
@@ -32065,7 +32048,7 @@ var EverosPlugin = class {
|
|
|
32065
32048
|
}
|
|
32066
32049
|
async isPortAlive(port) {
|
|
32067
32050
|
return new Promise((resolve12) => {
|
|
32068
|
-
const socket = new
|
|
32051
|
+
const socket = new net.Socket();
|
|
32069
32052
|
socket.setTimeout(2e3);
|
|
32070
32053
|
socket.on("connect", () => {
|
|
32071
32054
|
socket.destroy();
|
|
@@ -32650,8 +32633,8 @@ function createVisualBoot(ctx) {
|
|
|
32650
32633
|
}
|
|
32651
32634
|
|
|
32652
32635
|
// src/startup/message-pipeline.ts
|
|
32653
|
-
import * as
|
|
32654
|
-
import * as
|
|
32636
|
+
import * as fs37 from "node:fs";
|
|
32637
|
+
import * as path37 from "node:path";
|
|
32655
32638
|
|
|
32656
32639
|
// src/commands/registry.ts
|
|
32657
32640
|
var commands = /* @__PURE__ */ new Map();
|
|
@@ -33542,7 +33525,7 @@ function setupMessagePipeline(ctx) {
|
|
|
33542
33525
|
console.log(`[vision] Downloading image: ${att.filename}`);
|
|
33543
33526
|
let rawBuffer;
|
|
33544
33527
|
if (att.url.startsWith("file://")) {
|
|
33545
|
-
rawBuffer =
|
|
33528
|
+
rawBuffer = fs37.readFileSync(decodeURIComponent(att.url.slice(7)));
|
|
33546
33529
|
} else {
|
|
33547
33530
|
rawBuffer = await downloadImage2(att.url, attachDispatcher);
|
|
33548
33531
|
}
|
|
@@ -33550,8 +33533,8 @@ function setupMessagePipeline(ctx) {
|
|
|
33550
33533
|
const ext = detected.split("/")[1] || "png";
|
|
33551
33534
|
const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
|
|
33552
33535
|
const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
33553
|
-
const savedPath =
|
|
33554
|
-
|
|
33536
|
+
const savedPath = path37.join(config2.mediaDir, `${imageId}.${ext}`);
|
|
33537
|
+
fs37.writeFileSync(savedPath, resized.buffer);
|
|
33555
33538
|
savedPaths.push(savedPath);
|
|
33556
33539
|
console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
|
|
33557
33540
|
imageBlocks.push({
|
|
@@ -33581,8 +33564,8 @@ ${pathStr}` }];
|
|
|
33581
33564
|
}
|
|
33582
33565
|
const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
|
|
33583
33566
|
if (nonImageAttachments && nonImageAttachments.length > 0) {
|
|
33584
|
-
const outDir =
|
|
33585
|
-
|
|
33567
|
+
const outDir = path37.join(config2.mediaDir, sessionId);
|
|
33568
|
+
fs37.mkdirSync(outDir, { recursive: true });
|
|
33586
33569
|
const resolved = [];
|
|
33587
33570
|
for (const att of nonImageAttachments) {
|
|
33588
33571
|
console.log(`[file] Downloading: ${att.filename} (${att.contentType}, ${att.size}B)`);
|
|
@@ -33590,9 +33573,9 @@ ${pathStr}` }];
|
|
|
33590
33573
|
const resp = attachDispatcher ? await fetch(att.url, { dispatcher: attachDispatcher, signal: AbortSignal.timeout(3e4) }) : await fetch(att.url, { signal: AbortSignal.timeout(3e4) });
|
|
33591
33574
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
33592
33575
|
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
33593
|
-
const safeName2 =
|
|
33594
|
-
const savedPath =
|
|
33595
|
-
|
|
33576
|
+
const safeName2 = path37.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
|
|
33577
|
+
const savedPath = path37.join(outDir, safeName2);
|
|
33578
|
+
fs37.writeFileSync(savedPath, buffer);
|
|
33596
33579
|
resolved.push(savedPath);
|
|
33597
33580
|
console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
|
|
33598
33581
|
} catch (err) {
|
|
@@ -33841,7 +33824,7 @@ ${pathStr}` }];
|
|
|
33841
33824
|
init_live();
|
|
33842
33825
|
|
|
33843
33826
|
// src/provision-free.ts
|
|
33844
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
33827
|
+
import { chmodSync as chmodSync2, existsSync as existsSync24, mkdirSync as mkdirSync16, readFileSync as readFileSync35, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33845
33828
|
import { dirname as dirname11, join as join46 } from "node:path";
|
|
33846
33829
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
33847
33830
|
var __filename = fileURLToPath7(import.meta.url);
|
|
@@ -33865,13 +33848,13 @@ function readInstallIdentity(stateDir) {
|
|
|
33865
33848
|
}
|
|
33866
33849
|
|
|
33867
33850
|
// src/optional/reporting/state.ts
|
|
33868
|
-
import { existsSync as
|
|
33851
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync17, readFileSync as readFileSync36, writeFileSync as writeFileSync14 } from "node:fs";
|
|
33869
33852
|
import { join as join47 } from "node:path";
|
|
33870
33853
|
var STATE_FILE2 = "report-state.json";
|
|
33871
33854
|
function readReportState(stateDir) {
|
|
33872
33855
|
try {
|
|
33873
33856
|
const p = join47(stateDir, STATE_FILE2);
|
|
33874
|
-
if (!
|
|
33857
|
+
if (!existsSync25(p)) return {};
|
|
33875
33858
|
const parsed = JSON.parse(readFileSync36(p, "utf8"));
|
|
33876
33859
|
if (!parsed || typeof parsed !== "object") return {};
|
|
33877
33860
|
return parsed;
|
|
@@ -33881,7 +33864,7 @@ function readReportState(stateDir) {
|
|
|
33881
33864
|
}
|
|
33882
33865
|
function writeReportState(stateDir, s) {
|
|
33883
33866
|
try {
|
|
33884
|
-
|
|
33867
|
+
mkdirSync17(stateDir, { recursive: true });
|
|
33885
33868
|
writeFileSync14(join47(stateDir, STATE_FILE2), JSON.stringify(s, null, 2) + "\n");
|
|
33886
33869
|
} catch {
|
|
33887
33870
|
}
|
|
@@ -33911,6 +33894,9 @@ function buildReportPayload(opts) {
|
|
|
33911
33894
|
if (opts.counters.lastMsgAt > 0 || opts.counters.msgsTotal === 0) {
|
|
33912
33895
|
p.lastMsgAt = opts.counters.lastMsgAt;
|
|
33913
33896
|
}
|
|
33897
|
+
if (opts.tts) {
|
|
33898
|
+
p.tts = { free: opts.tts.free, billing: opts.tts.billing, failed: opts.tts.failed };
|
|
33899
|
+
}
|
|
33914
33900
|
if (opts.identity.contactEmail) p.contactEmail = opts.identity.contactEmail;
|
|
33915
33901
|
return p;
|
|
33916
33902
|
}
|
|
@@ -33949,6 +33935,71 @@ function restoreCounters(s) {
|
|
|
33949
33935
|
lastMsgAt = typeof s.lastMsgAt === "number" && Number.isFinite(s.lastMsgAt) && s.lastMsgAt > 0 ? s.lastMsgAt : 0;
|
|
33950
33936
|
}
|
|
33951
33937
|
|
|
33938
|
+
// src/tools/tts/usage.ts
|
|
33939
|
+
init_clock();
|
|
33940
|
+
var nowFn2 = () => /* @__PURE__ */ new Date();
|
|
33941
|
+
var today2 = "";
|
|
33942
|
+
var buckets = {
|
|
33943
|
+
free: { chars: 0, seconds: 0, calls: 0 },
|
|
33944
|
+
billing: { chars: 0, seconds: 0, calls: 0 }
|
|
33945
|
+
};
|
|
33946
|
+
var failedCount = 0;
|
|
33947
|
+
function rolloverIfNeeded2(now) {
|
|
33948
|
+
const d = todayKey(now);
|
|
33949
|
+
if (d !== today2) {
|
|
33950
|
+
today2 = d;
|
|
33951
|
+
for (const k of ["free", "billing"]) {
|
|
33952
|
+
buckets[k] = { chars: 0, seconds: 0, calls: 0 };
|
|
33953
|
+
}
|
|
33954
|
+
failedCount = 0;
|
|
33955
|
+
}
|
|
33956
|
+
}
|
|
33957
|
+
function noteTtsUsage(u) {
|
|
33958
|
+
captureFn?.(u);
|
|
33959
|
+
const now = nowFn2();
|
|
33960
|
+
rolloverIfNeeded2(now);
|
|
33961
|
+
if (!u.ok) {
|
|
33962
|
+
failedCount++;
|
|
33963
|
+
return;
|
|
33964
|
+
}
|
|
33965
|
+
const b = buckets[u.pool];
|
|
33966
|
+
if (!b) return;
|
|
33967
|
+
const amount = Number.isFinite(u.unit?.amount) && u.unit.amount > 0 ? u.unit.amount : 0;
|
|
33968
|
+
if (u.unit?.kind === "seconds") b.seconds += amount;
|
|
33969
|
+
else b.chars += amount;
|
|
33970
|
+
b.calls++;
|
|
33971
|
+
}
|
|
33972
|
+
function safeCount(v) {
|
|
33973
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? v : 0;
|
|
33974
|
+
}
|
|
33975
|
+
function readBucket(v) {
|
|
33976
|
+
const o = v && typeof v === "object" ? v : {};
|
|
33977
|
+
return { chars: safeCount(o.chars), seconds: safeCount(o.seconds), calls: safeCount(o.calls) };
|
|
33978
|
+
}
|
|
33979
|
+
function restoreTtsCounters(s) {
|
|
33980
|
+
today2 = todayKey(nowFn2());
|
|
33981
|
+
const o = s && typeof s === "object" ? s : {};
|
|
33982
|
+
if (o.day === today2) {
|
|
33983
|
+
buckets.free = readBucket(o.free);
|
|
33984
|
+
buckets.billing = readBucket(o.billing);
|
|
33985
|
+
failedCount = safeCount(o.failed);
|
|
33986
|
+
} else {
|
|
33987
|
+
buckets.free = { chars: 0, seconds: 0, calls: 0 };
|
|
33988
|
+
buckets.billing = { chars: 0, seconds: 0, calls: 0 };
|
|
33989
|
+
failedCount = 0;
|
|
33990
|
+
}
|
|
33991
|
+
}
|
|
33992
|
+
function snapshotTtsCounters() {
|
|
33993
|
+
rolloverIfNeeded2(nowFn2());
|
|
33994
|
+
return {
|
|
33995
|
+
day: today2,
|
|
33996
|
+
free: { ...buckets.free },
|
|
33997
|
+
billing: { ...buckets.billing },
|
|
33998
|
+
failed: failedCount
|
|
33999
|
+
};
|
|
34000
|
+
}
|
|
34001
|
+
var captureFn = null;
|
|
34002
|
+
|
|
33952
34003
|
// src/optional/reporting/reporter.ts
|
|
33953
34004
|
init_api_status();
|
|
33954
34005
|
var DEFAULT_REPORT_URL = "https://www.twinsun.top:8866/api/engine7/report";
|
|
@@ -33963,12 +34014,12 @@ function acceptReportUrl(v) {
|
|
|
33963
34014
|
function startReporting(opts) {
|
|
33964
34015
|
const { stateDir } = opts;
|
|
33965
34016
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
33966
|
-
const
|
|
34017
|
+
const nowFn3 = opts.now ?? (() => Date.now());
|
|
33967
34018
|
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
33968
34019
|
const initialDelayMs = opts.initialDelayMs ?? DEFAULT_INITIAL_DELAY_MS;
|
|
33969
34020
|
const log = opts.log ?? (() => {
|
|
33970
34021
|
});
|
|
33971
|
-
const startedAt =
|
|
34022
|
+
const startedAt = nowFn3();
|
|
33972
34023
|
const maybeIdentity = readInstallIdentity(stateDir);
|
|
33973
34024
|
if (!maybeIdentity) {
|
|
33974
34025
|
log("[reporting] \u6CA1\u6709\u53EF\u7528\u7684\u88C5\u673A\u8EAB\u4EFD\uFF0C\u4E0A\u62A5\u5173\u95ED");
|
|
@@ -33977,7 +34028,9 @@ function startReporting(opts) {
|
|
|
33977
34028
|
} };
|
|
33978
34029
|
}
|
|
33979
34030
|
const identity = maybeIdentity;
|
|
33980
|
-
|
|
34031
|
+
const bootState = readReportState(stateDir);
|
|
34032
|
+
restoreCounters(bootState);
|
|
34033
|
+
restoreTtsCounters(bootState.tts);
|
|
33981
34034
|
const engineVersion = readEngineVersion();
|
|
33982
34035
|
let timer = null;
|
|
33983
34036
|
let stopped = false;
|
|
@@ -33986,7 +34039,7 @@ function startReporting(opts) {
|
|
|
33986
34039
|
const enabled = liveConfig.get("reporting.enabled") !== false;
|
|
33987
34040
|
const counters = snapshotCounters();
|
|
33988
34041
|
if (!enabled) {
|
|
33989
|
-
writeReportState(stateDir, { ...counters, reportUrl: state2.reportUrl });
|
|
34042
|
+
writeReportState(stateDir, { ...counters, tts: snapshotTtsCounters(), reportUrl: state2.reportUrl });
|
|
33990
34043
|
return;
|
|
33991
34044
|
}
|
|
33992
34045
|
const fromServer = acceptReportUrl(state2.reportUrl);
|
|
@@ -33997,8 +34050,10 @@ function startReporting(opts) {
|
|
|
33997
34050
|
stateDir,
|
|
33998
34051
|
engineVersion,
|
|
33999
34052
|
startedAt,
|
|
34000
|
-
now:
|
|
34001
|
-
counters
|
|
34053
|
+
now: nowFn3(),
|
|
34054
|
+
counters,
|
|
34055
|
+
// 现取不缓存:snapshotTtsCounters 内部要做跨天检查(进程常驻,可能好几天没人发语音)
|
|
34056
|
+
tts: snapshotTtsCounters()
|
|
34002
34057
|
});
|
|
34003
34058
|
let nextUrl = state2.reportUrl;
|
|
34004
34059
|
try {
|
|
@@ -34013,7 +34068,7 @@ function startReporting(opts) {
|
|
|
34013
34068
|
if (adopted) nextUrl = adopted;
|
|
34014
34069
|
} catch {
|
|
34015
34070
|
}
|
|
34016
|
-
writeReportState(stateDir, { ...counters, reportUrl: nextUrl });
|
|
34071
|
+
writeReportState(stateDir, { ...counters, tts: snapshotTtsCounters(), reportUrl: nextUrl });
|
|
34017
34072
|
}
|
|
34018
34073
|
function schedule(delay) {
|
|
34019
34074
|
if (stopped) return;
|
|
@@ -34196,13 +34251,13 @@ var DiscordAdapter = class _DiscordAdapter {
|
|
|
34196
34251
|
}
|
|
34197
34252
|
/** 发送媒体附件(图片/文件/音频)— discord.js channel.send({ files }) */
|
|
34198
34253
|
async sendFile(target, message, attachment) {
|
|
34199
|
-
const
|
|
34200
|
-
const
|
|
34201
|
-
if (!
|
|
34254
|
+
const fs58 = await import("node:fs");
|
|
34255
|
+
const path60 = await import("node:path");
|
|
34256
|
+
if (!fs58.existsSync(attachment.path)) {
|
|
34202
34257
|
throw new Error(`File not found: ${attachment.path}`);
|
|
34203
34258
|
}
|
|
34204
|
-
const filename = attachment.filename ||
|
|
34205
|
-
const fileBuffer =
|
|
34259
|
+
const filename = attachment.filename || path60.basename(attachment.path);
|
|
34260
|
+
const fileBuffer = fs58.readFileSync(attachment.path);
|
|
34206
34261
|
const filePayload = {
|
|
34207
34262
|
attachment: fileBuffer,
|
|
34208
34263
|
name: filename
|
|
@@ -34651,13 +34706,13 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
34651
34706
|
}
|
|
34652
34707
|
/** 发送媒体附件(图片/文件) */
|
|
34653
34708
|
async sendFile(target, message, attachment) {
|
|
34654
|
-
const
|
|
34655
|
-
const
|
|
34656
|
-
if (!
|
|
34709
|
+
const fs58 = await import("node:fs");
|
|
34710
|
+
const path60 = await import("node:path");
|
|
34711
|
+
if (!fs58.existsSync(attachment.path)) {
|
|
34657
34712
|
throw new Error(`File not found: ${attachment.path}`);
|
|
34658
34713
|
}
|
|
34659
|
-
const filename = attachment.filename ||
|
|
34660
|
-
const fileBuffer =
|
|
34714
|
+
const filename = attachment.filename || path60.basename(attachment.path);
|
|
34715
|
+
const fileBuffer = fs58.readFileSync(attachment.path);
|
|
34661
34716
|
const safeTarget = target || this.config.defaultTarget || "";
|
|
34662
34717
|
if (!safeTarget) throw new Error("Feishu sendFile: no target (\u4E3B\u52A8\u53D1\u9001\u9700\u914D\u7F6E defaultTarget \u6216\u4F20\u5165 target)");
|
|
34663
34718
|
const receiveIdType = safeTarget.startsWith("ou_") ? "open_id" : "chat_id";
|
|
@@ -34688,10 +34743,10 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
34688
34743
|
} else {
|
|
34689
34744
|
let durationMs = Math.round(fileBuffer.length / 3);
|
|
34690
34745
|
try {
|
|
34691
|
-
const { execFile:
|
|
34692
|
-
const { promisify:
|
|
34693
|
-
const
|
|
34694
|
-
const probe = await
|
|
34746
|
+
const { execFile: execFile5 } = await import("node:child_process");
|
|
34747
|
+
const { promisify: promisify5 } = await import("node:util");
|
|
34748
|
+
const execFileAsync4 = promisify5(execFile5);
|
|
34749
|
+
const probe = await execFileAsync4("ffprobe", ["-v", "quiet", "-show_format", "-print_format", "json", attachment.path], { timeout: 1e4 });
|
|
34695
34750
|
const fmt = JSON.parse(probe.stdout);
|
|
34696
34751
|
if (fmt?.format?.duration) durationMs = Math.round(parseFloat(fmt.format.duration) * 1e3);
|
|
34697
34752
|
} catch {
|
|
@@ -35254,8 +35309,8 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
35254
35309
|
|
|
35255
35310
|
// src/channels/wechat.ts
|
|
35256
35311
|
import * as crypto5 from "crypto";
|
|
35257
|
-
import * as
|
|
35258
|
-
import * as
|
|
35312
|
+
import * as fs38 from "fs";
|
|
35313
|
+
import * as path38 from "path";
|
|
35259
35314
|
import * as os4 from "os";
|
|
35260
35315
|
import { promises as dns } from "dns";
|
|
35261
35316
|
function sleep3(ms) {
|
|
@@ -35479,7 +35534,7 @@ function guessChatType(msg2, accountId) {
|
|
|
35479
35534
|
return isGroup ? { type: "group", chatId: roomId || toId || String(msg2.from_user_id || "") } : { type: "dm", chatId: String(msg2.from_user_id || "") };
|
|
35480
35535
|
}
|
|
35481
35536
|
function mimeFromFilename(filename) {
|
|
35482
|
-
const ext =
|
|
35537
|
+
const ext = path38.extname(filename).toLowerCase();
|
|
35483
35538
|
const map = {
|
|
35484
35539
|
".jpg": "image/jpeg",
|
|
35485
35540
|
".jpeg": "image/jpeg",
|
|
@@ -35546,15 +35601,15 @@ var ContextTokenStore = class {
|
|
|
35546
35601
|
cache = /* @__PURE__ */ new Map();
|
|
35547
35602
|
filePath;
|
|
35548
35603
|
constructor(stateDir, accountId) {
|
|
35549
|
-
this.filePath =
|
|
35604
|
+
this.filePath = path38.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
|
|
35550
35605
|
}
|
|
35551
35606
|
k(a, p) {
|
|
35552
35607
|
return `${a}:${p}`;
|
|
35553
35608
|
}
|
|
35554
35609
|
restore() {
|
|
35555
35610
|
try {
|
|
35556
|
-
if (
|
|
35557
|
-
for (const [k, v] of Object.entries(JSON.parse(
|
|
35611
|
+
if (fs38.existsSync(this.filePath)) {
|
|
35612
|
+
for (const [k, v] of Object.entries(JSON.parse(fs38.readFileSync(this.filePath, "utf-8"))))
|
|
35558
35613
|
this.cache.set(k, String(v));
|
|
35559
35614
|
}
|
|
35560
35615
|
} catch {
|
|
@@ -35562,11 +35617,11 @@ var ContextTokenStore = class {
|
|
|
35562
35617
|
}
|
|
35563
35618
|
save() {
|
|
35564
35619
|
try {
|
|
35565
|
-
const dir =
|
|
35566
|
-
if (!
|
|
35620
|
+
const dir = path38.dirname(this.filePath);
|
|
35621
|
+
if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
|
|
35567
35622
|
const obj = {};
|
|
35568
35623
|
this.cache.forEach((v, k) => obj[k] = v);
|
|
35569
|
-
|
|
35624
|
+
fs38.writeFileSync(this.filePath, JSON.stringify(obj));
|
|
35570
35625
|
} catch {
|
|
35571
35626
|
}
|
|
35572
35627
|
}
|
|
@@ -35586,18 +35641,18 @@ var ContextTokenStore = class {
|
|
|
35586
35641
|
};
|
|
35587
35642
|
function loadSyncBuf(stateDir, accountId) {
|
|
35588
35643
|
try {
|
|
35589
|
-
const fp =
|
|
35590
|
-
if (
|
|
35644
|
+
const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35645
|
+
if (fs38.existsSync(fp)) return String(JSON.parse(fs38.readFileSync(fp, "utf-8")).sync_buf || "");
|
|
35591
35646
|
} catch {
|
|
35592
35647
|
}
|
|
35593
35648
|
return "";
|
|
35594
35649
|
}
|
|
35595
35650
|
function saveSyncBuf(stateDir, accountId, buf) {
|
|
35596
35651
|
try {
|
|
35597
|
-
const fp =
|
|
35598
|
-
const dir =
|
|
35599
|
-
if (!
|
|
35600
|
-
|
|
35652
|
+
const fp = path38.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35653
|
+
const dir = path38.dirname(fp);
|
|
35654
|
+
if (!fs38.existsSync(dir)) fs38.mkdirSync(dir, { recursive: true });
|
|
35655
|
+
fs38.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
|
|
35601
35656
|
} catch {
|
|
35602
35657
|
}
|
|
35603
35658
|
}
|
|
@@ -35639,8 +35694,8 @@ var WechatAdapter = class {
|
|
|
35639
35694
|
this.config = config2;
|
|
35640
35695
|
this.baseUrl = config2.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
|
|
35641
35696
|
this.cdnBaseUrl = config2.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
|
|
35642
|
-
this.stateDir = config2.stateDir ||
|
|
35643
|
-
if (!
|
|
35697
|
+
this.stateDir = config2.stateDir || path38.join(os4.homedir?.() || "/tmp", ".engine7");
|
|
35698
|
+
if (!fs38.existsSync(this.stateDir)) fs38.mkdirSync(this.stateDir, { recursive: true });
|
|
35644
35699
|
}
|
|
35645
35700
|
// --- ChannelAdapter interface ---
|
|
35646
35701
|
onMessage(handler2) {
|
|
@@ -35693,8 +35748,8 @@ var WechatAdapter = class {
|
|
|
35693
35748
|
}
|
|
35694
35749
|
async sendFile(target, message, attachment) {
|
|
35695
35750
|
const filePath = attachment.path;
|
|
35696
|
-
if (!
|
|
35697
|
-
const plaintext =
|
|
35751
|
+
if (!fs38.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
35752
|
+
const plaintext = fs38.readFileSync(filePath);
|
|
35698
35753
|
const mediaType = this.guessMediaType(filePath, attachment.mimeType);
|
|
35699
35754
|
const filekey = crypto5.randomBytes(16).toString("hex");
|
|
35700
35755
|
const aesKey = crypto5.randomBytes(16);
|
|
@@ -35726,7 +35781,7 @@ var WechatAdapter = class {
|
|
|
35726
35781
|
aesKeyForApi,
|
|
35727
35782
|
ciphertextSize: ciphertext.length,
|
|
35728
35783
|
plaintextSize: rawsize,
|
|
35729
|
-
filename:
|
|
35784
|
+
filename: path38.basename(filePath),
|
|
35730
35785
|
rawfilemd5,
|
|
35731
35786
|
voiceDurationSec: attachment.voiceDurationSec
|
|
35732
35787
|
});
|
|
@@ -35985,10 +36040,10 @@ var WechatAdapter = class {
|
|
|
35985
36040
|
mediaRef.full_url,
|
|
35986
36041
|
timeoutSec
|
|
35987
36042
|
);
|
|
35988
|
-
const tmpDir =
|
|
35989
|
-
if (!
|
|
35990
|
-
const tmpPath =
|
|
35991
|
-
|
|
36043
|
+
const tmpDir = path38.join(os4.tmpdir(), "weixin-media");
|
|
36044
|
+
if (!fs38.existsSync(tmpDir)) fs38.mkdirSync(tmpDir, { recursive: true });
|
|
36045
|
+
const tmpPath = path38.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
|
|
36046
|
+
fs38.writeFileSync(tmpPath, data);
|
|
35992
36047
|
return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
|
|
35993
36048
|
}
|
|
35994
36049
|
// --- Internal: send helpers ---
|
|
@@ -36038,7 +36093,7 @@ var WechatAdapter = class {
|
|
|
36038
36093
|
throw lastError;
|
|
36039
36094
|
}
|
|
36040
36095
|
guessMediaType(filePath, mimeType) {
|
|
36041
|
-
const ext =
|
|
36096
|
+
const ext = path38.extname(filePath).toLowerCase();
|
|
36042
36097
|
const mime = mimeType || mimeFromFilename(filePath);
|
|
36043
36098
|
if (mime.startsWith("image/")) return MEDIA_IMAGE;
|
|
36044
36099
|
if (mime.startsWith("video/")) return MEDIA_VIDEO;
|
|
@@ -36077,180 +36132,54 @@ var WechatAdapter = class {
|
|
|
36077
36132
|
// src/channels/desk-buddy.ts
|
|
36078
36133
|
import { WebSocketServer, WebSocket } from "ws";
|
|
36079
36134
|
|
|
36080
|
-
// src/tools/
|
|
36135
|
+
// src/tools/tts/impl.ts
|
|
36081
36136
|
init_live();
|
|
36082
36137
|
init_well_formed();
|
|
36083
|
-
|
|
36084
|
-
|
|
36085
|
-
|
|
36138
|
+
init_voice_chat_tts_daemon();
|
|
36139
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
36140
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
36141
|
+
import { promisify as promisify2 } from "node:util";
|
|
36086
36142
|
import * as fs40 from "node:fs";
|
|
36087
|
-
|
|
36088
|
-
|
|
36089
|
-
|
|
36090
|
-
var
|
|
36091
|
-
var
|
|
36092
|
-
|
|
36093
|
-
|
|
36094
|
-
|
|
36095
|
-
let r = i << 24;
|
|
36096
|
-
for (let k = 0; k < 8; k++) r = r & 2147483648 ? r << 1 ^ 79764919 : r << 1;
|
|
36097
|
-
t[i] = r >>> 0;
|
|
36098
|
-
}
|
|
36099
|
-
return t;
|
|
36100
|
-
})();
|
|
36101
|
-
function oggCrc32(buf) {
|
|
36102
|
-
let crc = 0;
|
|
36103
|
-
for (let i = 0; i < buf.length; i++) {
|
|
36104
|
-
crc = (crc << 8 ^ CRC_TABLE[(crc >>> 24 ^ buf[i]) & 255]) >>> 0;
|
|
36105
|
-
}
|
|
36106
|
-
return crc >>> 0;
|
|
36107
|
-
}
|
|
36108
|
-
var OggWriter = class {
|
|
36109
|
-
serial;
|
|
36110
|
-
seq = 0;
|
|
36111
|
-
pages = [];
|
|
36112
|
-
constructor() {
|
|
36113
|
-
this.serial = Math.random() * 4294967296 >>> 0;
|
|
36114
|
-
}
|
|
36115
|
-
/** 组一页。packets 为完整数据包;granule = 该页最后一包的 48k 域样本位置。 */
|
|
36116
|
-
page(packets, granule, bos = false, eos = false) {
|
|
36117
|
-
const lacing = [];
|
|
36118
|
-
for (const p of packets) {
|
|
36119
|
-
let rem = p.length;
|
|
36120
|
-
while (rem >= 255) {
|
|
36121
|
-
lacing.push(255);
|
|
36122
|
-
rem -= 255;
|
|
36123
|
-
}
|
|
36124
|
-
lacing.push(rem);
|
|
36125
|
-
}
|
|
36126
|
-
const payloadLen = packets.reduce((n, p) => n + p.length, 0);
|
|
36127
|
-
const page = Buffer.alloc(27 + lacing.length + payloadLen);
|
|
36128
|
-
page.write("OggS", 0, "ascii");
|
|
36129
|
-
page[4] = 0;
|
|
36130
|
-
page[5] = (bos ? 2 : 0) | (eos ? 1 : 0);
|
|
36131
|
-
page.writeBigUInt64LE(BigInt(granule), 6);
|
|
36132
|
-
page.writeUInt32LE(this.serial, 14);
|
|
36133
|
-
page.writeUInt32LE(this.seq++, 18);
|
|
36134
|
-
page.writeUInt32LE(0, 22);
|
|
36135
|
-
page[26] = lacing.length;
|
|
36136
|
-
lacing.forEach((v, i) => {
|
|
36137
|
-
page[27 + i] = v;
|
|
36138
|
-
});
|
|
36139
|
-
let off = 27 + lacing.length;
|
|
36140
|
-
for (const p of packets) {
|
|
36141
|
-
p.copy(page, off);
|
|
36142
|
-
off += p.length;
|
|
36143
|
-
}
|
|
36144
|
-
page.writeUInt32LE(oggCrc32(page), 22);
|
|
36145
|
-
this.pages.push(page);
|
|
36146
|
-
}
|
|
36147
|
-
finish() {
|
|
36148
|
-
return Buffer.concat(this.pages);
|
|
36149
|
-
}
|
|
36150
|
-
};
|
|
36151
|
-
function opusHeadPacket(channels, inputRate) {
|
|
36152
|
-
const b = Buffer.alloc(19);
|
|
36153
|
-
b.write("OpusHead", 0, "ascii");
|
|
36154
|
-
b[8] = 1;
|
|
36155
|
-
b[9] = channels;
|
|
36156
|
-
b.writeUInt16LE(PRESKIP_48K, 10);
|
|
36157
|
-
b.writeUInt32LE(inputRate, 12);
|
|
36158
|
-
b.writeInt16LE(0, 16);
|
|
36159
|
-
b[18] = 0;
|
|
36160
|
-
return b;
|
|
36161
|
-
}
|
|
36162
|
-
function opusTagsPacket() {
|
|
36163
|
-
const vendor = Buffer.from("engine7-native-opus", "utf8");
|
|
36164
|
-
const b = Buffer.alloc(8 + 4 + vendor.length + 4);
|
|
36165
|
-
b.write("OpusTags", 0, "ascii");
|
|
36166
|
-
b.writeUInt32LE(vendor.length, 8);
|
|
36167
|
-
vendor.copy(b, 12);
|
|
36168
|
-
b.writeUInt32LE(0, 12 + vendor.length);
|
|
36169
|
-
return b;
|
|
36170
|
-
}
|
|
36171
|
-
function parseWav(buf) {
|
|
36172
|
-
if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
36173
|
-
throw new Error("not a RIFF/WAVE file");
|
|
36143
|
+
import * as path40 from "node:path";
|
|
36144
|
+
import * as os5 from "node:os";
|
|
36145
|
+
import net3 from "net";
|
|
36146
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
36147
|
+
var VOICE_DIR = path40.join(os5.tmpdir(), "engine-voice");
|
|
36148
|
+
function wavDurationSec(buf) {
|
|
36149
|
+
if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
36150
|
+
throw new Error("wavDurationSec: not a RIFF/WAVE file");
|
|
36174
36151
|
}
|
|
36175
36152
|
let off = 12;
|
|
36176
|
-
let
|
|
36177
|
-
let
|
|
36178
|
-
|
|
36153
|
+
let fmtFound = false;
|
|
36154
|
+
let dataFound = false;
|
|
36155
|
+
let byteRate = 0;
|
|
36156
|
+
let dataOff = 0;
|
|
36157
|
+
let dataSize = 0;
|
|
36158
|
+
while (off + 8 <= buf.length && !(fmtFound && dataFound)) {
|
|
36179
36159
|
const id = buf.toString("ascii", off, off + 4);
|
|
36180
36160
|
const size = buf.readUInt32LE(off + 4);
|
|
36181
36161
|
const body = off + 8;
|
|
36182
|
-
if (id === "fmt " &&
|
|
36183
|
-
fmt
|
|
36184
|
-
|
|
36185
|
-
|
|
36186
|
-
|
|
36187
|
-
|
|
36188
|
-
|
|
36189
|
-
|
|
36190
|
-
pcm = buf.subarray(body, body + Math.min(size, buf.length - body));
|
|
36162
|
+
if (id === "fmt " && !fmtFound) {
|
|
36163
|
+
if (body + 12 > buf.length) throw new Error("wavDurationSec: truncated fmt chunk");
|
|
36164
|
+
byteRate = buf.readUInt32LE(body + 8);
|
|
36165
|
+
fmtFound = true;
|
|
36166
|
+
} else if (id === "data" && !dataFound) {
|
|
36167
|
+
dataOff = body;
|
|
36168
|
+
dataSize = size;
|
|
36169
|
+
dataFound = true;
|
|
36191
36170
|
}
|
|
36192
36171
|
off = body + size + (size & 1);
|
|
36193
36172
|
}
|
|
36194
|
-
if (!
|
|
36195
|
-
if (
|
|
36196
|
-
if (
|
|
36197
|
-
|
|
36198
|
-
|
|
36199
|
-
|
|
36200
|
-
|
|
36201
|
-
const OpusEncoder = mod.OpusEncoder ?? mod.default?.OpusEncoder;
|
|
36202
|
-
if (!OpusEncoder) throw new Error("@discordjs/opus export shape unexpected");
|
|
36203
|
-
const wav = parseWav(fs40.readFileSync(wavPath));
|
|
36204
|
-
if (wav.channels !== 1) throw new Error(`unsupported ${wav.channels}ch (need mono)`);
|
|
36205
|
-
if (!NATIVE_RATES.includes(wav.sampleRate)) throw new Error(`unsupported rate ${wav.sampleRate}`);
|
|
36206
|
-
if (wav.pcm.length === 0) throw new Error("empty pcm data");
|
|
36207
|
-
const encoder = new OpusEncoder(wav.sampleRate, wav.channels);
|
|
36208
|
-
encoder.setBitrate(BITRATE);
|
|
36209
|
-
const samplesPerFrame = Math.round(wav.sampleRate * FRAME_MS / 1e3);
|
|
36210
|
-
const bytesPerFrame = samplesPerFrame * 2;
|
|
36211
|
-
const frames = Math.ceil(wav.pcm.length / bytesPerFrame);
|
|
36212
|
-
const padded = Buffer.alloc(frames * bytesPerFrame);
|
|
36213
|
-
wav.pcm.copy(padded);
|
|
36214
|
-
const ogg = new OggWriter();
|
|
36215
|
-
ogg.page([opusHeadPacket(wav.channels, wav.sampleRate)], 0, true);
|
|
36216
|
-
ogg.page([opusTagsPacket()], 0);
|
|
36217
|
-
let cur = [];
|
|
36218
|
-
let curSegs = 0;
|
|
36219
|
-
let curBytes = 0;
|
|
36220
|
-
let pageGranule = 0;
|
|
36221
|
-
let pos48k = PRESKIP_48K;
|
|
36222
|
-
for (let f = 0; f < frames; f++) {
|
|
36223
|
-
const packet = encoder.encode(padded.subarray(f * bytesPerFrame, (f + 1) * bytesPerFrame));
|
|
36224
|
-
const pSegs = Math.floor((packet.length - 1) / 255) + 1;
|
|
36225
|
-
if (curSegs + pSegs > 255 || curBytes + packet.length > 6e4) {
|
|
36226
|
-
ogg.page(cur, pageGranule);
|
|
36227
|
-
cur = [];
|
|
36228
|
-
curSegs = 0;
|
|
36229
|
-
curBytes = 0;
|
|
36230
|
-
}
|
|
36231
|
-
cur.push(packet);
|
|
36232
|
-
curSegs += pSegs;
|
|
36233
|
-
curBytes += packet.length;
|
|
36234
|
-
pos48k += samplesPerFrame * (OPUS_RATE / wav.sampleRate);
|
|
36235
|
-
pageGranule = pos48k;
|
|
36173
|
+
if (!fmtFound) throw new Error("wavDurationSec: missing fmt chunk");
|
|
36174
|
+
if (!dataFound) throw new Error("wavDurationSec: missing data chunk");
|
|
36175
|
+
if (!(byteRate > 0)) throw new Error(`wavDurationSec: invalid byteRate ${byteRate}`);
|
|
36176
|
+
const bytes = dataSize === 4294967295 ? buf.length - dataOff : dataSize;
|
|
36177
|
+
const sec = Math.round(bytes / byteRate * 1e3) / 1e3;
|
|
36178
|
+
if (!Number.isFinite(sec) || sec <= 0) {
|
|
36179
|
+
throw new Error(`wavDurationSec: non-positive duration (${bytes} bytes / ${byteRate} Bps)`);
|
|
36236
36180
|
}
|
|
36237
|
-
|
|
36238
|
-
const oggPath = wavPath.replace(/\.wav$/, ".ogg");
|
|
36239
|
-
fs40.writeFileSync(oggPath, ogg.finish());
|
|
36240
|
-
return oggPath;
|
|
36181
|
+
return sec;
|
|
36241
36182
|
}
|
|
36242
|
-
|
|
36243
|
-
// src/tools/my-voice.ts
|
|
36244
|
-
init_voice_chat_tts_daemon();
|
|
36245
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
36246
|
-
import { createInterface as createInterface2 } from "node:readline";
|
|
36247
|
-
import { promisify as promisify2 } from "node:util";
|
|
36248
|
-
import * as fs42 from "node:fs";
|
|
36249
|
-
import * as path41 from "node:path";
|
|
36250
|
-
import * as os5 from "node:os";
|
|
36251
|
-
import net4 from "net";
|
|
36252
|
-
var execFileAsync2 = promisify2(execFile2);
|
|
36253
|
-
var VOICE_DIR = path41.join(os5.tmpdir(), "engine-voice");
|
|
36254
36183
|
var _pyCache = null;
|
|
36255
36184
|
function resolvePython(configuredPath) {
|
|
36256
36185
|
if (configuredPath) {
|
|
@@ -36263,11 +36192,16 @@ function resolvePython(configuredPath) {
|
|
|
36263
36192
|
}
|
|
36264
36193
|
}
|
|
36265
36194
|
if (_pyCache) return _pyCache;
|
|
36195
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
36266
36196
|
const candidates = [
|
|
36267
36197
|
"python3",
|
|
36268
36198
|
"python",
|
|
36269
|
-
|
|
36270
|
-
|
|
36199
|
+
// LOCALAPPDATA 缺席(非 Windows / 精简环境)时**不加这两个候选**:
|
|
36200
|
+
// 拼出来会是相对路径 'Microsoft/WindowsApps/python.exe',那可能撞上 cwd 里的东西。
|
|
36201
|
+
...localAppData ? [
|
|
36202
|
+
path40.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
|
|
36203
|
+
path40.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
|
|
36204
|
+
] : []
|
|
36271
36205
|
];
|
|
36272
36206
|
for (const c of candidates) {
|
|
36273
36207
|
try {
|
|
@@ -36286,8 +36220,8 @@ async function ttsCosyvoice(text, apiKey, model, voice, workspaceId, instruction
|
|
|
36286
36220
|
}
|
|
36287
36221
|
async function ttsCosyvoiceStream(text, cfg, onChunk) {
|
|
36288
36222
|
const { apiKey, model, voice, workspaceId, instruction } = cfg;
|
|
36289
|
-
|
|
36290
|
-
const output =
|
|
36223
|
+
fs40.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36224
|
+
const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36291
36225
|
const script = `
|
|
36292
36226
|
import sys, json, base64, threading
|
|
36293
36227
|
import dashscope
|
|
@@ -36372,7 +36306,7 @@ print('EOS')
|
|
|
36372
36306
|
header.writeUInt16LE(16, 34);
|
|
36373
36307
|
header.write("data", 36);
|
|
36374
36308
|
header.writeUInt32LE(pcm.length, 40);
|
|
36375
|
-
|
|
36309
|
+
fs40.writeFileSync(output, Buffer.concat([header, pcm]));
|
|
36376
36310
|
return output;
|
|
36377
36311
|
}
|
|
36378
36312
|
async function ttsCosyvoiceWorker(text, cfg, onChunk) {
|
|
@@ -36384,7 +36318,7 @@ async function ttsCosyvoiceWorker(text, cfg, onChunk) {
|
|
|
36384
36318
|
}
|
|
36385
36319
|
}
|
|
36386
36320
|
return new Promise((resolve12, reject) => {
|
|
36387
|
-
const sock =
|
|
36321
|
+
const sock = net3.connect(getSockPath());
|
|
36388
36322
|
let buf = "";
|
|
36389
36323
|
const finish = (err) => {
|
|
36390
36324
|
try {
|
|
@@ -36482,35 +36416,39 @@ async function ttsQwen3Opus(text, cfg) {
|
|
|
36482
36416
|
const ogg = Buffer.from(await res.arrayBuffer());
|
|
36483
36417
|
if (ogg.length < 20) throw new Error("qwen3-3060 opus: empty output");
|
|
36484
36418
|
const durationMs = parseInt(res.headers.get("x-duration-ms") || "0", 10);
|
|
36485
|
-
|
|
36486
|
-
const oggPath =
|
|
36487
|
-
|
|
36419
|
+
fs40.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36420
|
+
const oggPath = path40.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
|
|
36421
|
+
fs40.writeFileSync(oggPath, ogg);
|
|
36488
36422
|
return { oggPath, durationMs };
|
|
36489
36423
|
}
|
|
36490
|
-
|
|
36491
|
-
|
|
36492
|
-
|
|
36493
|
-
|
|
36494
|
-
|
|
36495
|
-
|
|
36496
|
-
|
|
36424
|
+
async function ttsGptsovits(text, c) {
|
|
36425
|
+
const url = String(c.url || "").replace(/\/+$/, "");
|
|
36426
|
+
const refWav = String(c.refWav || "");
|
|
36427
|
+
const refText = String(c.refText || "");
|
|
36428
|
+
if (!url || !refWav || !refText) {
|
|
36429
|
+
throw new TtsConfigError(
|
|
36430
|
+
"gptsovits \u672A\u914D\u7F6E\uFF1A\u9700\u8981 tools.my_voice \u91CC\u7684 gptsovitsUrl / gptsovitsRefWav / gptsovitsRefText\u3002\u53C2\u8003\u97F3\u9891\u4E0E\u53C2\u8003\u6587\u672C**\u5FC5\u987B\u9010\u5B57\u5BF9\u5E94**\uFF0C\u5426\u5219\u5408\u6210\u51FA\u6765\u7684\u58F0\u97F3\u4F1A\u5E26\u6742\u97F3\uFF080925 \u8E29\u8FC7\u88C1\u574F ref.wav \u7684\u5751\uFF09\u3002"
|
|
36431
|
+
);
|
|
36432
|
+
}
|
|
36433
|
+
fs40.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36434
|
+
const output = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36497
36435
|
const params = new URLSearchParams({
|
|
36498
36436
|
text,
|
|
36499
36437
|
text_language: "zh",
|
|
36500
|
-
refer_wav_path:
|
|
36501
|
-
prompt_text:
|
|
36502
|
-
prompt_language:
|
|
36438
|
+
refer_wav_path: refWav,
|
|
36439
|
+
prompt_text: refText,
|
|
36440
|
+
prompt_language: String(c.refLang || "zh")
|
|
36503
36441
|
});
|
|
36504
|
-
const res = await fetch(`${
|
|
36442
|
+
const res = await fetch(`${url}/?${params}`);
|
|
36505
36443
|
if (!res.ok) throw new Error(`GPT-SoVITS API ${res.status}`);
|
|
36506
36444
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
36507
|
-
|
|
36445
|
+
fs40.writeFileSync(output, buf);
|
|
36508
36446
|
return output;
|
|
36509
36447
|
}
|
|
36510
36448
|
var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
|
|
36511
36449
|
async function ttsEdge(text) {
|
|
36512
|
-
|
|
36513
|
-
const output =
|
|
36450
|
+
fs40.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36451
|
+
const output = path40.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
|
|
36514
36452
|
const script = `
|
|
36515
36453
|
import asyncio, edge_tts, sys
|
|
36516
36454
|
async def main():
|
|
@@ -36521,363 +36459,64 @@ asyncio.run(main())
|
|
|
36521
36459
|
await execFileAsync2(resolvePython((liveConfig.get("tools.my_voice") || {}).pythonPath), ["-c", script, text, EDGE_VOICE, output], { timeout: 3e4 });
|
|
36522
36460
|
return output;
|
|
36523
36461
|
}
|
|
36524
|
-
|
|
36525
|
-
|
|
36526
|
-
|
|
36527
|
-
|
|
36528
|
-
|
|
36529
|
-
"-y",
|
|
36530
|
-
"-i",
|
|
36531
|
-
audioPath,
|
|
36532
|
-
"-ar",
|
|
36533
|
-
"24000",
|
|
36534
|
-
"-ac",
|
|
36535
|
-
"1",
|
|
36536
|
-
"-c:a",
|
|
36537
|
-
"pcm_s16le",
|
|
36538
|
-
wavPath
|
|
36539
|
-
], { timeout: 3e4 });
|
|
36540
|
-
if (!fs42.existsSync(wavPath) || fs42.statSync(wavPath).size < 44) {
|
|
36541
|
-
throw new Error("toWav24k produced empty output");
|
|
36542
|
-
}
|
|
36543
|
-
try {
|
|
36544
|
-
fs42.unlinkSync(audioPath);
|
|
36545
|
-
} catch {
|
|
36546
|
-
}
|
|
36547
|
-
}
|
|
36548
|
-
const script = `
|
|
36549
|
-
import sys, wave
|
|
36550
|
-
with wave.open(sys.argv[1], 'rb') as wf:
|
|
36551
|
-
print(round(wf.getnframes() / wf.getframerate(), 3))
|
|
36552
|
-
`;
|
|
36553
|
-
const { stdout } = await execFileAsync2(resolvePython((liveConfig.get("tools.my_voice") || {}).pythonPath), ["-c", script, wavPath], { timeout: 15e3 });
|
|
36554
|
-
const durationSec = parseFloat(stdout.trim());
|
|
36555
|
-
if (!Number.isFinite(durationSec) || durationSec <= 0) {
|
|
36556
|
-
throw new Error(`invalid duration: ${stdout.trim()}`);
|
|
36557
|
-
}
|
|
36558
|
-
return { wavPath, durationSec };
|
|
36559
|
-
}
|
|
36560
|
-
async function compressWav(wavPath) {
|
|
36561
|
-
const m4aPath = wavPath.replace(/\.wav$/, ".m4a");
|
|
36562
|
-
try {
|
|
36563
|
-
await execFileAsync2("ffmpeg", [
|
|
36564
|
-
"-y",
|
|
36565
|
-
"-i",
|
|
36566
|
-
wavPath,
|
|
36567
|
-
"-c:a",
|
|
36568
|
-
"aac",
|
|
36569
|
-
"-b:a",
|
|
36570
|
-
"64k",
|
|
36571
|
-
"-movflags",
|
|
36572
|
-
"+faststart",
|
|
36573
|
-
m4aPath
|
|
36574
|
-
], { timeout: 3e4 });
|
|
36575
|
-
fs42.unlinkSync(wavPath);
|
|
36576
|
-
return m4aPath;
|
|
36577
|
-
} catch {
|
|
36578
|
-
return wavPath;
|
|
36462
|
+
var unit = (text) => ({ kind: "chars", amount: [...text].length });
|
|
36463
|
+
var TtsConfigError = class extends Error {
|
|
36464
|
+
constructor(message) {
|
|
36465
|
+
super(message);
|
|
36466
|
+
this.name = "TtsConfigError";
|
|
36579
36467
|
}
|
|
36468
|
+
};
|
|
36469
|
+
async function synthCosyvoice(text, _sink) {
|
|
36470
|
+
const c = liveConfig.get("tools.my_voice") || {};
|
|
36471
|
+
if (!c.apiKey || !c.voice) throw new TtsConfigError("cosyvoice provider requires apiKey and voice in config");
|
|
36472
|
+
const p = await ttsCosyvoice(text, c.apiKey, c.model || "cosyvoice-v1", c.voice, c.workspaceId, c.instruction);
|
|
36473
|
+
return { path: p, format: "wav", unit: unit(text), model: c.model, voice: c.voice };
|
|
36474
|
+
}
|
|
36475
|
+
async function synthGptsovits(text, _sink) {
|
|
36476
|
+
const c = liveConfig.get("tools.my_voice") || {};
|
|
36477
|
+
const p = await ttsGptsovits(text, {
|
|
36478
|
+
url: c.gptsovitsUrl,
|
|
36479
|
+
refWav: c.gptsovitsRefWav,
|
|
36480
|
+
refText: c.gptsovitsRefText,
|
|
36481
|
+
refLang: c.gptsovitsRefLang
|
|
36482
|
+
});
|
|
36483
|
+
return { path: p, format: "wav", unit: unit(text) };
|
|
36580
36484
|
}
|
|
36581
|
-
async function
|
|
36582
|
-
|
|
36485
|
+
async function synthQwen3(text, _sink) {
|
|
36486
|
+
const c = liveConfig.get("tools.my_voice") || {};
|
|
36487
|
+
if (!c.url) throw new TtsConfigError("qwen3-3060 provider requires url in config");
|
|
36583
36488
|
try {
|
|
36584
|
-
oggPath = await
|
|
36489
|
+
const { oggPath, durationMs } = await ttsQwen3Opus(text, { url: c.url, voice: c.voice, lang: c.lang });
|
|
36490
|
+
return { path: oggPath, format: "ogg", durationMs, unit: unit(text), voice: c.voice };
|
|
36585
36491
|
} catch (e) {
|
|
36586
|
-
|
|
36587
|
-
|
|
36588
|
-
|
|
36589
|
-
|
|
36590
|
-
|
|
36591
|
-
|
|
36592
|
-
|
|
36593
|
-
|
|
36594
|
-
|
|
36595
|
-
|
|
36596
|
-
|
|
36597
|
-
|
|
36598
|
-
|
|
36599
|
-
|
|
36600
|
-
|
|
36601
|
-
|
|
36602
|
-
|
|
36603
|
-
|
|
36604
|
-
|
|
36605
|
-
|
|
36606
|
-
|
|
36492
|
+
console.warn(`[my-voice] qwen3-3060 opus failed (${e.message}), trying PCM stream`);
|
|
36493
|
+
const pcmChunks = [];
|
|
36494
|
+
await ttsQwen3Local(text, { url: c.url, voice: c.voice, lang: c.lang }, (pcm2) => pcmChunks.push(pcm2));
|
|
36495
|
+
const pcm = Buffer.concat(pcmChunks);
|
|
36496
|
+
fs40.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36497
|
+
const wavPath = path40.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36498
|
+
const header = Buffer.alloc(44);
|
|
36499
|
+
header.write("RIFF", 0);
|
|
36500
|
+
header.writeUInt32LE(36 + pcm.length, 4);
|
|
36501
|
+
header.write("WAVE", 8);
|
|
36502
|
+
header.write("fmt ", 12);
|
|
36503
|
+
header.writeUInt32LE(16, 16);
|
|
36504
|
+
header.writeUInt16LE(1, 20);
|
|
36505
|
+
header.writeUInt16LE(1, 22);
|
|
36506
|
+
header.writeUInt32LE(24e3, 24);
|
|
36507
|
+
header.writeUInt32LE(48e3, 28);
|
|
36508
|
+
header.writeUInt16LE(2, 32);
|
|
36509
|
+
header.writeUInt16LE(16, 34);
|
|
36510
|
+
header.write("data", 36);
|
|
36511
|
+
header.writeUInt32LE(pcm.length, 40);
|
|
36512
|
+
fs40.writeFileSync(wavPath, Buffer.concat([header, pcm]));
|
|
36513
|
+
return { path: wavPath, format: "wav", unit: unit(text), voice: c.voice };
|
|
36514
|
+
}
|
|
36515
|
+
}
|
|
36516
|
+
async function synthEdge(text, _sink) {
|
|
36517
|
+
const p = await ttsEdge(text);
|
|
36518
|
+
return { path: p, format: "mp3", unit: unit(text) };
|
|
36607
36519
|
}
|
|
36608
|
-
async function sendVoiceToMulticaIssue(text, issueId, opts) {
|
|
36609
|
-
const vc = liveConfig.get("tools.my_voice");
|
|
36610
|
-
let audioPath = "";
|
|
36611
|
-
let actualEngine = opts.engine || vc?.provider || "edge";
|
|
36612
|
-
try {
|
|
36613
|
-
if (actualEngine === "qwen3-3060" && vc?.url) {
|
|
36614
|
-
try {
|
|
36615
|
-
const { oggPath } = await ttsQwen3Opus(text, { url: vc.url, voice: vc.voice, lang: vc.lang });
|
|
36616
|
-
audioPath = oggPath;
|
|
36617
|
-
} catch (e) {
|
|
36618
|
-
console.warn(`[my-voice:multaca] qwen3 opus failed (${e.message}), trying PCM`);
|
|
36619
|
-
const pcmChunks = [];
|
|
36620
|
-
await ttsQwen3Local(text, { url: vc.url, voice: vc.voice, lang: vc.lang }, (pcm2) => pcmChunks.push(pcm2));
|
|
36621
|
-
const pcm = Buffer.concat(pcmChunks);
|
|
36622
|
-
fs42.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36623
|
-
const wavPath = path41.join(VOICE_DIR, `tts_mc_${Date.now()}.wav`);
|
|
36624
|
-
const header = Buffer.alloc(44);
|
|
36625
|
-
header.write("RIFF", 0);
|
|
36626
|
-
header.writeUInt32LE(36 + pcm.length, 4);
|
|
36627
|
-
header.write("WAVE", 8);
|
|
36628
|
-
header.write("fmt ", 12);
|
|
36629
|
-
header.writeUInt32LE(16, 16);
|
|
36630
|
-
header.writeUInt16LE(1, 20);
|
|
36631
|
-
header.writeUInt16LE(1, 22);
|
|
36632
|
-
header.writeUInt32LE(24e3, 24);
|
|
36633
|
-
header.writeUInt32LE(48e3, 28);
|
|
36634
|
-
header.writeUInt16LE(2, 32);
|
|
36635
|
-
header.writeUInt16LE(16, 34);
|
|
36636
|
-
header.write("data", 36);
|
|
36637
|
-
header.writeUInt32LE(pcm.length, 40);
|
|
36638
|
-
fs42.writeFileSync(wavPath, Buffer.concat([header, pcm]));
|
|
36639
|
-
audioPath = wavPath;
|
|
36640
|
-
}
|
|
36641
|
-
} else if (actualEngine === "cosyvoice" && vc?.apiKey) {
|
|
36642
|
-
audioPath = await ttsCosyvoice(text, vc.apiKey, vc.model || "cosyvoice-v1", vc.voice, vc.workspaceId, vc.instruction);
|
|
36643
|
-
} else if (actualEngine === "gptsovits") {
|
|
36644
|
-
audioPath = await ttsGptsovits(text);
|
|
36645
|
-
} else {
|
|
36646
|
-
audioPath = await ttsEdge(text);
|
|
36647
|
-
actualEngine = "edge";
|
|
36648
|
-
}
|
|
36649
|
-
} catch (e) {
|
|
36650
|
-
return { content: `\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
|
|
36651
|
-
}
|
|
36652
|
-
if (!audioPath || !fs42.existsSync(audioPath)) {
|
|
36653
|
-
return { content: "\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1ATTS \u672A\u4EA7\u51FA\u6587\u4EF6\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002", isError: true };
|
|
36654
|
-
}
|
|
36655
|
-
if (audioPath.endsWith(".wav")) {
|
|
36656
|
-
try {
|
|
36657
|
-
audioPath = await wavToOggOpus(audioPath);
|
|
36658
|
-
} catch (e) {
|
|
36659
|
-
console.warn(`[my-voice:multaca] opus encode failed (${e.message}), attaching wav`);
|
|
36660
|
-
}
|
|
36661
|
-
}
|
|
36662
|
-
const ext = path41.extname(audioPath).toLowerCase();
|
|
36663
|
-
const sizeKB = fs42.statSync(audioPath).size / 1024;
|
|
36664
|
-
const voiceFile = `voice_${Date.now()}${ext}`;
|
|
36665
|
-
const cwdCopy = path41.join(process.cwd(), voiceFile);
|
|
36666
|
-
try {
|
|
36667
|
-
fs42.copyFileSync(audioPath, cwdCopy);
|
|
36668
|
-
} catch (e) {
|
|
36669
|
-
try {
|
|
36670
|
-
fs42.unlinkSync(audioPath);
|
|
36671
|
-
} catch {
|
|
36672
|
-
}
|
|
36673
|
-
return { content: `\u8BED\u97F3\u6302\u8F7D\u5931\u8D25\uFF08\u62F7\u8D1D\u5230\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1A${e.message}`, isError: true };
|
|
36674
|
-
}
|
|
36675
|
-
try {
|
|
36676
|
-
fs42.unlinkSync(audioPath);
|
|
36677
|
-
} catch {
|
|
36678
|
-
}
|
|
36679
|
-
const content = opts.caption || `\u{1F399}\uFE0F \u8BED\u97F3\u6D88\u606F\uFF08${text.length} \u5B57\uFF09`;
|
|
36680
|
-
try {
|
|
36681
|
-
const { execFile: execFile4 } = await import("node:child_process");
|
|
36682
|
-
const { promisify: promisify4 } = await import("node:util");
|
|
36683
|
-
const execFileA = promisify4(execFile4);
|
|
36684
|
-
const { stdout } = await execFileA("multica", [
|
|
36685
|
-
"issue",
|
|
36686
|
-
"comment",
|
|
36687
|
-
"add",
|
|
36688
|
-
issueId,
|
|
36689
|
-
"--content",
|
|
36690
|
-
content,
|
|
36691
|
-
"--attachment",
|
|
36692
|
-
voiceFile,
|
|
36693
|
-
"--output",
|
|
36694
|
-
"json"
|
|
36695
|
-
], { timeout: 6e4 });
|
|
36696
|
-
let ok = false;
|
|
36697
|
-
try {
|
|
36698
|
-
const parsed = JSON.parse(stdout);
|
|
36699
|
-
ok = !!(parsed?.comment_id || parsed?.id || parsed?.attachments && parsed.attachments.length > 0);
|
|
36700
|
-
} catch {
|
|
36701
|
-
ok = stdout.includes("Comment added");
|
|
36702
|
-
}
|
|
36703
|
-
if (!ok) throw new Error(`unexpected CLI output: ${String(stdout).slice(0, 200)}`);
|
|
36704
|
-
try {
|
|
36705
|
-
fs42.unlinkSync(cwdCopy);
|
|
36706
|
-
} catch {
|
|
36707
|
-
}
|
|
36708
|
-
return { content: `Voice sent to multaca issue ${issueId}! (${actualEngine}, ${sizeKB.toFixed(0)}KB, \u9644\u4EF6\u5DF2\u6302\u5230\u8BC4\u8BBA\u2014\u2014\u5728 multaca \u5BA2\u6237\u7AEF\u64AD\u653E)` };
|
|
36709
|
-
} catch (e) {
|
|
36710
|
-
try {
|
|
36711
|
-
fs42.unlinkSync(cwdCopy);
|
|
36712
|
-
} catch {
|
|
36713
|
-
}
|
|
36714
|
-
return { content: `\u8BED\u97F3\u6302\u5230 multaca issue \u5931\u8D25\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
|
|
36715
|
-
}
|
|
36716
|
-
}
|
|
36717
|
-
registry.register({
|
|
36718
|
-
name: "my_voice",
|
|
36719
|
-
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.",
|
|
36720
|
-
schema: {
|
|
36721
|
-
type: "object",
|
|
36722
|
-
properties: {
|
|
36723
|
-
text: { type: "string", description: "What to say (Chinese text)." },
|
|
36724
|
-
engine: { type: "string", enum: ["cosyvoice", "gptsovits", "qwen3-3060", "edge"], description: "TTS engine. cosyvoice = \u767E\u70BCAPI (default if configured), gptsovits = local voice clone, qwen3-3060 = \u672C\u5730 GGML Q8_0 (3060), edge = backup." },
|
|
36725
|
-
channel: { type: "string", enum: ["weixin", "feishu", "discord"], description: "Target channel. Default: current channel." },
|
|
36726
|
-
to: { type: "string", description: "Recipient ID. \u5FAE\u4FE1\u683C\u5F0F o\u5F00\u5934@im.wechat\u7ED3\u5C3E\uFF0C\u98DE\u4E66\u683C\u5F0F ou_\u5F00\u5934 open_id\u3002\u4E0D\u786E\u5B9A\u5C31\u5148 read prompts/contacts.md \u67E5\u6536\u4EF6\u4EBA ID\u3002multaca \u4EFB\u52A1\u91CC\u4E0D\u9700\u8981\u586B to\u2014\u2014\u8BED\u97F3\u4F1A\u6302\u5230\u5F53\u524D issue \u8BC4\u8BBA\uFF0C\u586B issueId \u6307\u5B9A\u76EE\u6807 issue\u3002" },
|
|
36727
|
-
issueId: { type: "string", description: 'multaca headless \u4E13\u7528\uFF1A\u76EE\u6807 issue ID\uFF08\u4EFB\u52A1 prompt \u91CC\u7684 "Your assigned issue ID"\uFF09\u3002\u666E\u901A\u98DE\u4E66/\u5FAE\u4FE1\u573A\u666F\u4E0D\u8981\u586B\u3002' },
|
|
36728
|
-
caption: { type: "string", description: "Optional text to accompany the voice message." }
|
|
36729
|
-
},
|
|
36730
|
-
required: ["text"]
|
|
36731
|
-
},
|
|
36732
|
-
handler: async (args2, ctx) => {
|
|
36733
|
-
const text = args2.text?.trim();
|
|
36734
|
-
if (!text) return { content: "Error: text is required", isError: true };
|
|
36735
|
-
const requestedEngine = args2.engine || "";
|
|
36736
|
-
const caption = args2.caption || "";
|
|
36737
|
-
const mgr = ctx.channelManager;
|
|
36738
|
-
if (!mgr) {
|
|
36739
|
-
const multicaTaskId = process.env.MULTICA_TASK_ID || "";
|
|
36740
|
-
const issueId = args2.issueId || multicaTaskId;
|
|
36741
|
-
if (multicaTaskId && issueId) {
|
|
36742
|
-
return await sendVoiceToMulticaIssue(text, issueId, { engine: requestedEngine, caption });
|
|
36743
|
-
}
|
|
36744
|
-
return {
|
|
36745
|
-
content: "\u8BED\u97F3\u672A\u53D1\u9001\uFF1A\u5F53\u524D\u662F\u65E0\u6E20\u9053\u73AF\u5883\uFF08headless/CLI\uFF09\uFF0C\u4E14\u6CA1\u6709 multaca \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08MULTICA_TASK_ID\uFF09\u6216 issueId\u3002\u6CA1\u6709\u53EF\u7528\u7684\u53D1\u9001\u51FA\u53E3\uFF0C\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002",
|
|
36746
|
-
isError: true
|
|
36747
|
-
};
|
|
36748
|
-
}
|
|
36749
|
-
const resolvedChannelRaw = args2.channel || (ctx.channel === "deskBuddy" ? "feishu" : ctx.channel) || "feishu";
|
|
36750
|
-
const resolvedChannel = resolvedChannelRaw === "weixin" ? "wechat" : resolvedChannelRaw;
|
|
36751
|
-
let target = args2.to || ctx.channelTarget || ctx.from;
|
|
36752
|
-
if (resolvedChannel === "wechat" && !/^o[\w-]+@im\.wechat$/.test(target || "")) {
|
|
36753
|
-
console.warn(`[my-voice] wechat target "${target}" not a wechat user id`);
|
|
36754
|
-
return {
|
|
36755
|
-
content: `\u8BED\u97F3\u672A\u53D1\u9001\uFF1Ato "${target}" \u4E0D\u662F\u6709\u6548\u7684\u5FAE\u4FE1\u7528\u6237 ID\uFF08\u683C\u5F0F o \u5F00\u5934\u3001@im.wechat \u7ED3\u5C3E\uFF09\u3002\u4F60\u8981\u53D1\u7ED9\u8C01\uFF0C\u5C31\u5148 read prompts/contacts.md \u67E5\u90A3\u4E2A\u4EBA\u7684\u5FAE\u4FE1 ID\uFF0C\u518D\u586B\u8FDB to \u53C2\u6570\u91CD\u8BD5\u3002`,
|
|
36756
|
-
isError: true
|
|
36757
|
-
};
|
|
36758
|
-
}
|
|
36759
|
-
if (resolvedChannel === "feishu" && !/^ou_[a-f0-9]+$/.test(target || "")) {
|
|
36760
|
-
console.warn(`[my-voice] feishu target "${target}" invalid`);
|
|
36761
|
-
return {
|
|
36762
|
-
content: `\u8BED\u97F3\u672A\u53D1\u9001\uFF1Ato "${target}" \u4E0D\u662F\u6709\u6548\u7684\u98DE\u4E66\u7528\u6237 open_id\uFF08\u683C\u5F0F ou_ \u5F00\u5934\uFF09\u3002\u4F60\u8981\u53D1\u7ED9\u8C01\uFF0C\u5C31\u5148 read prompts/contacts.md \u67E5\u90A3\u4E2A\u4EBA\u7684\u98DE\u4E66 open_id\uFF0C\u518D\u586B\u8FDB to \u53C2\u6570\u91CD\u8BD5\u3002`,
|
|
36763
|
-
isError: true
|
|
36764
|
-
};
|
|
36765
|
-
}
|
|
36766
|
-
let voiceDurationSec;
|
|
36767
|
-
const vc = liveConfig.get("tools.my_voice");
|
|
36768
|
-
const provider = vc?.provider || "";
|
|
36769
|
-
const useEngine = requestedEngine || provider || "edge";
|
|
36770
|
-
let audioPath;
|
|
36771
|
-
let actualEngine = useEngine;
|
|
36772
|
-
try {
|
|
36773
|
-
if (useEngine === "cosyvoice") {
|
|
36774
|
-
if (!vc?.apiKey || !vc?.voice) throw new Error("cosyvoice provider requires apiKey and voice in config");
|
|
36775
|
-
try {
|
|
36776
|
-
audioPath = await ttsCosyvoice(
|
|
36777
|
-
text,
|
|
36778
|
-
vc.apiKey,
|
|
36779
|
-
vc.model || "cosyvoice-v1",
|
|
36780
|
-
vc.voice,
|
|
36781
|
-
vc.workspaceId,
|
|
36782
|
-
vc.instruction
|
|
36783
|
-
);
|
|
36784
|
-
} catch (e) {
|
|
36785
|
-
console.log(`[my-voice] cosyvoice failed (${e.message}), falling back to edge-tts`);
|
|
36786
|
-
audioPath = await ttsEdge(text);
|
|
36787
|
-
actualEngine = "edge";
|
|
36788
|
-
}
|
|
36789
|
-
} else if (useEngine === "gptsovits") {
|
|
36790
|
-
try {
|
|
36791
|
-
audioPath = await ttsGptsovits(text);
|
|
36792
|
-
} catch (e) {
|
|
36793
|
-
console.log(`[my-voice] gptsovits failed (${e.message}), falling back to edge-tts`);
|
|
36794
|
-
audioPath = await ttsEdge(text);
|
|
36795
|
-
actualEngine = "edge";
|
|
36796
|
-
}
|
|
36797
|
-
} else if (useEngine === "qwen3-3060") {
|
|
36798
|
-
if (!vc?.url) throw new Error("qwen3-3060 provider requires url in config");
|
|
36799
|
-
try {
|
|
36800
|
-
try {
|
|
36801
|
-
const { oggPath, durationMs } = await ttsQwen3Opus(text, { url: vc.url, voice: vc.voice, lang: vc.lang });
|
|
36802
|
-
audioPath = oggPath;
|
|
36803
|
-
if (durationMs > 0) voiceDurationSec = durationMs / 1e3;
|
|
36804
|
-
} catch (e) {
|
|
36805
|
-
console.warn(`[my-voice] qwen3-3060 opus failed (${e.message}), trying PCM stream`);
|
|
36806
|
-
const pcmChunks = [];
|
|
36807
|
-
await ttsQwen3Local(text, { url: vc.url, voice: vc.voice, lang: vc.lang }, (pcm2) => pcmChunks.push(pcm2));
|
|
36808
|
-
const pcm = Buffer.concat(pcmChunks);
|
|
36809
|
-
fs42.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36810
|
-
const wavPath = path41.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36811
|
-
const header = Buffer.alloc(44);
|
|
36812
|
-
header.write("RIFF", 0);
|
|
36813
|
-
header.writeUInt32LE(36 + pcm.length, 4);
|
|
36814
|
-
header.write("WAVE", 8);
|
|
36815
|
-
header.write("fmt ", 12);
|
|
36816
|
-
header.writeUInt32LE(16, 16);
|
|
36817
|
-
header.writeUInt16LE(1, 20);
|
|
36818
|
-
header.writeUInt16LE(1, 22);
|
|
36819
|
-
header.writeUInt32LE(24e3, 24);
|
|
36820
|
-
header.writeUInt32LE(48e3, 28);
|
|
36821
|
-
header.writeUInt16LE(2, 32);
|
|
36822
|
-
header.writeUInt16LE(16, 34);
|
|
36823
|
-
header.write("data", 36);
|
|
36824
|
-
header.writeUInt32LE(pcm.length, 40);
|
|
36825
|
-
fs42.writeFileSync(wavPath, Buffer.concat([header, pcm]));
|
|
36826
|
-
audioPath = wavPath;
|
|
36827
|
-
}
|
|
36828
|
-
} catch (e) {
|
|
36829
|
-
console.warn(`[my-voice] qwen3-3060 failed (${e.message}), falling back to edge-tts`);
|
|
36830
|
-
audioPath = await ttsEdge(text);
|
|
36831
|
-
actualEngine = "edge";
|
|
36832
|
-
}
|
|
36833
|
-
} else {
|
|
36834
|
-
audioPath = await ttsEdge(text);
|
|
36835
|
-
}
|
|
36836
|
-
} catch (e) {
|
|
36837
|
-
return { content: `TTS failed: ${e.message}`, isError: true };
|
|
36838
|
-
}
|
|
36839
|
-
if (!audioPath.endsWith(".ogg")) {
|
|
36840
|
-
try {
|
|
36841
|
-
const r = await toWav24kWithDuration(audioPath);
|
|
36842
|
-
audioPath = r.wavPath;
|
|
36843
|
-
voiceDurationSec = voiceDurationSec ?? r.durationSec;
|
|
36844
|
-
} catch (e) {
|
|
36845
|
-
console.warn(`[my-voice] toWav24k failed (${e.message}), sending raw file`);
|
|
36846
|
-
}
|
|
36847
|
-
}
|
|
36848
|
-
if (audioPath.endsWith(".wav")) {
|
|
36849
|
-
try {
|
|
36850
|
-
audioPath = await wavToOggOpus(audioPath);
|
|
36851
|
-
} catch (e) {
|
|
36852
|
-
console.warn(`[my-voice] ogg/opus encode failed (${e.message}), falling back to m4a`);
|
|
36853
|
-
audioPath = await compressWav(audioPath);
|
|
36854
|
-
}
|
|
36855
|
-
}
|
|
36856
|
-
const ext = path41.extname(audioPath).toLowerCase();
|
|
36857
|
-
const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg", ".silk": "audio/silk" };
|
|
36858
|
-
const mimeType = mimeMap[ext] || "audio/mpeg";
|
|
36859
|
-
const sizeKB = fs42.statSync(audioPath).size / 1024;
|
|
36860
|
-
try {
|
|
36861
|
-
await mgr.sendFile(resolvedChannel, target, caption, {
|
|
36862
|
-
path: audioPath,
|
|
36863
|
-
mimeType,
|
|
36864
|
-
filename: `voice_${Date.now()}${ext}`,
|
|
36865
|
-
...voiceDurationSec !== void 0 ? { voiceDurationSec } : {}
|
|
36866
|
-
});
|
|
36867
|
-
try {
|
|
36868
|
-
fs42.unlinkSync(audioPath);
|
|
36869
|
-
} catch {
|
|
36870
|
-
}
|
|
36871
|
-
return { content: `Voice sent! (${actualEngine}, ${sizeKB.toFixed(0)}KB, ${resolvedChannel})` };
|
|
36872
|
-
} catch (err) {
|
|
36873
|
-
return { content: `Voice send failed: ${err.message}`, isError: true };
|
|
36874
|
-
}
|
|
36875
|
-
},
|
|
36876
|
-
isConcurrencySafe: () => false,
|
|
36877
|
-
interruptBehavior: () => "block",
|
|
36878
|
-
isReadOnly: () => false,
|
|
36879
|
-
isDestructive: () => false
|
|
36880
|
-
});
|
|
36881
36520
|
|
|
36882
36521
|
// src/channels/desk-buddy.ts
|
|
36883
36522
|
init_live();
|
|
@@ -36894,23 +36533,23 @@ var DeskBuddyAdapter = class {
|
|
|
36894
36533
|
appendHistory(entry) {
|
|
36895
36534
|
try {
|
|
36896
36535
|
if (!this.historyFile) {
|
|
36897
|
-
const
|
|
36898
|
-
const
|
|
36899
|
-
const
|
|
36900
|
-
const dir =
|
|
36901
|
-
|
|
36902
|
-
this.historyFile =
|
|
36903
|
-
this._fs =
|
|
36904
|
-
}
|
|
36905
|
-
const
|
|
36536
|
+
const os7 = __require("os");
|
|
36537
|
+
const fs59 = __require("fs");
|
|
36538
|
+
const path60 = __require("path");
|
|
36539
|
+
const dir = path60.join(os7.homedir(), "xiaowen", "workspace", ".desktopBuddy");
|
|
36540
|
+
fs59.mkdirSync(dir, { recursive: true });
|
|
36541
|
+
this.historyFile = path60.join(dir, "history.json");
|
|
36542
|
+
this._fs = fs59;
|
|
36543
|
+
}
|
|
36544
|
+
const fs58 = this._fs;
|
|
36906
36545
|
let arr = [];
|
|
36907
36546
|
try {
|
|
36908
|
-
arr = JSON.parse(
|
|
36547
|
+
arr = JSON.parse(fs58.readFileSync(this.historyFile, "utf-8")) || [];
|
|
36909
36548
|
} catch {
|
|
36910
36549
|
}
|
|
36911
36550
|
arr.push({ ...entry, ts: (/* @__PURE__ */ new Date()).toLocaleString("zh-CN", { hour12: false }) });
|
|
36912
36551
|
if (arr.length > 500) arr = arr.slice(-500);
|
|
36913
|
-
|
|
36552
|
+
fs58.writeFileSync(this.historyFile, JSON.stringify(arr, null, 1));
|
|
36914
36553
|
} catch (e) {
|
|
36915
36554
|
console.warn("[deskBuddy] history write fail", e);
|
|
36916
36555
|
}
|
|
@@ -37006,8 +36645,8 @@ var DeskBuddyAdapter = class {
|
|
|
37006
36645
|
startAsr() {
|
|
37007
36646
|
if (this.asrSock) return;
|
|
37008
36647
|
const port = this.asrBackend === "sensevoice" ? 9790 : 9791;
|
|
37009
|
-
const
|
|
37010
|
-
const sock =
|
|
36648
|
+
const net4 = __require("node:net");
|
|
36649
|
+
const sock = net4.connect(port, "127.0.0.1");
|
|
37011
36650
|
sock.setNoDelay(true);
|
|
37012
36651
|
this.asrSock = sock;
|
|
37013
36652
|
this.asrBuf = "";
|
|
@@ -37068,9 +36707,9 @@ var DeskBuddyAdapter = class {
|
|
|
37068
36707
|
sock.on("error", (e) => {
|
|
37069
36708
|
console.warn(`[deskBuddy] ASR tcp error (${this.asrBackend}):`, e.message);
|
|
37070
36709
|
this.sendToClient({ type: "asr-text", text: "(ASR \u670D\u52A1\u6CA1\u5728\u8DD1\uFF0C\u6B63\u5728\u62C9\u8D77\u2026\u518D\u6309\u4E00\u6B21\u8BF4\u8BDD)" });
|
|
37071
|
-
const { exec:
|
|
36710
|
+
const { exec: exec3 } = __require("node:child_process");
|
|
37072
36711
|
const ctr = this.asrBackend === "sensevoice" ? "xiaowen-asr-svc" : "xw-asr-stream";
|
|
37073
|
-
|
|
36712
|
+
exec3(`docker start ${ctr} 2>/dev/null || true`);
|
|
37074
36713
|
this.asrSock = null;
|
|
37075
36714
|
});
|
|
37076
36715
|
}
|
|
@@ -37523,20 +37162,20 @@ var ChannelManager = class {
|
|
|
37523
37162
|
// src/session/session-manager.ts
|
|
37524
37163
|
init_types();
|
|
37525
37164
|
init_types();
|
|
37526
|
-
import * as
|
|
37527
|
-
import * as
|
|
37165
|
+
import * as path42 from "node:path";
|
|
37166
|
+
import * as fs42 from "node:fs";
|
|
37528
37167
|
import * as crypto6 from "node:crypto";
|
|
37529
37168
|
|
|
37530
37169
|
// src/session/reader.ts
|
|
37531
37170
|
init_attachments();
|
|
37532
|
-
import * as
|
|
37533
|
-
import * as
|
|
37171
|
+
import * as fs41 from "node:fs";
|
|
37172
|
+
import * as path41 from "node:path";
|
|
37534
37173
|
import * as readline2 from "node:readline";
|
|
37535
37174
|
var SKIP_PRECOMPACT_THRESHOLD = 5 * 1024 * 1024;
|
|
37536
37175
|
var TRANSCRIPT_READ_CHUNK_SIZE = 1024 * 1024;
|
|
37537
37176
|
var MAX_TRANSCRIPT_READ_BYTES = 50 * 1024 * 1024;
|
|
37538
37177
|
async function readSessionHeader(filePath) {
|
|
37539
|
-
const stream =
|
|
37178
|
+
const stream = fs41.createReadStream(filePath, { encoding: "utf-8" });
|
|
37540
37179
|
const rl = readline2.createInterface({ input: stream });
|
|
37541
37180
|
return new Promise((resolve12) => {
|
|
37542
37181
|
let resolved = false;
|
|
@@ -37567,7 +37206,7 @@ async function readSessionHeader(filePath) {
|
|
|
37567
37206
|
});
|
|
37568
37207
|
}
|
|
37569
37208
|
async function readLargeFilePostBoundary(filePath) {
|
|
37570
|
-
const stat4 =
|
|
37209
|
+
const stat4 = fs41.statSync(filePath);
|
|
37571
37210
|
const fileSize = stat4.size;
|
|
37572
37211
|
const boundaryMarker = '"compact_boundary"';
|
|
37573
37212
|
const CHUNK_SIZE = TRANSCRIPT_READ_CHUNK_SIZE;
|
|
@@ -37575,13 +37214,13 @@ async function readLargeFilePostBoundary(filePath) {
|
|
|
37575
37214
|
let outLen = 0;
|
|
37576
37215
|
let boundaryStartOffset = 0;
|
|
37577
37216
|
const chunk = Buffer.allocUnsafe(CHUNK_SIZE);
|
|
37578
|
-
const fd =
|
|
37217
|
+
const fd = fs41.openSync(filePath, "r");
|
|
37579
37218
|
try {
|
|
37580
37219
|
let filePos = 0;
|
|
37581
37220
|
let carryBuf;
|
|
37582
37221
|
let carryLen = 0;
|
|
37583
37222
|
while (filePos < fileSize) {
|
|
37584
|
-
const bytesRead =
|
|
37223
|
+
const bytesRead = fs41.readSync(fd, chunk, 0, Math.min(CHUNK_SIZE, fileSize - filePos), filePos);
|
|
37585
37224
|
if (bytesRead === 0) break;
|
|
37586
37225
|
filePos += bytesRead;
|
|
37587
37226
|
let scanBuf;
|
|
@@ -37633,7 +37272,7 @@ async function readLargeFilePostBoundary(filePath) {
|
|
|
37633
37272
|
}
|
|
37634
37273
|
}
|
|
37635
37274
|
} finally {
|
|
37636
|
-
|
|
37275
|
+
fs41.closeSync(fd);
|
|
37637
37276
|
}
|
|
37638
37277
|
const postBoundaryText = outBuf.subarray(0, outLen).toString("utf-8");
|
|
37639
37278
|
return postBoundaryText.split("\n").filter((l) => l.trim().length > 0);
|
|
@@ -37757,7 +37396,7 @@ function recoverOrphanedParallelToolResults(allEntries, chain, byUuid, seen) {
|
|
|
37757
37396
|
return result;
|
|
37758
37397
|
}
|
|
37759
37398
|
async function readSessionHistory(filePath) {
|
|
37760
|
-
const stat4 =
|
|
37399
|
+
const stat4 = fs41.statSync(filePath);
|
|
37761
37400
|
const fileSize = stat4.size;
|
|
37762
37401
|
if (fileSize > MAX_TRANSCRIPT_READ_BYTES) {
|
|
37763
37402
|
console.warn(`[reader] Session file exceeds 50MB limit (${(fileSize / 1024 / 1024).toFixed(1)}MB), using chunked fd read with boundary skip`);
|
|
@@ -37796,7 +37435,7 @@ async function readSessionHistory(filePath) {
|
|
|
37796
37435
|
return [...sessionStarts, ...filtered, ...restAttachments];
|
|
37797
37436
|
}
|
|
37798
37437
|
async function readAllLines(filePath) {
|
|
37799
|
-
const stream =
|
|
37438
|
+
const stream = fs41.createReadStream(filePath, { encoding: "utf-8" });
|
|
37800
37439
|
const rl = readline2.createInterface({ input: stream });
|
|
37801
37440
|
const lines = [];
|
|
37802
37441
|
return new Promise((resolve12, reject) => {
|
|
@@ -37950,12 +37589,12 @@ function extractText4(content) {
|
|
|
37950
37589
|
return content.filter((b) => b.type === "text").map((b) => b.text || "").join("") || null;
|
|
37951
37590
|
}
|
|
37952
37591
|
function listSessions(sessionsDir) {
|
|
37953
|
-
if (!
|
|
37954
|
-
const files =
|
|
37955
|
-
file:
|
|
37592
|
+
if (!fs41.existsSync(sessionsDir)) return [];
|
|
37593
|
+
const files = fs41.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") || f.includes(".jsonl.")).map((f) => ({
|
|
37594
|
+
file: path41.join(sessionsDir, f),
|
|
37956
37595
|
id: f.replace(/\.jsonl.*$/, ""),
|
|
37957
37596
|
// strip .jsonl and everything after (archived suffix)
|
|
37958
|
-
modified: new Date(
|
|
37597
|
+
modified: new Date(fs41.statSync(path41.join(sessionsDir, f)).mtimeMs)
|
|
37959
37598
|
})).sort((a, b) => b.modified.getTime() - a.modified.getTime());
|
|
37960
37599
|
return files;
|
|
37961
37600
|
}
|
|
@@ -37990,24 +37629,24 @@ var SessionManager = class {
|
|
|
37990
37629
|
constructor(config2) {
|
|
37991
37630
|
this.config = config2;
|
|
37992
37631
|
this.scopeConfig = config2.scopeConfig;
|
|
37993
|
-
this.sessionsDir =
|
|
37994
|
-
this.indexPath =
|
|
37995
|
-
this.platformMapPath =
|
|
37996
|
-
|
|
37632
|
+
this.sessionsDir = path42.join(config2.stateDir, "agents", "main", "sessions");
|
|
37633
|
+
this.indexPath = path42.join(this.sessionsDir, "session-index.json");
|
|
37634
|
+
this.platformMapPath = path42.join(this.sessionsDir, "platform-map.json");
|
|
37635
|
+
fs42.mkdirSync(this.sessionsDir, { recursive: true });
|
|
37997
37636
|
}
|
|
37998
37637
|
// === 索引操作 ===
|
|
37999
37638
|
loadIndex() {
|
|
38000
37639
|
try {
|
|
38001
|
-
if (
|
|
38002
|
-
const raw =
|
|
37640
|
+
if (fs42.existsSync(this.indexPath)) {
|
|
37641
|
+
const raw = fs42.readFileSync(this.indexPath, "utf-8");
|
|
38003
37642
|
const obj = JSON.parse(raw);
|
|
38004
37643
|
this.index = new Map(Object.entries(obj));
|
|
38005
37644
|
let fixed = 0;
|
|
38006
37645
|
for (const [key, entry] of this.index) {
|
|
38007
|
-
if (
|
|
38008
|
-
const basename10 =
|
|
38009
|
-
const localPath =
|
|
38010
|
-
if (
|
|
37646
|
+
if (fs42.existsSync(entry.file)) continue;
|
|
37647
|
+
const basename10 = path42.basename(entry.file);
|
|
37648
|
+
const localPath = path42.join(this.sessionsDir, basename10);
|
|
37649
|
+
if (fs42.existsSync(localPath)) {
|
|
38011
37650
|
entry.file = localPath;
|
|
38012
37651
|
fixed++;
|
|
38013
37652
|
}
|
|
@@ -38026,7 +37665,7 @@ var SessionManager = class {
|
|
|
38026
37665
|
try {
|
|
38027
37666
|
const obj = {};
|
|
38028
37667
|
for (const [k, v] of this.index) obj[k] = v;
|
|
38029
|
-
|
|
37668
|
+
fs42.writeFileSync(this.indexPath, JSON.stringify(obj, null, 2));
|
|
38030
37669
|
} catch (err) {
|
|
38031
37670
|
console.error(`[session] Failed to save index: ${err.message}`);
|
|
38032
37671
|
}
|
|
@@ -38034,8 +37673,8 @@ var SessionManager = class {
|
|
|
38034
37673
|
// === 平台映射 ===
|
|
38035
37674
|
loadPlatformMap() {
|
|
38036
37675
|
try {
|
|
38037
|
-
if (
|
|
38038
|
-
const raw =
|
|
37676
|
+
if (fs42.existsSync(this.platformMapPath)) {
|
|
37677
|
+
const raw = fs42.readFileSync(this.platformMapPath, "utf-8");
|
|
38039
37678
|
const obj = JSON.parse(raw);
|
|
38040
37679
|
this.platformMap = new Map(Object.entries(obj));
|
|
38041
37680
|
console.log(`[session] Platform map loaded: ${this.platformMap.size} entries`);
|
|
@@ -38047,7 +37686,7 @@ var SessionManager = class {
|
|
|
38047
37686
|
try {
|
|
38048
37687
|
const obj = {};
|
|
38049
37688
|
for (const [k, v] of this.platformMap) obj[k] = v;
|
|
38050
|
-
|
|
37689
|
+
fs42.writeFileSync(this.platformMapPath, JSON.stringify(obj, null, 2));
|
|
38051
37690
|
} catch (err) {
|
|
38052
37691
|
console.error(`[session] Failed to save platform map: ${err.message}`);
|
|
38053
37692
|
}
|
|
@@ -38086,17 +37725,17 @@ var SessionManager = class {
|
|
|
38086
37725
|
*/
|
|
38087
37726
|
getSessionFilePath(sessionId) {
|
|
38088
37727
|
const entry = this.index.get(sessionId);
|
|
38089
|
-
if (entry &&
|
|
38090
|
-
const direct =
|
|
38091
|
-
return
|
|
37728
|
+
if (entry && fs42.existsSync(entry.file)) return entry.file;
|
|
37729
|
+
const direct = path42.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
37730
|
+
return fs42.existsSync(direct) ? direct : null;
|
|
38092
37731
|
}
|
|
38093
37732
|
// === Writer 管理 ===
|
|
38094
37733
|
getWriter(sessionId) {
|
|
38095
37734
|
let writer = this.writers.get(sessionId);
|
|
38096
37735
|
if (!writer) {
|
|
38097
37736
|
const existing = this.index.get(sessionId);
|
|
38098
|
-
if (existing &&
|
|
38099
|
-
const existingFileId =
|
|
37737
|
+
if (existing && fs42.existsSync(existing.file)) {
|
|
37738
|
+
const existingFileId = path42.basename(existing.file, ".jsonl");
|
|
38100
37739
|
writer = new SessionWriter(this.sessionsDir, existingFileId);
|
|
38101
37740
|
writer.open(this.config.workspace, sessionId);
|
|
38102
37741
|
} else {
|
|
@@ -38108,7 +37747,7 @@ var SessionManager = class {
|
|
|
38108
37747
|
this.saveIndex();
|
|
38109
37748
|
this.writers.set(sessionId, writer);
|
|
38110
37749
|
this.onWriterCreated?.(writer, sessionId);
|
|
38111
|
-
console.log(`[session] ${existing ? "Resumed" : "New"}: ${sessionId} \u2192 ${
|
|
37750
|
+
console.log(`[session] ${existing ? "Resumed" : "New"}: ${sessionId} \u2192 ${path42.basename(writer.path)}`);
|
|
38112
37751
|
}
|
|
38113
37752
|
return writer;
|
|
38114
37753
|
}
|
|
@@ -38127,7 +37766,7 @@ var SessionManager = class {
|
|
|
38127
37766
|
if (!writer) return null;
|
|
38128
37767
|
try {
|
|
38129
37768
|
this.lastArchiveCheck = Date.now();
|
|
38130
|
-
const stat4 =
|
|
37769
|
+
const stat4 = fs42.statSync(writer.path);
|
|
38131
37770
|
if (stat4.size < threshold) return null;
|
|
38132
37771
|
const sizeMB = (stat4.size / 1024 / 1024).toFixed(1);
|
|
38133
37772
|
const thresholdMB = (threshold / 1024 / 1024).toFixed(1);
|
|
@@ -38136,10 +37775,10 @@ var SessionManager = class {
|
|
|
38136
37775
|
if (archivedPath) {
|
|
38137
37776
|
this.index.set(sessionId, { file: writer.path, updatedAt: Date.now() });
|
|
38138
37777
|
this.saveIndex();
|
|
38139
|
-
const newSize =
|
|
37778
|
+
const newSize = fs42.statSync(writer.path).size;
|
|
38140
37779
|
const oldSizeMB = (stat4.size / 1024 / 1024).toFixed(1);
|
|
38141
|
-
console.log(`[session] Archive complete: ${
|
|
38142
|
-
const fileId =
|
|
37780
|
+
console.log(`[session] Archive complete: ${path42.basename(archivedPath)} (${oldSizeMB}MB) \u2192 new JSONL created (${newSize} bytes)`);
|
|
37781
|
+
const fileId = path42.basename(writer.path).replace(/\.jsonl$/, "");
|
|
38143
37782
|
this.purgeOldArchives(fileId, 3);
|
|
38144
37783
|
return archivedPath;
|
|
38145
37784
|
}
|
|
@@ -38155,15 +37794,15 @@ var SessionManager = class {
|
|
|
38155
37794
|
purgeOldArchives(sessionId, keepCount = 3) {
|
|
38156
37795
|
try {
|
|
38157
37796
|
const dir = this.sessionsDir;
|
|
38158
|
-
if (!
|
|
38159
|
-
const allFiles =
|
|
37797
|
+
if (!fs42.existsSync(dir)) return;
|
|
37798
|
+
const allFiles = fs42.readdirSync(dir);
|
|
38160
37799
|
const archiveFiles = allFiles.filter((f) => {
|
|
38161
37800
|
if (!f.startsWith(sessionId)) return false;
|
|
38162
37801
|
return f.includes(".archived.") || f.includes(".compaction.");
|
|
38163
37802
|
}).map((f) => ({
|
|
38164
37803
|
name: f,
|
|
38165
|
-
path:
|
|
38166
|
-
mtime:
|
|
37804
|
+
path: path42.join(dir, f),
|
|
37805
|
+
mtime: fs42.statSync(path42.join(dir, f)).mtimeMs
|
|
38167
37806
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
38168
37807
|
console.log(`[session] purgeOldArchives: sessionId=${sessionId.substring(0, 8)}... found=${archiveFiles.length} keep=${keepCount}`);
|
|
38169
37808
|
if (archiveFiles.length <= keepCount) return;
|
|
@@ -38173,7 +37812,7 @@ var SessionManager = class {
|
|
|
38173
37812
|
console.log(`[session] Skipping archived (kept for history): ${f.name}`);
|
|
38174
37813
|
continue;
|
|
38175
37814
|
}
|
|
38176
|
-
|
|
37815
|
+
fs42.unlinkSync(f.path);
|
|
38177
37816
|
console.log(`[session] Purged old archive: ${f.name}`);
|
|
38178
37817
|
}
|
|
38179
37818
|
if (toDelete.length > 0) {
|
|
@@ -38222,29 +37861,29 @@ var SessionManager = class {
|
|
|
38222
37861
|
/** 列出 session 的所有文件(当前 + 归档),按修改时间排序 */
|
|
38223
37862
|
findAllSessionFiles(sessionId) {
|
|
38224
37863
|
const results = [];
|
|
38225
|
-
const mainFile =
|
|
38226
|
-
if (
|
|
37864
|
+
const mainFile = path42.join(this.sessionsDir, `${sessionId}.jsonl`);
|
|
37865
|
+
if (fs42.existsSync(mainFile)) {
|
|
38227
37866
|
results.push(mainFile);
|
|
38228
37867
|
}
|
|
38229
|
-
if (
|
|
38230
|
-
const compactionFiles =
|
|
37868
|
+
if (fs42.existsSync(this.sessionsDir)) {
|
|
37869
|
+
const compactionFiles = fs42.readdirSync(this.sessionsDir).filter((f) => f.startsWith(`${sessionId}.jsonl.compaction.`)).map((f) => ({
|
|
38231
37870
|
name: f,
|
|
38232
|
-
path:
|
|
38233
|
-
mtime:
|
|
37871
|
+
path: path42.join(this.sessionsDir, f),
|
|
37872
|
+
mtime: fs42.statSync(path42.join(this.sessionsDir, f)).mtimeMs
|
|
38234
37873
|
})).sort((a, b) => b.mtime - a.mtime);
|
|
38235
37874
|
if (compactionFiles.length > 0) {
|
|
38236
37875
|
results.push(compactionFiles[0].path);
|
|
38237
37876
|
}
|
|
38238
37877
|
}
|
|
38239
37878
|
results.sort((a, b) => {
|
|
38240
|
-
const ta =
|
|
38241
|
-
const tb =
|
|
37879
|
+
const ta = fs42.statSync(a).mtimeMs;
|
|
37880
|
+
const tb = fs42.statSync(b).mtimeMs;
|
|
38242
37881
|
return ta - tb;
|
|
38243
37882
|
});
|
|
38244
37883
|
return results;
|
|
38245
37884
|
}
|
|
38246
37885
|
async findSessionFiles(sessionId) {
|
|
38247
|
-
if (!
|
|
37886
|
+
if (!fs42.existsSync(this.sessionsDir)) {
|
|
38248
37887
|
console.log(`[session:find] SESSIONS_DIR not found: ${this.sessionsDir}`);
|
|
38249
37888
|
return [];
|
|
38250
37889
|
}
|
|
@@ -38256,15 +37895,15 @@ var SessionManager = class {
|
|
|
38256
37895
|
return mappedFiles;
|
|
38257
37896
|
}
|
|
38258
37897
|
const indexedEntry = this.index.get(mappedId);
|
|
38259
|
-
if (indexedEntry &&
|
|
38260
|
-
const fileUuid =
|
|
37898
|
+
if (indexedEntry && fs42.existsSync(indexedEntry.file)) {
|
|
37899
|
+
const fileUuid = path42.basename(indexedEntry.file, ".jsonl");
|
|
38261
37900
|
return this.findAllSessionFiles(fileUuid);
|
|
38262
37901
|
}
|
|
38263
37902
|
}
|
|
38264
37903
|
const allFiles = this.findAllSessionFiles(sessionId);
|
|
38265
37904
|
if (allFiles.length > 0) {
|
|
38266
37905
|
const labels = allFiles.map((f) => {
|
|
38267
|
-
const base =
|
|
37906
|
+
const base = path42.basename(f);
|
|
38268
37907
|
return base.endsWith(".jsonl") ? base : `${base} (archived)`;
|
|
38269
37908
|
});
|
|
38270
37909
|
console.log(`[session:find] Found ${allFiles.length} file(s) for ${sessionId}: ${labels.join(", ")}`);
|
|
@@ -38272,9 +37911,9 @@ var SessionManager = class {
|
|
|
38272
37911
|
}
|
|
38273
37912
|
const indexed = this.index.get(sessionId);
|
|
38274
37913
|
if (indexed) {
|
|
38275
|
-
console.log(`[session:find] Index hit: ${sessionId} \u2192 ${indexed.file} (exists=${
|
|
38276
|
-
if (
|
|
38277
|
-
return this.findAllSessionFiles(
|
|
37914
|
+
console.log(`[session:find] Index hit: ${sessionId} \u2192 ${indexed.file} (exists=${fs42.existsSync(indexed.file)})`);
|
|
37915
|
+
if (fs42.existsSync(indexed.file)) {
|
|
37916
|
+
return this.findAllSessionFiles(path42.basename(indexed.file, ".jsonl"));
|
|
38278
37917
|
}
|
|
38279
37918
|
} else {
|
|
38280
37919
|
console.log(`[session:find] Index miss: ${sessionId} (index has ${this.index.size} entries)`);
|
|
@@ -38303,8 +37942,8 @@ var SessionManager = class {
|
|
|
38303
37942
|
return [];
|
|
38304
37943
|
}
|
|
38305
37944
|
files.sort((a, b) => {
|
|
38306
|
-
const ta =
|
|
38307
|
-
const tb =
|
|
37945
|
+
const ta = fs42.statSync(a).mtimeMs;
|
|
37946
|
+
const tb = fs42.statSync(b).mtimeMs;
|
|
38308
37947
|
return ta - tb;
|
|
38309
37948
|
});
|
|
38310
37949
|
const latestFile = files[files.length - 1];
|
|
@@ -38312,7 +37951,7 @@ var SessionManager = class {
|
|
|
38312
37951
|
if (!currentEntry || currentEntry.file !== latestFile) {
|
|
38313
37952
|
this.index.set(sessionId, { file: latestFile, updatedAt: Date.now() });
|
|
38314
37953
|
this.saveIndex();
|
|
38315
|
-
console.log(`[session:restore] Synced index: ${sessionId} \u2192 ${
|
|
37954
|
+
console.log(`[session:restore] Synced index: ${sessionId} \u2192 ${path42.basename(latestFile)}`);
|
|
38316
37955
|
}
|
|
38317
37956
|
const allMessages = [];
|
|
38318
37957
|
for (const file2 of files) {
|
|
@@ -38351,7 +37990,7 @@ var SessionManager = class {
|
|
|
38351
37990
|
if (isAttachmentMessage(m) && m.attachment.type === "relevant_memories") {
|
|
38352
37991
|
for (const mem of m.attachment.memories) {
|
|
38353
37992
|
rawCount++;
|
|
38354
|
-
restoredPaths.add(
|
|
37993
|
+
restoredPaths.add(path42.resolve(mem.path));
|
|
38355
37994
|
}
|
|
38356
37995
|
}
|
|
38357
37996
|
}
|
|
@@ -38383,15 +38022,15 @@ var SessionManager = class {
|
|
|
38383
38022
|
*/
|
|
38384
38023
|
rotateMainSession() {
|
|
38385
38024
|
const entry = this.index.get("main");
|
|
38386
|
-
if (!entry || !
|
|
38387
|
-
const oldFileId =
|
|
38388
|
-
const stat4 =
|
|
38025
|
+
if (!entry || !fs42.existsSync(entry.file)) return;
|
|
38026
|
+
const oldFileId = path42.basename(entry.file, ".jsonl");
|
|
38027
|
+
const stat4 = fs42.statSync(entry.file);
|
|
38389
38028
|
if (stat4.size < 1024) return;
|
|
38390
38029
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-");
|
|
38391
38030
|
const archivedPath = `${entry.file}.archived.${ts}`;
|
|
38392
|
-
|
|
38031
|
+
fs42.renameSync(entry.file, archivedPath);
|
|
38393
38032
|
const sizeMB = (stat4.size / 1024 / 1024).toFixed(1);
|
|
38394
|
-
console.log(`[session] Archived: ${
|
|
38033
|
+
console.log(`[session] Archived: ${path42.basename(entry.file)} \u2192 ${path42.basename(archivedPath)} (${sizeMB}MB)`);
|
|
38395
38034
|
const newWriter = new SessionWriter(this.sessionsDir);
|
|
38396
38035
|
newWriter.open(this.config.workspace, "main");
|
|
38397
38036
|
newWriter.writeModelChange(this.config.providerId, this.config.model);
|
|
@@ -38400,7 +38039,7 @@ var SessionManager = class {
|
|
|
38400
38039
|
this.index.set("main", { file: newPath, updatedAt: Date.now() });
|
|
38401
38040
|
this.saveIndex();
|
|
38402
38041
|
this.purgeOldArchives(oldFileId, 3);
|
|
38403
|
-
console.log(`[session] Rotated main: ${oldFileId}.jsonl \u2192 ${
|
|
38042
|
+
console.log(`[session] Rotated main: ${oldFileId}.jsonl \u2192 ${path42.basename(newPath)}`);
|
|
38404
38043
|
}
|
|
38405
38044
|
// === 迁移 ===
|
|
38406
38045
|
/** 迁移旧索引:session-index.json 里有 "discord:xxx" 格式的 key 时,生成 UUID 替换 */
|
|
@@ -39292,12 +38931,12 @@ Examples:
|
|
|
39292
38931
|
// src/tools/msg-husband.ts
|
|
39293
38932
|
init_registry();
|
|
39294
38933
|
init_live();
|
|
39295
|
-
import
|
|
39296
|
-
import
|
|
38934
|
+
import fs43 from "node:fs";
|
|
38935
|
+
import path43 from "node:path";
|
|
39297
38936
|
function getHusbandFeishuId(workspace) {
|
|
39298
|
-
const contactsPath =
|
|
38937
|
+
const contactsPath = path43.join(workspace, "prompts", "contacts.md");
|
|
39299
38938
|
try {
|
|
39300
|
-
const text =
|
|
38939
|
+
const text = fs43.readFileSync(contactsPath, "utf-8");
|
|
39301
38940
|
const m = text.match(/\|\s*翀哥\s*\|\s*(ou_[a-f0-9]+)\s*\|/);
|
|
39302
38941
|
return m ? m[1] : null;
|
|
39303
38942
|
} catch {
|
|
@@ -39439,8 +39078,8 @@ Examples:
|
|
|
39439
39078
|
if (!to && !resolvedChannelId) {
|
|
39440
39079
|
return { content: "to \u548C channel_id \u4E0D\u80FD\u540C\u65F6\u4E3A\u7A7A\uFF0C\u4E14\u6CA1\u6709\u53EF\u7528\u7684\u6765\u6E90\u9891\u9053", isError: true };
|
|
39441
39080
|
}
|
|
39442
|
-
const
|
|
39443
|
-
if (!
|
|
39081
|
+
const fs58 = await import("node:fs");
|
|
39082
|
+
if (!fs58.existsSync(filePath)) {
|
|
39444
39083
|
return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6\u4E0D\u5B58\u5728 ${filePath}`, isError: true };
|
|
39445
39084
|
}
|
|
39446
39085
|
const toIds = to ? to.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
@@ -39468,7 +39107,7 @@ Examples:
|
|
|
39468
39107
|
md: "text/markdown"
|
|
39469
39108
|
};
|
|
39470
39109
|
const mimeType = mimeTypeMap[ext] || "application/octet-stream";
|
|
39471
|
-
const stat4 =
|
|
39110
|
+
const stat4 = fs58.statSync(filePath);
|
|
39472
39111
|
const sizeMB = stat4.size / 1024 / 1024;
|
|
39473
39112
|
if (sizeMB > 25) {
|
|
39474
39113
|
return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6 ${sizeMB.toFixed(1)}MB \u8D85\u8FC7 Discord 25MB \u9650\u5236`, isError: true };
|
|
@@ -39498,8 +39137,8 @@ Examples:
|
|
|
39498
39137
|
init_live();
|
|
39499
39138
|
init_registry();
|
|
39500
39139
|
init_well_formed();
|
|
39501
|
-
import * as
|
|
39502
|
-
import * as
|
|
39140
|
+
import * as fs44 from "node:fs";
|
|
39141
|
+
import * as path44 from "node:path";
|
|
39503
39142
|
var MIME_MAP = {
|
|
39504
39143
|
".jpg": "jpeg",
|
|
39505
39144
|
".jpeg": "jpeg",
|
|
@@ -39511,9 +39150,9 @@ var MIME_MAP = {
|
|
|
39511
39150
|
var VIDEO_EXT = /\.(mp4|mov)$/i;
|
|
39512
39151
|
var VIDEO_MAX_BYTES = 40 * 1024 * 1024;
|
|
39513
39152
|
function resolveLatestImage(specifiedPath, mediaDir) {
|
|
39514
|
-
if (specifiedPath &&
|
|
39515
|
-
if (!
|
|
39516
|
-
const files =
|
|
39153
|
+
if (specifiedPath && fs44.existsSync(specifiedPath)) return specifiedPath;
|
|
39154
|
+
if (!fs44.existsSync(mediaDir)) return null;
|
|
39155
|
+
const files = fs44.readdirSync(mediaDir).filter((f) => /\.(jpg|jpeg|png|webp|gif|bmp|mp4|mov)$/i.test(f)).map((f) => ({ name: f, p: path44.join(mediaDir, f), mtime: fs44.statSync(path44.join(mediaDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
39517
39156
|
return files[0]?.p || null;
|
|
39518
39157
|
}
|
|
39519
39158
|
registry.register({
|
|
@@ -39544,21 +39183,21 @@ registry.register({
|
|
|
39544
39183
|
if (!provider?.streamChat) {
|
|
39545
39184
|
return { content: "Error: provider \u4E0D\u53EF\u7528\u3002", isError: true };
|
|
39546
39185
|
}
|
|
39547
|
-
const mediaDir =
|
|
39186
|
+
const mediaDir = path44.join(ctx.stateDir, "media", "inbound");
|
|
39548
39187
|
const imagePath = resolveLatestImage(args2.image_path, mediaDir);
|
|
39549
39188
|
if (!imagePath) {
|
|
39550
39189
|
return { content: "Error: no image found. Provide image_path or ensure media/inbound has images.", isError: true };
|
|
39551
39190
|
}
|
|
39552
39191
|
const rawPrompt = args2.prompt?.trim() || "\u63CF\u8FF0\u8FD9\u5F20\u56FE\u7247\u7684\u5185\u5BB9";
|
|
39553
|
-
const ext =
|
|
39192
|
+
const ext = path44.extname(imagePath).toLowerCase();
|
|
39554
39193
|
if (VIDEO_EXT.test(imagePath)) {
|
|
39555
|
-
const stat4 =
|
|
39194
|
+
const stat4 = fs44.statSync(imagePath);
|
|
39556
39195
|
if (stat4.size > VIDEO_MAX_BYTES) {
|
|
39557
39196
|
return { content: `Error: video too large (${(stat4.size / 1e6).toFixed(1)}MB > 40MB). Compress or trim first.`, isError: true };
|
|
39558
39197
|
}
|
|
39559
39198
|
const explicit = liveConfig.get("tools.my_eyes.videoBaseUrl");
|
|
39560
39199
|
const baseUrl = explicit || providerCfg.baseUrl.replace(/\/anthropic\/?$/, "/v1").replace(/\/$/, "");
|
|
39561
|
-
const vidB64 =
|
|
39200
|
+
const vidB64 = fs44.readFileSync(imagePath).toString("base64");
|
|
39562
39201
|
try {
|
|
39563
39202
|
const res = await fetch(`${baseUrl}/chat/completions`, {
|
|
39564
39203
|
method: "POST",
|
|
@@ -39588,7 +39227,7 @@ registry.register({
|
|
|
39588
39227
|
}
|
|
39589
39228
|
}
|
|
39590
39229
|
const mime = MIME_MAP[ext] || "jpeg";
|
|
39591
|
-
const imgB64 =
|
|
39230
|
+
const imgB64 = fs44.readFileSync(imagePath).toString("base64");
|
|
39592
39231
|
const userMsg = {
|
|
39593
39232
|
role: "user",
|
|
39594
39233
|
content: [
|
|
@@ -39620,12 +39259,675 @@ registry.register({
|
|
|
39620
39259
|
}
|
|
39621
39260
|
});
|
|
39622
39261
|
|
|
39623
|
-
// src/tools/my-
|
|
39262
|
+
// src/tools/my-voice.ts
|
|
39624
39263
|
init_live();
|
|
39264
|
+
|
|
39265
|
+
// src/config/tts-providers.ts
|
|
39266
|
+
var TTS_PROVIDERS = [
|
|
39267
|
+
// relay:走 7yi 中转站的 /v1/audio/speech。
|
|
39268
|
+
// 后三个 billing* 键也在这支读——选池要用;漏了的话托盘切 provider 会把它们清掉。
|
|
39269
|
+
//
|
|
39270
|
+
// ⚠️ `voiceTier` 与 `fallback` 都在这一支读,且**都由引擎侧读、托盘没有输入框**
|
|
39271
|
+
// (见 config-editors.ts 的 VOICE_FORM_KEYS —— 托盘只建它有框的那几格)。
|
|
39272
|
+
// 它们出现在这里**不是为了给用户编辑**,而是为了让托盘的保存路径**别把它们删掉**:
|
|
39273
|
+
// mergeMyVoice 的 `keep` 来自这份清单(它决定"这一版 provider 会读哪些键"),
|
|
39274
|
+
// 而不在 keep 里的键会被整份替换下去 —— 漏了 `voiceTier` 就是"托盘一保存,
|
|
39275
|
+
// 计价档位静默消失"。这是本清单**唯一**的第二个用途,别把这两个键当成多余的。
|
|
39276
|
+
// · voiceTier → providers.ts:39 算 tier(route 用它选池、usage 用它记账;
|
|
39277
|
+
// spec §4.4 留的克隆音色计费钩子,今天没有生产者)
|
|
39278
|
+
// · fallback → my-voice.ts 主路径的回退链(spec §4.1,默认 ['edge'])
|
|
39279
|
+
{ id: "relay", requiredKeys: [
|
|
39280
|
+
"pythonPath",
|
|
39281
|
+
"relayProviderId",
|
|
39282
|
+
"relayModel",
|
|
39283
|
+
"voice",
|
|
39284
|
+
"billingProviderId",
|
|
39285
|
+
"billingModel",
|
|
39286
|
+
"billingMode",
|
|
39287
|
+
"voiceTier",
|
|
39288
|
+
"fallback"
|
|
39289
|
+
] },
|
|
39290
|
+
{ id: "cosyvoice", requiredKeys: ["pythonPath", "apiKey", "model", "voice", "workspaceId", "instruction"] },
|
|
39291
|
+
// gptsovits:四个 gptsovits* 键是 Task 9 从硬编码改过来的(原来是 GPTSOVITS_API / REF_WAV /
|
|
39292
|
+
// REF_TEXT 写死在 my-voice.ts:303-306,那个 REF_WAV 是翀哥 Linux 机器上的路径,所有用户都会去找它)
|
|
39293
|
+
{ id: "gptsovits", requiredKeys: [
|
|
39294
|
+
"pythonPath",
|
|
39295
|
+
"gptsovitsUrl",
|
|
39296
|
+
"gptsovitsRefWav",
|
|
39297
|
+
"gptsovitsRefText",
|
|
39298
|
+
"gptsovitsRefLang"
|
|
39299
|
+
] },
|
|
39300
|
+
{ id: "qwen3-3060", requiredKeys: ["pythonPath", "url", "voice", "lang"] },
|
|
39301
|
+
{ id: "edge", requiredKeys: ["pythonPath"] }
|
|
39302
|
+
];
|
|
39303
|
+
|
|
39304
|
+
// src/tools/my-voice.ts
|
|
39625
39305
|
init_registry();
|
|
39626
|
-
|
|
39306
|
+
|
|
39307
|
+
// src/tools/opus-encoder.ts
|
|
39308
|
+
import * as fs45 from "node:fs";
|
|
39309
|
+
var FRAME_MS = 20;
|
|
39310
|
+
var PRESKIP_48K = 312;
|
|
39311
|
+
var OPUS_RATE = 48e3;
|
|
39312
|
+
var NATIVE_RATES = [8e3, 12e3, 16e3, 24e3, 48e3];
|
|
39313
|
+
var BITRATE = 32e3;
|
|
39314
|
+
var CRC_TABLE = (() => {
|
|
39315
|
+
const t = new Uint32Array(256);
|
|
39316
|
+
for (let i = 0; i < 256; i++) {
|
|
39317
|
+
let r = i << 24;
|
|
39318
|
+
for (let k = 0; k < 8; k++) r = r & 2147483648 ? r << 1 ^ 79764919 : r << 1;
|
|
39319
|
+
t[i] = r >>> 0;
|
|
39320
|
+
}
|
|
39321
|
+
return t;
|
|
39322
|
+
})();
|
|
39323
|
+
function oggCrc32(buf) {
|
|
39324
|
+
let crc = 0;
|
|
39325
|
+
for (let i = 0; i < buf.length; i++) {
|
|
39326
|
+
crc = (crc << 8 ^ CRC_TABLE[(crc >>> 24 ^ buf[i]) & 255]) >>> 0;
|
|
39327
|
+
}
|
|
39328
|
+
return crc >>> 0;
|
|
39329
|
+
}
|
|
39330
|
+
var OggWriter = class {
|
|
39331
|
+
serial;
|
|
39332
|
+
seq = 0;
|
|
39333
|
+
pages = [];
|
|
39334
|
+
constructor() {
|
|
39335
|
+
this.serial = Math.random() * 4294967296 >>> 0;
|
|
39336
|
+
}
|
|
39337
|
+
/** 组一页。packets 为完整数据包;granule = 该页最后一包的 48k 域样本位置。 */
|
|
39338
|
+
page(packets, granule, bos = false, eos = false) {
|
|
39339
|
+
const lacing = [];
|
|
39340
|
+
for (const p of packets) {
|
|
39341
|
+
let rem = p.length;
|
|
39342
|
+
while (rem >= 255) {
|
|
39343
|
+
lacing.push(255);
|
|
39344
|
+
rem -= 255;
|
|
39345
|
+
}
|
|
39346
|
+
lacing.push(rem);
|
|
39347
|
+
}
|
|
39348
|
+
const payloadLen = packets.reduce((n, p) => n + p.length, 0);
|
|
39349
|
+
const page = Buffer.alloc(27 + lacing.length + payloadLen);
|
|
39350
|
+
page.write("OggS", 0, "ascii");
|
|
39351
|
+
page[4] = 0;
|
|
39352
|
+
page[5] = (bos ? 2 : 0) | (eos ? 1 : 0);
|
|
39353
|
+
page.writeBigUInt64LE(BigInt(granule), 6);
|
|
39354
|
+
page.writeUInt32LE(this.serial, 14);
|
|
39355
|
+
page.writeUInt32LE(this.seq++, 18);
|
|
39356
|
+
page.writeUInt32LE(0, 22);
|
|
39357
|
+
page[26] = lacing.length;
|
|
39358
|
+
lacing.forEach((v, i) => {
|
|
39359
|
+
page[27 + i] = v;
|
|
39360
|
+
});
|
|
39361
|
+
let off = 27 + lacing.length;
|
|
39362
|
+
for (const p of packets) {
|
|
39363
|
+
p.copy(page, off);
|
|
39364
|
+
off += p.length;
|
|
39365
|
+
}
|
|
39366
|
+
page.writeUInt32LE(oggCrc32(page), 22);
|
|
39367
|
+
this.pages.push(page);
|
|
39368
|
+
}
|
|
39369
|
+
finish() {
|
|
39370
|
+
return Buffer.concat(this.pages);
|
|
39371
|
+
}
|
|
39372
|
+
};
|
|
39373
|
+
function opusHeadPacket(channels, inputRate) {
|
|
39374
|
+
const b = Buffer.alloc(19);
|
|
39375
|
+
b.write("OpusHead", 0, "ascii");
|
|
39376
|
+
b[8] = 1;
|
|
39377
|
+
b[9] = channels;
|
|
39378
|
+
b.writeUInt16LE(PRESKIP_48K, 10);
|
|
39379
|
+
b.writeUInt32LE(inputRate, 12);
|
|
39380
|
+
b.writeInt16LE(0, 16);
|
|
39381
|
+
b[18] = 0;
|
|
39382
|
+
return b;
|
|
39383
|
+
}
|
|
39384
|
+
function opusTagsPacket() {
|
|
39385
|
+
const vendor = Buffer.from("engine7-native-opus", "utf8");
|
|
39386
|
+
const b = Buffer.alloc(8 + 4 + vendor.length + 4);
|
|
39387
|
+
b.write("OpusTags", 0, "ascii");
|
|
39388
|
+
b.writeUInt32LE(vendor.length, 8);
|
|
39389
|
+
vendor.copy(b, 12);
|
|
39390
|
+
b.writeUInt32LE(0, 12 + vendor.length);
|
|
39391
|
+
return b;
|
|
39392
|
+
}
|
|
39393
|
+
function parseWav(buf) {
|
|
39394
|
+
if (buf.length < 44 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
39395
|
+
throw new Error("not a RIFF/WAVE file");
|
|
39396
|
+
}
|
|
39397
|
+
let off = 12;
|
|
39398
|
+
let fmt = null;
|
|
39399
|
+
let pcm = null;
|
|
39400
|
+
while (off + 8 <= buf.length) {
|
|
39401
|
+
const id = buf.toString("ascii", off, off + 4);
|
|
39402
|
+
const size = buf.readUInt32LE(off + 4);
|
|
39403
|
+
const body = off + 8;
|
|
39404
|
+
if (id === "fmt " && body + 16 <= buf.length) {
|
|
39405
|
+
fmt = {
|
|
39406
|
+
format: buf.readUInt16LE(body),
|
|
39407
|
+
ch: buf.readUInt16LE(body + 2),
|
|
39408
|
+
rate: buf.readUInt32LE(body + 4),
|
|
39409
|
+
bits: buf.readUInt16LE(body + 14)
|
|
39410
|
+
};
|
|
39411
|
+
} else if (id === "data") {
|
|
39412
|
+
pcm = buf.subarray(body, body + Math.min(size, buf.length - body));
|
|
39413
|
+
}
|
|
39414
|
+
off = body + size + (size & 1);
|
|
39415
|
+
}
|
|
39416
|
+
if (!fmt || !pcm) throw new Error("missing fmt/data chunk");
|
|
39417
|
+
if (fmt.format !== 1) throw new Error(`unsupported wav format ${fmt.format} (need PCM)`);
|
|
39418
|
+
if (fmt.bits !== 16) throw new Error(`unsupported ${fmt.bits}-bit (need s16le)`);
|
|
39419
|
+
return { sampleRate: fmt.rate, channels: fmt.ch, bits: fmt.bits, pcm };
|
|
39420
|
+
}
|
|
39421
|
+
async function wavToOggOpusNative(wavPath) {
|
|
39422
|
+
const mod = await import("@discordjs/opus");
|
|
39423
|
+
const OpusEncoder = mod.OpusEncoder ?? mod.default?.OpusEncoder;
|
|
39424
|
+
if (!OpusEncoder) throw new Error("@discordjs/opus export shape unexpected");
|
|
39425
|
+
const wav = parseWav(fs45.readFileSync(wavPath));
|
|
39426
|
+
if (wav.channels !== 1) throw new Error(`unsupported ${wav.channels}ch (need mono)`);
|
|
39427
|
+
if (!NATIVE_RATES.includes(wav.sampleRate)) throw new Error(`unsupported rate ${wav.sampleRate}`);
|
|
39428
|
+
if (wav.pcm.length === 0) throw new Error("empty pcm data");
|
|
39429
|
+
const encoder = new OpusEncoder(wav.sampleRate, wav.channels);
|
|
39430
|
+
encoder.setBitrate(BITRATE);
|
|
39431
|
+
const samplesPerFrame = Math.round(wav.sampleRate * FRAME_MS / 1e3);
|
|
39432
|
+
const bytesPerFrame = samplesPerFrame * 2;
|
|
39433
|
+
const frames = Math.ceil(wav.pcm.length / bytesPerFrame);
|
|
39434
|
+
const padded = Buffer.alloc(frames * bytesPerFrame);
|
|
39435
|
+
wav.pcm.copy(padded);
|
|
39436
|
+
const ogg = new OggWriter();
|
|
39437
|
+
ogg.page([opusHeadPacket(wav.channels, wav.sampleRate)], 0, true);
|
|
39438
|
+
ogg.page([opusTagsPacket()], 0);
|
|
39439
|
+
let cur = [];
|
|
39440
|
+
let curSegs = 0;
|
|
39441
|
+
let curBytes = 0;
|
|
39442
|
+
let pageGranule = 0;
|
|
39443
|
+
let pos48k = PRESKIP_48K;
|
|
39444
|
+
for (let f = 0; f < frames; f++) {
|
|
39445
|
+
const packet = encoder.encode(padded.subarray(f * bytesPerFrame, (f + 1) * bytesPerFrame));
|
|
39446
|
+
const pSegs = Math.floor((packet.length - 1) / 255) + 1;
|
|
39447
|
+
if (curSegs + pSegs > 255 || curBytes + packet.length > 6e4) {
|
|
39448
|
+
ogg.page(cur, pageGranule);
|
|
39449
|
+
cur = [];
|
|
39450
|
+
curSegs = 0;
|
|
39451
|
+
curBytes = 0;
|
|
39452
|
+
}
|
|
39453
|
+
cur.push(packet);
|
|
39454
|
+
curSegs += pSegs;
|
|
39455
|
+
curBytes += packet.length;
|
|
39456
|
+
pos48k += samplesPerFrame * (OPUS_RATE / wav.sampleRate);
|
|
39457
|
+
pageGranule = pos48k;
|
|
39458
|
+
}
|
|
39459
|
+
ogg.page(cur, pageGranule, false, true);
|
|
39460
|
+
const oggPath = wavPath.replace(/\.wav$/, ".ogg");
|
|
39461
|
+
fs45.writeFileSync(oggPath, ogg.finish());
|
|
39462
|
+
return oggPath;
|
|
39463
|
+
}
|
|
39464
|
+
|
|
39465
|
+
// src/tools/tts/pick.ts
|
|
39466
|
+
var KNOWN = new Set(TTS_PROVIDERS.map((p) => p.id));
|
|
39467
|
+
function pickProvider(requested, cfgProvider) {
|
|
39468
|
+
const req = typeof requested === "string" ? requested.trim() : "";
|
|
39469
|
+
const cfg = typeof cfgProvider === "string" ? cfgProvider.trim() : "";
|
|
39470
|
+
const name = cfg || req;
|
|
39471
|
+
if (KNOWN.has(name)) return { id: name };
|
|
39472
|
+
return { id: "edge" };
|
|
39473
|
+
}
|
|
39474
|
+
function fallbackChainOf(raw) {
|
|
39475
|
+
const list2 = Array.isArray(raw) ? raw : ["edge"];
|
|
39476
|
+
const out = [];
|
|
39477
|
+
for (const item of list2) {
|
|
39478
|
+
if (typeof item !== "string") continue;
|
|
39479
|
+
const id = item.trim();
|
|
39480
|
+
if (!KNOWN.has(id) || out.includes(id)) continue;
|
|
39481
|
+
out.push(id);
|
|
39482
|
+
}
|
|
39483
|
+
return out;
|
|
39484
|
+
}
|
|
39485
|
+
|
|
39486
|
+
// src/tools/tts/providers.ts
|
|
39487
|
+
init_live();
|
|
39488
|
+
|
|
39489
|
+
// src/tools/tts/relay.ts
|
|
39490
|
+
import * as fs46 from "node:fs";
|
|
39491
|
+
import * as os6 from "node:os";
|
|
39492
|
+
import * as path45 from "node:path";
|
|
39493
|
+
var VOICE_DIR2 = path45.join(os6.tmpdir(), "engine-voice");
|
|
39494
|
+
var fetchFn = fetch;
|
|
39495
|
+
async function synthRelay(text, cfg) {
|
|
39496
|
+
const res = await fetchFn(`${cfg.baseUrl}/audio/speech`, {
|
|
39497
|
+
method: "POST",
|
|
39498
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${cfg.apiKey}` },
|
|
39499
|
+
body: JSON.stringify({
|
|
39500
|
+
model: cfg.model,
|
|
39501
|
+
input: text,
|
|
39502
|
+
voice: cfg.voice,
|
|
39503
|
+
response_format: "mp3"
|
|
39504
|
+
})
|
|
39505
|
+
});
|
|
39506
|
+
if (!res.ok) {
|
|
39507
|
+
const body = await res.text().catch(() => "");
|
|
39508
|
+
throw new Error(`relay tts http ${res.status}: ${body.slice(0, 300)}`);
|
|
39509
|
+
}
|
|
39510
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
39511
|
+
if (buf.length < 20) throw new Error(`relay tts: \u54CD\u5E94\u4F53\u53EA\u6709 ${buf.length} \u5B57\u8282\uFF0C\u4E0D\u662F\u6709\u6548\u97F3\u9891`);
|
|
39512
|
+
fs46.mkdirSync(VOICE_DIR2, { recursive: true });
|
|
39513
|
+
const out = path45.join(VOICE_DIR2, `tts_relay_${Date.now()}.mp3`);
|
|
39514
|
+
fs46.writeFileSync(out, buf);
|
|
39515
|
+
return {
|
|
39516
|
+
path: out,
|
|
39517
|
+
format: "mp3",
|
|
39518
|
+
unit: { kind: "chars", amount: [...text].length },
|
|
39519
|
+
model: cfg.model,
|
|
39520
|
+
voice: cfg.voice
|
|
39521
|
+
};
|
|
39522
|
+
}
|
|
39523
|
+
|
|
39524
|
+
// src/tools/tts/route.ts
|
|
39525
|
+
init_loader();
|
|
39526
|
+
function providerOf(cfg, id) {
|
|
39527
|
+
const p = cfg?.models?.providers?.[id];
|
|
39528
|
+
const baseUrl = String(p?.baseUrl || "").replace(/\/+$/, "");
|
|
39529
|
+
const apiKey = String(p?.apiKey || "");
|
|
39530
|
+
if (!baseUrl || !apiKey) return null;
|
|
39531
|
+
if (isUnresolvedEnvRef(apiKey)) return null;
|
|
39532
|
+
return { baseUrl, apiKey };
|
|
39533
|
+
}
|
|
39534
|
+
function resolveTtsRoute(cfg, tier) {
|
|
39535
|
+
const mv = cfg?.tools?.my_voice || {};
|
|
39536
|
+
const mode = String(mv.billingMode || "off");
|
|
39537
|
+
const billingId = String(mv.billingProviderId || "").trim();
|
|
39538
|
+
const voice = String(mv.voice || "");
|
|
39539
|
+
const freePool = (warning) => {
|
|
39540
|
+
const free = providerOf(cfg, String(mv.relayProviderId || "7yi"));
|
|
39541
|
+
if (!free) {
|
|
39542
|
+
throw new TtsConfigError(
|
|
39543
|
+
`\u514D\u8D39\u6C60\u672A\u914D\u7F6E\uFF1Amodels.providers.${mv.relayProviderId || "7yi"} \u7F3A baseUrl \u6216 apiKey\u3002\u514D\u8D39\u6863\u88C5\u673A\u5E94\u5DF2\u81EA\u52A8\u914D\u597D\uFF0C\u53EF\u8DD1 \`engine7 reconfig llm --free\` \u8865\u3002`
|
|
39544
|
+
);
|
|
39545
|
+
}
|
|
39546
|
+
return {
|
|
39547
|
+
pool: "free",
|
|
39548
|
+
...free,
|
|
39549
|
+
model: String(mv.relayModel || "engine7-tts"),
|
|
39550
|
+
voice,
|
|
39551
|
+
...warning ? { warning } : {}
|
|
39552
|
+
};
|
|
39553
|
+
};
|
|
39554
|
+
const knownMode = mode === "off" || mode === "custom-only" || mode === "all";
|
|
39555
|
+
const modeWarning = knownMode ? "" : `billingMode=${mode} \u4E0D\u5728 off / custom-only / all \u91CC\u2014\u2014\u8FD9\u4E2A\u540D\u5B57\u6CA1\u4EBA\u8BA4\u8BC6\uFF0C\u6309\u300C\u672A\u542F\u7528\u300D\u5904\u7406\uFF08\u8D70\u514D\u8D39\u6C60\uFF09\u3002\u591A\u534A\u662F\u624B\u6253\u9519\u4E86\uFF08\u6BD4\u5982 customOnly \u5C11\u4E86\u8FDE\u5B57\u7B26\uFF09\u3002`;
|
|
39556
|
+
const wantBilling = knownMode && (mode === "all" || mode === "custom-only" && tier === "custom");
|
|
39557
|
+
if (!billingId) {
|
|
39558
|
+
return freePool(modeWarning || (wantBilling ? `billingMode=${mode} \u58F0\u79F0\u8981\u8D70\u8BA1\u8D39\u6C60\uFF0C\u4F46 billingProviderId \u662F\u7A7A\u7684\u2014\u2014\u4ECD\u8D70\u514D\u8D39\u6C60\u3002` : ""));
|
|
39559
|
+
}
|
|
39560
|
+
if (!wantBilling) return freePool(modeWarning);
|
|
39561
|
+
const bill = providerOf(cfg, billingId);
|
|
39562
|
+
if (!bill) {
|
|
39563
|
+
throw new TtsConfigError(
|
|
39564
|
+
`\u8BA1\u8D39\u6C60\u5DF2\u542F\u7528\u4F46\u4E0D\u53EF\u7528\uFF1Amodels.providers.${billingId} \u7F3A baseUrl \u6216 apiKey\u3002\u8FD9\u6761**\u4E0D\u4F1A**\u81EA\u52A8\u56DE\u843D\u514D\u8D39\u6C60\u2014\u2014\u4ED8\u8D39\u529F\u80FD\u9759\u9ED8\u767D\u9001\u6BD4\u62A5\u9519\u5371\u9669\u5F97\u591A\u3002\u8BF7\u8865\u914D\u7F6E\u6216\u628A billingMode \u6539\u56DE off\u3002`
|
|
39565
|
+
);
|
|
39566
|
+
}
|
|
39567
|
+
return { pool: "billing", ...bill, model: String(mv.billingModel || mv.relayModel || "engine7-tts"), voice };
|
|
39568
|
+
}
|
|
39569
|
+
|
|
39570
|
+
// src/tools/tts/providers.ts
|
|
39571
|
+
var TTS_IMPL = {
|
|
39572
|
+
relay: {
|
|
39573
|
+
supportsStreaming: false,
|
|
39574
|
+
synthesize: async (text) => {
|
|
39575
|
+
const all = liveConfig.all();
|
|
39576
|
+
const mv = all?.tools?.my_voice || {};
|
|
39577
|
+
const tier = mv.voiceTier === "custom" ? "custom" : "official";
|
|
39578
|
+
const r = resolveTtsRoute(all, tier);
|
|
39579
|
+
if (r.warning) console.warn(`[my-voice] ${r.warning}`);
|
|
39580
|
+
if (!r.voice) {
|
|
39581
|
+
throw new TtsConfigError(
|
|
39582
|
+
"relay TTS \u7F3A\u97F3\u8272\uFF1Atools.my_voice.voice \u662F\u7A7A\u7684\uFF0C\u4E0A\u6E38\u4F1A\u62D2\u3002\u514D\u8D39\u6863\u88C5\u673A\u5E94\u5DF2\u7531 applyFreeTier \u586B\u597D\u9ED8\u8BA4\u97F3\u8272\u3002"
|
|
39583
|
+
);
|
|
39584
|
+
}
|
|
39585
|
+
try {
|
|
39586
|
+
const res = await synthRelay(text, {
|
|
39587
|
+
baseUrl: r.baseUrl,
|
|
39588
|
+
apiKey: r.apiKey,
|
|
39589
|
+
model: r.model,
|
|
39590
|
+
voice: r.voice
|
|
39591
|
+
});
|
|
39592
|
+
noteTtsUsage({
|
|
39593
|
+
provider: "relay",
|
|
39594
|
+
model: r.model,
|
|
39595
|
+
voice: r.voice,
|
|
39596
|
+
tier,
|
|
39597
|
+
pool: r.pool,
|
|
39598
|
+
unit: res.unit,
|
|
39599
|
+
ok: true,
|
|
39600
|
+
at: Date.now()
|
|
39601
|
+
});
|
|
39602
|
+
return res;
|
|
39603
|
+
} catch (e) {
|
|
39604
|
+
noteTtsUsage({
|
|
39605
|
+
provider: "relay",
|
|
39606
|
+
model: r.model,
|
|
39607
|
+
voice: r.voice,
|
|
39608
|
+
tier,
|
|
39609
|
+
pool: r.pool,
|
|
39610
|
+
unit: { kind: "chars", amount: 0 },
|
|
39611
|
+
ok: false,
|
|
39612
|
+
at: Date.now()
|
|
39613
|
+
});
|
|
39614
|
+
throw e;
|
|
39615
|
+
}
|
|
39616
|
+
}
|
|
39617
|
+
},
|
|
39618
|
+
cosyvoice: { supportsStreaming: true, synthesize: synthCosyvoice },
|
|
39619
|
+
gptsovits: { supportsStreaming: false, synthesize: synthGptsovits },
|
|
39620
|
+
"qwen3-3060": { supportsStreaming: true, synthesize: synthQwen3 },
|
|
39621
|
+
edge: { supportsStreaming: false, synthesize: synthEdge }
|
|
39622
|
+
};
|
|
39623
|
+
var overrides = {};
|
|
39624
|
+
function synthesize(id, text, sink) {
|
|
39625
|
+
const impl = overrides[id] ?? TTS_IMPL[id];
|
|
39626
|
+
if (!impl) throw new Error(`TTS provider \u6CA1\u6709\u5B9E\u73B0\uFF1A${id}`);
|
|
39627
|
+
return impl.synthesize(text, sink);
|
|
39628
|
+
}
|
|
39629
|
+
|
|
39630
|
+
// src/tools/my-voice.ts
|
|
39631
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
39632
|
+
import { promisify as promisify3 } from "node:util";
|
|
39627
39633
|
import * as fs47 from "node:fs";
|
|
39628
39634
|
import * as path46 from "node:path";
|
|
39635
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
39636
|
+
async function toWav24kWithDuration(audioPath) {
|
|
39637
|
+
let wavPath = audioPath;
|
|
39638
|
+
if (!audioPath.endsWith(".wav")) {
|
|
39639
|
+
wavPath = audioPath.replace(/\.[^.]+$/, ".wav");
|
|
39640
|
+
await execFileAsync3("ffmpeg", [
|
|
39641
|
+
"-y",
|
|
39642
|
+
"-i",
|
|
39643
|
+
audioPath,
|
|
39644
|
+
"-ar",
|
|
39645
|
+
"24000",
|
|
39646
|
+
"-ac",
|
|
39647
|
+
"1",
|
|
39648
|
+
"-c:a",
|
|
39649
|
+
"pcm_s16le",
|
|
39650
|
+
wavPath
|
|
39651
|
+
], { timeout: 3e4 });
|
|
39652
|
+
if (!fs47.existsSync(wavPath) || fs47.statSync(wavPath).size < 44) {
|
|
39653
|
+
throw new Error("toWav24k produced empty output");
|
|
39654
|
+
}
|
|
39655
|
+
try {
|
|
39656
|
+
fs47.unlinkSync(audioPath);
|
|
39657
|
+
} catch {
|
|
39658
|
+
}
|
|
39659
|
+
}
|
|
39660
|
+
const durationSec = wavDurationSec(fs47.readFileSync(wavPath));
|
|
39661
|
+
return { wavPath, durationSec };
|
|
39662
|
+
}
|
|
39663
|
+
async function compressWav(wavPath) {
|
|
39664
|
+
const m4aPath = wavPath.replace(/\.wav$/, ".m4a");
|
|
39665
|
+
try {
|
|
39666
|
+
await execFileAsync3("ffmpeg", [
|
|
39667
|
+
"-y",
|
|
39668
|
+
"-i",
|
|
39669
|
+
wavPath,
|
|
39670
|
+
"-c:a",
|
|
39671
|
+
"aac",
|
|
39672
|
+
"-b:a",
|
|
39673
|
+
"64k",
|
|
39674
|
+
"-movflags",
|
|
39675
|
+
"+faststart",
|
|
39676
|
+
m4aPath
|
|
39677
|
+
], { timeout: 3e4 });
|
|
39678
|
+
fs47.unlinkSync(wavPath);
|
|
39679
|
+
return m4aPath;
|
|
39680
|
+
} catch {
|
|
39681
|
+
return wavPath;
|
|
39682
|
+
}
|
|
39683
|
+
}
|
|
39684
|
+
async function wavToOggOpus(wavPath) {
|
|
39685
|
+
let oggPath;
|
|
39686
|
+
try {
|
|
39687
|
+
oggPath = await wavToOggOpusNative(wavPath);
|
|
39688
|
+
} catch (e) {
|
|
39689
|
+
if (globalThis.__ffmpegMissing) throw e;
|
|
39690
|
+
console.warn(`[my-voice] native opus encode failed (${e.message}), trying ffmpeg`);
|
|
39691
|
+
oggPath = wavPath.replace(/\.wav$/, ".ogg");
|
|
39692
|
+
await execFileAsync3("ffmpeg", [
|
|
39693
|
+
"-y",
|
|
39694
|
+
"-i",
|
|
39695
|
+
wavPath,
|
|
39696
|
+
"-c:a",
|
|
39697
|
+
"libopus",
|
|
39698
|
+
"-b:a",
|
|
39699
|
+
"32k",
|
|
39700
|
+
"-ar",
|
|
39701
|
+
"24000",
|
|
39702
|
+
oggPath
|
|
39703
|
+
], { timeout: 3e4 });
|
|
39704
|
+
}
|
|
39705
|
+
if (!fs47.existsSync(oggPath) || fs47.statSync(oggPath).size < 20) {
|
|
39706
|
+
throw new Error("ogg/opus encode produced empty output");
|
|
39707
|
+
}
|
|
39708
|
+
fs47.unlinkSync(wavPath);
|
|
39709
|
+
return oggPath;
|
|
39710
|
+
}
|
|
39711
|
+
async function sendVoiceToMulticaIssue(text, issueId, opts) {
|
|
39712
|
+
const vc = liveConfig.get("tools.my_voice");
|
|
39713
|
+
let audioPath = "";
|
|
39714
|
+
let actualEngine;
|
|
39715
|
+
const picked = pickProvider(opts.engine, vc?.provider);
|
|
39716
|
+
actualEngine = picked.id;
|
|
39717
|
+
try {
|
|
39718
|
+
const r = await synthesize(picked.id, text, {});
|
|
39719
|
+
audioPath = r.path;
|
|
39720
|
+
} catch (e) {
|
|
39721
|
+
return { content: `\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
|
|
39722
|
+
}
|
|
39723
|
+
if (!audioPath || !fs47.existsSync(audioPath)) {
|
|
39724
|
+
return { content: "\u8BED\u97F3\u751F\u6210\u5931\u8D25\uFF08headless\u2192multaca\uFF09\uFF1ATTS \u672A\u4EA7\u51FA\u6587\u4EF6\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002", isError: true };
|
|
39725
|
+
}
|
|
39726
|
+
if (audioPath.endsWith(".wav")) {
|
|
39727
|
+
try {
|
|
39728
|
+
audioPath = await wavToOggOpus(audioPath);
|
|
39729
|
+
} catch (e) {
|
|
39730
|
+
console.warn(`[my-voice:multaca] opus encode failed (${e.message}), attaching wav`);
|
|
39731
|
+
}
|
|
39732
|
+
}
|
|
39733
|
+
const ext = path46.extname(audioPath).toLowerCase();
|
|
39734
|
+
const sizeKB = fs47.statSync(audioPath).size / 1024;
|
|
39735
|
+
const voiceFile = `voice_${Date.now()}${ext}`;
|
|
39736
|
+
const cwdCopy = path46.join(process.cwd(), voiceFile);
|
|
39737
|
+
try {
|
|
39738
|
+
fs47.copyFileSync(audioPath, cwdCopy);
|
|
39739
|
+
} catch (e) {
|
|
39740
|
+
try {
|
|
39741
|
+
fs47.unlinkSync(audioPath);
|
|
39742
|
+
} catch {
|
|
39743
|
+
}
|
|
39744
|
+
return { content: `\u8BED\u97F3\u6302\u8F7D\u5931\u8D25\uFF08\u62F7\u8D1D\u5230\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1A${e.message}`, isError: true };
|
|
39745
|
+
}
|
|
39746
|
+
try {
|
|
39747
|
+
fs47.unlinkSync(audioPath);
|
|
39748
|
+
} catch {
|
|
39749
|
+
}
|
|
39750
|
+
const content = opts.caption || `\u{1F399}\uFE0F \u8BED\u97F3\u6D88\u606F\uFF08${text.length} \u5B57\uFF09`;
|
|
39751
|
+
try {
|
|
39752
|
+
const { execFile: execFile5 } = await import("node:child_process");
|
|
39753
|
+
const { promisify: promisify5 } = await import("node:util");
|
|
39754
|
+
const execFileA = promisify5(execFile5);
|
|
39755
|
+
const { stdout } = await execFileA("multica", [
|
|
39756
|
+
"issue",
|
|
39757
|
+
"comment",
|
|
39758
|
+
"add",
|
|
39759
|
+
issueId,
|
|
39760
|
+
"--content",
|
|
39761
|
+
content,
|
|
39762
|
+
"--attachment",
|
|
39763
|
+
voiceFile,
|
|
39764
|
+
"--output",
|
|
39765
|
+
"json"
|
|
39766
|
+
], { timeout: 6e4 });
|
|
39767
|
+
let ok = false;
|
|
39768
|
+
try {
|
|
39769
|
+
const parsed = JSON.parse(stdout);
|
|
39770
|
+
ok = !!(parsed?.comment_id || parsed?.id || parsed?.attachments && parsed.attachments.length > 0);
|
|
39771
|
+
} catch {
|
|
39772
|
+
ok = stdout.includes("Comment added");
|
|
39773
|
+
}
|
|
39774
|
+
if (!ok) throw new Error(`unexpected CLI output: ${String(stdout).slice(0, 200)}`);
|
|
39775
|
+
try {
|
|
39776
|
+
fs47.unlinkSync(cwdCopy);
|
|
39777
|
+
} catch {
|
|
39778
|
+
}
|
|
39779
|
+
return { content: `Voice sent to multaca issue ${issueId}! (${actualEngine}, ${sizeKB.toFixed(0)}KB, \u9644\u4EF6\u5DF2\u6302\u5230\u8BC4\u8BBA\u2014\u2014\u5728 multaca \u5BA2\u6237\u7AEF\u64AD\u653E)` };
|
|
39780
|
+
} catch (e) {
|
|
39781
|
+
try {
|
|
39782
|
+
fs47.unlinkSync(cwdCopy);
|
|
39783
|
+
} catch {
|
|
39784
|
+
}
|
|
39785
|
+
return { content: `\u8BED\u97F3\u6302\u5230 multaca issue \u5931\u8D25\uFF1A${e.message}\u3002\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002`, isError: true };
|
|
39786
|
+
}
|
|
39787
|
+
}
|
|
39788
|
+
registry.register({
|
|
39789
|
+
name: "my_voice",
|
|
39790
|
+
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. TTS provider comes from config (tools.my_voice.provider); relay = 7yi relay station, the free-tier default. Do NOT pass `engine` unless the user explicitly names one \u2014 the configured provider is correct. Sends to weixin or feishu.",
|
|
39791
|
+
schema: {
|
|
39792
|
+
type: "object",
|
|
39793
|
+
properties: {
|
|
39794
|
+
text: { type: "string", description: "What to say (Chinese text)." },
|
|
39795
|
+
// ⚠️ enum **必须从 TTS_PROVIDERS 派生**,不许再手写一份四支的字面量:
|
|
39796
|
+
// 硬编的话模型传 engine:'relay' 会被工具 schema 拒掉,而托盘把它列成可选项 ——
|
|
39797
|
+
// 两边打架,「唯一源」就成了空话。providers.test.ts 有一条断言钉着它。
|
|
39798
|
+
engine: {
|
|
39799
|
+
type: "string",
|
|
39800
|
+
enum: TTS_PROVIDERS.map((p) => p.id),
|
|
39801
|
+
description: "TTS engine. relay = 7yi \u4E2D\u8F6C\u7AD9\uFF08\u514D\u8D39\u6863\u9ED8\u8BA4\uFF09\uFF0Ccosyvoice = \u767E\u70BC API\uFF0Cgptsovits = \u672C\u5730\u514B\u9686\uFF0Cqwen3-3060 = \u672C\u5730 GGML\uFF0Cedge = \u515C\u5E95\u3002"
|
|
39802
|
+
},
|
|
39803
|
+
channel: { type: "string", enum: ["weixin", "feishu", "discord"], description: "Target channel. Default: current channel." },
|
|
39804
|
+
to: { type: "string", description: "Recipient ID. \u5FAE\u4FE1\u683C\u5F0F o\u5F00\u5934@im.wechat\u7ED3\u5C3E\uFF0C\u98DE\u4E66\u683C\u5F0F ou_\u5F00\u5934 open_id\u3002\u4E0D\u786E\u5B9A\u5C31\u5148 read prompts/contacts.md \u67E5\u6536\u4EF6\u4EBA ID\u3002multaca \u4EFB\u52A1\u91CC\u4E0D\u9700\u8981\u586B to\u2014\u2014\u8BED\u97F3\u4F1A\u6302\u5230\u5F53\u524D issue \u8BC4\u8BBA\uFF0C\u586B issueId \u6307\u5B9A\u76EE\u6807 issue\u3002" },
|
|
39805
|
+
issueId: { type: "string", description: 'multaca headless \u4E13\u7528\uFF1A\u76EE\u6807 issue ID\uFF08\u4EFB\u52A1 prompt \u91CC\u7684 "Your assigned issue ID"\uFF09\u3002\u666E\u901A\u98DE\u4E66/\u5FAE\u4FE1\u573A\u666F\u4E0D\u8981\u586B\u3002' },
|
|
39806
|
+
caption: { type: "string", description: "Optional text to accompany the voice message." }
|
|
39807
|
+
},
|
|
39808
|
+
required: ["text"]
|
|
39809
|
+
},
|
|
39810
|
+
handler: async (args2, ctx) => {
|
|
39811
|
+
const text = args2.text?.trim();
|
|
39812
|
+
if (!text) return { content: "Error: text is required", isError: true };
|
|
39813
|
+
const requestedEngine = args2.engine || "";
|
|
39814
|
+
const caption = args2.caption || "";
|
|
39815
|
+
const mgr = ctx.channelManager;
|
|
39816
|
+
if (!mgr) {
|
|
39817
|
+
const multicaTaskId = process.env.MULTICA_TASK_ID || "";
|
|
39818
|
+
const issueId = args2.issueId || multicaTaskId;
|
|
39819
|
+
if (multicaTaskId && issueId) {
|
|
39820
|
+
return await sendVoiceToMulticaIssue(text, issueId, { engine: requestedEngine, caption });
|
|
39821
|
+
}
|
|
39822
|
+
return {
|
|
39823
|
+
content: "\u8BED\u97F3\u672A\u53D1\u9001\uFF1A\u5F53\u524D\u662F\u65E0\u6E20\u9053\u73AF\u5883\uFF08headless/CLI\uFF09\uFF0C\u4E14\u6CA1\u6709 multaca \u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF08MULTICA_TASK_ID\uFF09\u6216 issueId\u3002\u6CA1\u6709\u53EF\u7528\u7684\u53D1\u9001\u51FA\u53E3\uFF0C\u8BF7\u7528\u6587\u5B57\u56DE\u590D\u4EE3\u66FF\u3002",
|
|
39824
|
+
isError: true
|
|
39825
|
+
};
|
|
39826
|
+
}
|
|
39827
|
+
const resolvedChannelRaw = args2.channel || (ctx.channel === "deskBuddy" ? "feishu" : ctx.channel) || "feishu";
|
|
39828
|
+
const resolvedChannel = resolvedChannelRaw === "weixin" ? "wechat" : resolvedChannelRaw;
|
|
39829
|
+
let target = args2.to || ctx.channelTarget || ctx.from;
|
|
39830
|
+
if (resolvedChannel === "wechat" && !/^o[\w-]+@im\.wechat$/.test(target || "")) {
|
|
39831
|
+
console.warn(`[my-voice] wechat target "${target}" not a wechat user id`);
|
|
39832
|
+
return {
|
|
39833
|
+
content: `\u8BED\u97F3\u672A\u53D1\u9001\uFF1Ato "${target}" \u4E0D\u662F\u6709\u6548\u7684\u5FAE\u4FE1\u7528\u6237 ID\uFF08\u683C\u5F0F o \u5F00\u5934\u3001@im.wechat \u7ED3\u5C3E\uFF09\u3002\u4F60\u8981\u53D1\u7ED9\u8C01\uFF0C\u5C31\u5148 read prompts/contacts.md \u67E5\u90A3\u4E2A\u4EBA\u7684\u5FAE\u4FE1 ID\uFF0C\u518D\u586B\u8FDB to \u53C2\u6570\u91CD\u8BD5\u3002`,
|
|
39834
|
+
isError: true
|
|
39835
|
+
};
|
|
39836
|
+
}
|
|
39837
|
+
if (resolvedChannel === "feishu" && !/^ou_[a-f0-9]+$/.test(target || "")) {
|
|
39838
|
+
console.warn(`[my-voice] feishu target "${target}" invalid`);
|
|
39839
|
+
return {
|
|
39840
|
+
content: `\u8BED\u97F3\u672A\u53D1\u9001\uFF1Ato "${target}" \u4E0D\u662F\u6709\u6548\u7684\u98DE\u4E66\u7528\u6237 open_id\uFF08\u683C\u5F0F ou_ \u5F00\u5934\uFF09\u3002\u4F60\u8981\u53D1\u7ED9\u8C01\uFF0C\u5C31\u5148 read prompts/contacts.md \u67E5\u90A3\u4E2A\u4EBA\u7684\u98DE\u4E66 open_id\uFF0C\u518D\u586B\u8FDB to \u53C2\u6570\u91CD\u8BD5\u3002`,
|
|
39841
|
+
isError: true
|
|
39842
|
+
};
|
|
39843
|
+
}
|
|
39844
|
+
let voiceDurationSec;
|
|
39845
|
+
const vc = liveConfig.get("tools.my_voice");
|
|
39846
|
+
const picked = pickProvider(requestedEngine, vc?.provider);
|
|
39847
|
+
const useEngine = picked.id;
|
|
39848
|
+
console.log(`[my-voice] engine=requested:"${requestedEngine}" config:"${vc?.provider || ""}" \u2192 ${useEngine}`);
|
|
39849
|
+
let audioPath;
|
|
39850
|
+
let actualEngine = useEngine;
|
|
39851
|
+
try {
|
|
39852
|
+
const r = await synthesize(picked.id, text, {});
|
|
39853
|
+
audioPath = r.path;
|
|
39854
|
+
if (r.durationMs && r.durationMs > 0) voiceDurationSec = r.durationMs / 1e3;
|
|
39855
|
+
} catch (e) {
|
|
39856
|
+
if (e?.name === "TtsConfigError") {
|
|
39857
|
+
return { content: `TTS failed: ${e.message}`, isError: true };
|
|
39858
|
+
}
|
|
39859
|
+
const chain = fallbackChainOf(liveConfig.get("tools.my_voice")?.fallback);
|
|
39860
|
+
const tried = /* @__PURE__ */ new Set([picked.id]);
|
|
39861
|
+
let lastErr = e;
|
|
39862
|
+
let recovered = false;
|
|
39863
|
+
for (const id of chain) {
|
|
39864
|
+
if (tried.has(id)) continue;
|
|
39865
|
+
tried.add(id);
|
|
39866
|
+
console.log(`[my-voice] ${picked.id} failed (${e.message}), falling back to ${id}`);
|
|
39867
|
+
try {
|
|
39868
|
+
const r = await synthesize(id, text, {});
|
|
39869
|
+
audioPath = r.path;
|
|
39870
|
+
actualEngine = id;
|
|
39871
|
+
if (r.durationMs && r.durationMs > 0) voiceDurationSec = r.durationMs / 1e3;
|
|
39872
|
+
recovered = true;
|
|
39873
|
+
break;
|
|
39874
|
+
} catch (e2) {
|
|
39875
|
+
lastErr = e2;
|
|
39876
|
+
}
|
|
39877
|
+
}
|
|
39878
|
+
if (!recovered) {
|
|
39879
|
+
return { content: `TTS failed: ${lastErr.message}`, isError: true };
|
|
39880
|
+
}
|
|
39881
|
+
}
|
|
39882
|
+
if (!audioPath.endsWith(".ogg")) {
|
|
39883
|
+
try {
|
|
39884
|
+
const r = await toWav24kWithDuration(audioPath);
|
|
39885
|
+
audioPath = r.wavPath;
|
|
39886
|
+
voiceDurationSec = voiceDurationSec ?? r.durationSec;
|
|
39887
|
+
} catch (e) {
|
|
39888
|
+
console.warn(`[my-voice] toWav24k failed (${e.message}), sending raw file`);
|
|
39889
|
+
}
|
|
39890
|
+
}
|
|
39891
|
+
if (audioPath.endsWith(".wav")) {
|
|
39892
|
+
try {
|
|
39893
|
+
audioPath = await wavToOggOpus(audioPath);
|
|
39894
|
+
} catch (e) {
|
|
39895
|
+
console.warn(`[my-voice] ogg/opus encode failed (${e.message}), falling back to m4a`);
|
|
39896
|
+
audioPath = await compressWav(audioPath);
|
|
39897
|
+
}
|
|
39898
|
+
}
|
|
39899
|
+
const ext = path46.extname(audioPath).toLowerCase();
|
|
39900
|
+
const mimeMap = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".ogg": "audio/ogg", ".silk": "audio/silk" };
|
|
39901
|
+
const mimeType = mimeMap[ext] || "audio/mpeg";
|
|
39902
|
+
const sizeKB = fs47.statSync(audioPath).size / 1024;
|
|
39903
|
+
try {
|
|
39904
|
+
await mgr.sendFile(resolvedChannel, target, caption, {
|
|
39905
|
+
path: audioPath,
|
|
39906
|
+
mimeType,
|
|
39907
|
+
filename: `voice_${Date.now()}${ext}`,
|
|
39908
|
+
...voiceDurationSec !== void 0 ? { voiceDurationSec } : {}
|
|
39909
|
+
});
|
|
39910
|
+
try {
|
|
39911
|
+
fs47.unlinkSync(audioPath);
|
|
39912
|
+
} catch {
|
|
39913
|
+
}
|
|
39914
|
+
return { content: `Voice sent! (${actualEngine}, ${sizeKB.toFixed(0)}KB, ${resolvedChannel})` };
|
|
39915
|
+
} catch (err) {
|
|
39916
|
+
return { content: `Voice send failed: ${err.message}`, isError: true };
|
|
39917
|
+
}
|
|
39918
|
+
},
|
|
39919
|
+
isConcurrencySafe: () => false,
|
|
39920
|
+
interruptBehavior: () => "block",
|
|
39921
|
+
isReadOnly: () => false,
|
|
39922
|
+
isDestructive: () => false
|
|
39923
|
+
});
|
|
39924
|
+
|
|
39925
|
+
// src/tools/my-selfie.ts
|
|
39926
|
+
init_live();
|
|
39927
|
+
init_registry();
|
|
39928
|
+
init_well_formed();
|
|
39929
|
+
import * as fs48 from "node:fs";
|
|
39930
|
+
import * as path47 from "node:path";
|
|
39629
39931
|
function getProxyDispatcher2() {
|
|
39630
39932
|
const cfg = liveConfig.all();
|
|
39631
39933
|
const proxy = cfg.providers?.xai?.proxy;
|
|
@@ -39639,7 +39941,7 @@ function getProxyDispatcher2() {
|
|
|
39639
39941
|
}
|
|
39640
39942
|
function dimsOf(p) {
|
|
39641
39943
|
try {
|
|
39642
|
-
const b =
|
|
39944
|
+
const b = fs48.readFileSync(p);
|
|
39643
39945
|
let w = 0, h = 0;
|
|
39644
39946
|
if (b.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
39645
39947
|
w = b.readUInt32BE(16);
|
|
@@ -39762,9 +40064,9 @@ function registerPortrait(relPath, name) {
|
|
|
39762
40064
|
const cur = cfg?.tools?.my_selfie?.references || cfg?.my_selfie?.references || [];
|
|
39763
40065
|
const next = Array.isArray(cur) ? cur.filter((r) => r?.name !== name) : [];
|
|
39764
40066
|
next.unshift({ name, path: relPath });
|
|
39765
|
-
const
|
|
39766
|
-
void liveConfig.set(
|
|
39767
|
-
console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${
|
|
40067
|
+
const path60 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
|
|
40068
|
+
void liveConfig.set(path60, next);
|
|
40069
|
+
console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path60}[0] = ${relPath}`);
|
|
39768
40070
|
} catch (e) {
|
|
39769
40071
|
console.warn(`[my-selfie] \u5199\u5165 references \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u51FA\u56FE\uFF09: ${e.message}`);
|
|
39770
40072
|
}
|
|
@@ -40045,10 +40347,10 @@ registry.register({
|
|
|
40045
40347
|
const REFERENCES = getReferences(ctx);
|
|
40046
40348
|
const refName = args2.reference || "default";
|
|
40047
40349
|
const refEntry = REFERENCES.find((r) => r.name === refName) || REFERENCES[0];
|
|
40048
|
-
let refPath =
|
|
40350
|
+
let refPath = path47.join(ctx.workspace, refEntry.p);
|
|
40049
40351
|
const adoptImg = args2.referenceImage?.trim();
|
|
40050
40352
|
if (adoptImg) {
|
|
40051
|
-
if (!
|
|
40353
|
+
if (!fs48.existsSync(adoptImg)) {
|
|
40052
40354
|
return { content: `\u627E\u4E0D\u5230\u90A3\u5F20\u56FE\uFF1A${adoptImg}`, isError: true };
|
|
40053
40355
|
}
|
|
40054
40356
|
const acheck = dimsOf(adoptImg);
|
|
@@ -40059,16 +40361,16 @@ registry.register({
|
|
|
40059
40361
|
isError: true
|
|
40060
40362
|
};
|
|
40061
40363
|
}
|
|
40062
|
-
const adir =
|
|
40063
|
-
if (!
|
|
40064
|
-
const aext = (
|
|
40364
|
+
const adir = path47.join(ctx.workspace, "images");
|
|
40365
|
+
if (!fs48.existsSync(adir)) fs48.mkdirSync(adir, { recursive: true });
|
|
40366
|
+
const aext = (path47.extname(adoptImg) || ".png").toLowerCase();
|
|
40065
40367
|
const afname = `portrait_${Date.now()}${aext}`;
|
|
40066
|
-
|
|
40368
|
+
fs48.copyFileSync(adoptImg, path47.join(adir, afname));
|
|
40067
40369
|
registerPortrait(`images/${afname}`, refName);
|
|
40068
|
-
refPath =
|
|
40370
|
+
refPath = path47.join(adir, afname);
|
|
40069
40371
|
console.log(`[my-selfie] \u91C7\u7EB3\u73B0\u6210\u56FE\u5F53\u5F62\u8C61\uFF1Aimages/${afname}${acheck ? ` (${acheck})` : ""}`);
|
|
40070
40372
|
}
|
|
40071
|
-
if (provider !== "autodl" && !
|
|
40373
|
+
if (provider !== "autodl" && !fs48.existsSync(refPath)) {
|
|
40072
40374
|
const cfgAll = liveConfig.all();
|
|
40073
40375
|
const portraitDesc = resolvePortraitDesc(
|
|
40074
40376
|
args2.portrait,
|
|
@@ -40093,16 +40395,16 @@ registry.register({
|
|
|
40093
40395
|
} catch (e) {
|
|
40094
40396
|
return { content: `\u5851\u9020\u5F62\u8C61\u5931\u8D25\uFF1A${e.message}`, isError: true };
|
|
40095
40397
|
}
|
|
40096
|
-
const pdir =
|
|
40097
|
-
if (!
|
|
40398
|
+
const pdir = path47.join(ctx.workspace, "images");
|
|
40399
|
+
if (!fs48.existsSync(pdir)) fs48.mkdirSync(pdir, { recursive: true });
|
|
40098
40400
|
const pfname = `portrait_${Date.now()}.png`;
|
|
40099
|
-
|
|
40401
|
+
fs48.writeFileSync(path47.join(pdir, pfname), portraitBuf);
|
|
40100
40402
|
registerPortrait(`images/${pfname}`, refName);
|
|
40101
|
-
refPath =
|
|
40403
|
+
refPath = path47.join(pdir, pfname);
|
|
40102
40404
|
console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u5B58 images/${pfname}\uFF0C\u7EE7\u7EED\u8FD9\u6B21\u81EA\u62CD`);
|
|
40103
40405
|
}
|
|
40104
40406
|
const resolution = args2.resolution || DEFAULT_RESOLUTION;
|
|
40105
|
-
const refB64 =
|
|
40407
|
+
const refB64 = fs48.existsSync(refPath) ? fs48.readFileSync(refPath).toString("base64") : "";
|
|
40106
40408
|
let imageBuffer;
|
|
40107
40409
|
try {
|
|
40108
40410
|
if (provider === "autodl") {
|
|
@@ -40118,7 +40420,7 @@ registry.register({
|
|
|
40118
40420
|
return { content: `Error: relay provider \u672A\u914D\u7F6E\u3002config \u91CC\u8981\u6709 models.providers.${cfgAll?.my_selfie?.relayProviderId || "7yi"} \u7684 baseUrl \u548C apiKey\u3002`, isError: true };
|
|
40119
40421
|
}
|
|
40120
40422
|
const model = cfgAll?.my_selfie?.relayModel || cfgAll?.tools?.my_selfie?.relayModel || "engine7-img-free";
|
|
40121
|
-
const ext =
|
|
40423
|
+
const ext = path47.extname(refPath).toLowerCase();
|
|
40122
40424
|
const mime = ext === ".png" ? "image/png" : ext === ".webp" ? "image/webp" : "image/jpeg";
|
|
40123
40425
|
imageBuffer = await generateWithRelay(refB64, fullPrompt, mime, relay.baseUrl, relay.apiKey, model);
|
|
40124
40426
|
} else {
|
|
@@ -40127,11 +40429,11 @@ registry.register({
|
|
|
40127
40429
|
} catch (err) {
|
|
40128
40430
|
return { content: `Selfie generation failed: ${err.message}`, isError: true };
|
|
40129
40431
|
}
|
|
40130
|
-
const imagesDir =
|
|
40131
|
-
if (!
|
|
40432
|
+
const imagesDir = path47.join(ctx.workspace, "images");
|
|
40433
|
+
if (!fs48.existsSync(imagesDir)) fs48.mkdirSync(imagesDir, { recursive: true });
|
|
40132
40434
|
const filename = `selfie_${Date.now()}.jpg`;
|
|
40133
|
-
const outputPath =
|
|
40134
|
-
|
|
40435
|
+
const outputPath = path47.join(imagesDir, filename);
|
|
40436
|
+
fs48.writeFileSync(outputPath, imageBuffer);
|
|
40135
40437
|
const mgr = ctx.channelManager;
|
|
40136
40438
|
if (mgr) {
|
|
40137
40439
|
const resolvedChannel = ctx.channel || "feishu";
|
|
@@ -40142,11 +40444,11 @@ registry.register({
|
|
|
40142
40444
|
mimeType: "image/jpeg"
|
|
40143
40445
|
});
|
|
40144
40446
|
} catch (err) {
|
|
40145
|
-
return { content: `Selfie generated but send failed: ${err.message}. Image: ${
|
|
40447
|
+
return { content: `Selfie generated but send failed: ${err.message}. Image: ${path47.resolve(outputPath)}`, isError: false };
|
|
40146
40448
|
}
|
|
40147
40449
|
return { content: `Selfie sent! Mode: ${mode}, Provider: ${provider}, Ref: ${refEntry.name}` };
|
|
40148
40450
|
}
|
|
40149
|
-
return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${
|
|
40451
|
+
return { content: `Selfie generated! Mode: ${mode}, Ref: ${refEntry.name}. Image: ${path47.resolve(outputPath)}` };
|
|
40150
40452
|
},
|
|
40151
40453
|
isConcurrencySafe: () => false,
|
|
40152
40454
|
interruptBehavior: () => "block",
|
|
@@ -40698,12 +41000,20 @@ ${lines.join("\n")}` };
|
|
|
40698
41000
|
init_registry();
|
|
40699
41001
|
init_live();
|
|
40700
41002
|
init_clock();
|
|
40701
|
-
import
|
|
40702
|
-
import
|
|
40703
|
-
|
|
41003
|
+
import * as fs49 from "node:fs";
|
|
41004
|
+
import * as path48 from "node:path";
|
|
41005
|
+
import { exec as exec2, spawn as spawn6 } from "node:child_process";
|
|
41006
|
+
import { promisify as promisify4 } from "node:util";
|
|
41007
|
+
var execAsync = promisify4(exec2);
|
|
40704
41008
|
function getServices() {
|
|
40705
41009
|
return liveConfig.get("services") || {};
|
|
40706
41010
|
}
|
|
41011
|
+
function serviceLogPath(fileName) {
|
|
41012
|
+
const stateDir = String(liveConfig.get("stateDir") ?? "");
|
|
41013
|
+
const logDir = path48.join(stateDir, "logs");
|
|
41014
|
+
if (!fs49.existsSync(logDir)) fs49.mkdirSync(logDir, { recursive: true });
|
|
41015
|
+
return path48.join(logDir, fileName);
|
|
41016
|
+
}
|
|
40707
41017
|
async function runCmd(cmd, timeoutMs = 15e3) {
|
|
40708
41018
|
try {
|
|
40709
41019
|
const { stdout, stderr } = await execAsync(cmd, { timeout: timeoutMs, maxBuffer: 1024 * 1024 });
|
|
@@ -40715,9 +41025,9 @@ async function runCmd(cmd, timeoutMs = 15e3) {
|
|
|
40715
41025
|
function spawnDetached(cmd) {
|
|
40716
41026
|
const isWin = process.platform === "win32";
|
|
40717
41027
|
const shell = isWin ? "cmd.exe" : "/bin/sh";
|
|
40718
|
-
const logFile = `
|
|
41028
|
+
const logFile = serviceLogPath(`service-${todayKey().replace(/-/g, "")}.log`);
|
|
40719
41029
|
const wrappedCmd = isWin ? `/c "${cmd.replace(/"/g, '\\"')} > "${logFile}" 2>&1"` : `-c "${cmd} > '${logFile}' 2>&1"`;
|
|
40720
|
-
const child =
|
|
41030
|
+
const child = spawn6(shell, isWin ? [wrappedCmd] : ["-c", `${cmd} > '${logFile}' 2>&1`], {
|
|
40721
41031
|
detached: true,
|
|
40722
41032
|
stdio: "ignore",
|
|
40723
41033
|
windowsHide: true,
|
|
@@ -40778,7 +41088,7 @@ registry.register({
|
|
|
40778
41088
|
const startCmd = svcArgs ? `${svc.start} ${svcArgs}` : svc.start;
|
|
40779
41089
|
if (longRunning) {
|
|
40780
41090
|
const isWin = process.platform === "win32";
|
|
40781
|
-
const logFile =
|
|
41091
|
+
const logFile = serviceLogPath(`${name}${svcArgs ? "-" + svcArgs : ""}-${todayKey().replace(/-/g, "")}.log`);
|
|
40782
41092
|
const redirect = isWin ? ` >> "${logFile}" 2>&1` : ` >> "${logFile}" 2>&1`;
|
|
40783
41093
|
const pid = spawnDetached(startCmd + redirect);
|
|
40784
41094
|
lines.push(`start: spawned PID ${pid} (background${svcArgs ? ", args=" + svcArgs : ""})`);
|
|
@@ -41021,16 +41331,16 @@ var EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode";
|
|
|
41021
41331
|
init_planModeState();
|
|
41022
41332
|
|
|
41023
41333
|
// src/utils/plans.ts
|
|
41024
|
-
import * as
|
|
41025
|
-
import * as
|
|
41334
|
+
import * as fs50 from "node:fs";
|
|
41335
|
+
import * as path49 from "node:path";
|
|
41026
41336
|
import * as crypto7 from "node:crypto";
|
|
41027
41337
|
var MAX_SLUG_RETRIES = 10;
|
|
41028
41338
|
function generateSlug() {
|
|
41029
41339
|
return crypto7.randomBytes(4).toString("hex");
|
|
41030
41340
|
}
|
|
41031
41341
|
function getPlansDirectory(stateDir) {
|
|
41032
|
-
const plansDir =
|
|
41033
|
-
|
|
41342
|
+
const plansDir = path49.join(stateDir, "plans");
|
|
41343
|
+
fs50.mkdirSync(plansDir, { recursive: true });
|
|
41034
41344
|
return plansDir;
|
|
41035
41345
|
}
|
|
41036
41346
|
var planSlugCache = /* @__PURE__ */ new Map();
|
|
@@ -41040,8 +41350,8 @@ function getPlanSlug(sessionId, stateDir) {
|
|
|
41040
41350
|
const plansDir = getPlansDirectory(stateDir);
|
|
41041
41351
|
for (let i = 0; i < MAX_SLUG_RETRIES; i++) {
|
|
41042
41352
|
slug = generateSlug();
|
|
41043
|
-
const filePath =
|
|
41044
|
-
if (!
|
|
41353
|
+
const filePath = path49.join(plansDir, `${slug}.md`);
|
|
41354
|
+
if (!fs50.existsSync(filePath)) {
|
|
41045
41355
|
break;
|
|
41046
41356
|
}
|
|
41047
41357
|
}
|
|
@@ -41052,21 +41362,21 @@ function getPlanSlug(sessionId, stateDir) {
|
|
|
41052
41362
|
function getPlanFilePath(sessionId, stateDir, agentId) {
|
|
41053
41363
|
const slug = getPlanSlug(sessionId, stateDir);
|
|
41054
41364
|
if (!agentId) {
|
|
41055
|
-
return
|
|
41365
|
+
return path49.join(getPlansDirectory(stateDir), `${slug}.md`);
|
|
41056
41366
|
}
|
|
41057
|
-
return
|
|
41367
|
+
return path49.join(getPlansDirectory(stateDir), `${slug}-agent-${agentId}.md`);
|
|
41058
41368
|
}
|
|
41059
41369
|
function getPlan(sessionId, stateDir, agentId) {
|
|
41060
41370
|
const filePath = getPlanFilePath(sessionId, stateDir, agentId);
|
|
41061
41371
|
try {
|
|
41062
|
-
return
|
|
41372
|
+
return fs50.readFileSync(filePath, "utf-8");
|
|
41063
41373
|
} catch {
|
|
41064
41374
|
return null;
|
|
41065
41375
|
}
|
|
41066
41376
|
}
|
|
41067
41377
|
function writePlan(sessionId, stateDir, content, agentId) {
|
|
41068
41378
|
const filePath = getPlanFilePath(sessionId, stateDir, agentId);
|
|
41069
|
-
|
|
41379
|
+
fs50.writeFileSync(filePath, content, "utf-8");
|
|
41070
41380
|
return filePath;
|
|
41071
41381
|
}
|
|
41072
41382
|
|
|
@@ -41707,8 +42017,8 @@ init_features2();
|
|
|
41707
42017
|
init_agent_registry();
|
|
41708
42018
|
|
|
41709
42019
|
// src/commands/work.ts
|
|
41710
|
-
import * as
|
|
41711
|
-
import * as
|
|
42020
|
+
import * as fs55 from "node:fs";
|
|
42021
|
+
import * as path55 from "node:path";
|
|
41712
42022
|
registerCommand({
|
|
41713
42023
|
name: "work",
|
|
41714
42024
|
description: "\u5DE5\u4F5C\u6A21\u5F0F\uFF1A\u65E0\u53C2\u770B\u72B6\u6001\uFF0Con=\u5F3A\u5236work\uFF0Coff=\u5F3A\u5236emotion\uFF0Cauto=\u81EA\u52A8\u5224\u5B9A",
|
|
@@ -41721,20 +42031,20 @@ registerCommand({
|
|
|
41721
42031
|
],
|
|
41722
42032
|
handler: async (ctx, deps) => {
|
|
41723
42033
|
const mode = (ctx.args.mode || "").trim().toLowerCase();
|
|
41724
|
-
const modeFile =
|
|
42034
|
+
const modeFile = path55.join(deps.config.workspace, ".work-mode");
|
|
41725
42035
|
if (mode === "on" || mode === "off") {
|
|
41726
|
-
|
|
42036
|
+
fs55.writeFileSync(modeFile, mode, "utf8");
|
|
41727
42037
|
await ctx.reply(mode === "on" ? "\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u5F3A\u5236 **work**\uFF08\u6240\u6709\u6D88\u606F\u5168\u91CF\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6761\u751F\u6548\uFF09" : "\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u5F3A\u5236 **emotion**\uFF08\u6240\u6709\u6D88\u606F\u8F7B\u91CF\u95F2\u804A\uFF0C\u4E0B\u4E00\u6761\u751F\u6548\uFF09");
|
|
41728
42038
|
} else if (mode === "auto") {
|
|
41729
42039
|
try {
|
|
41730
|
-
|
|
42040
|
+
fs55.unlinkSync(modeFile);
|
|
41731
42041
|
} catch {
|
|
41732
42042
|
}
|
|
41733
42043
|
await ctx.reply("\u5DE5\u4F5C\u6A21\u5F0F\u5DF2\u6062\u590D **auto**\uFF08emotion_state_machine \u9010\u6761\u81EA\u52A8\u5224\u5B9A\uFF09");
|
|
41734
42044
|
} else {
|
|
41735
42045
|
let cur = "auto";
|
|
41736
42046
|
try {
|
|
41737
|
-
cur =
|
|
42047
|
+
cur = fs55.readFileSync(modeFile, "utf8").trim() || "auto";
|
|
41738
42048
|
} catch {
|
|
41739
42049
|
}
|
|
41740
42050
|
const hint = cur === "on" ? "\uFF08\u5F3A\u5236 work\uFF1A\u5168\u91CF\u4E0A\u4E0B\u6587\uFF09" : cur === "off" ? "\uFF08\u5F3A\u5236 emotion\uFF1A\u8F7B\u91CF\u4E0A\u4E0B\u6587\uFF09" : "\uFF08\u6BCF\u6761\u6D88\u606F\u7531 emotion_state_machine \u81EA\u52A8\u5224\u5B9A\uFF09";
|
|
@@ -42204,8 +42514,8 @@ ${err.stack}`);
|
|
|
42204
42514
|
});
|
|
42205
42515
|
|
|
42206
42516
|
// src/commands/model.ts
|
|
42207
|
-
import * as
|
|
42208
|
-
import * as
|
|
42517
|
+
import * as fs56 from "node:fs";
|
|
42518
|
+
import * as path56 from "node:path";
|
|
42209
42519
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
42210
42520
|
function collectModels(config2) {
|
|
42211
42521
|
const aliases = config2.modelAliases || {};
|
|
@@ -42237,11 +42547,11 @@ function resolveConfigWritePath(config2, label) {
|
|
|
42237
42547
|
const configPath2 = config2._configFilePath;
|
|
42238
42548
|
if (!configPath2) return void 0;
|
|
42239
42549
|
let writePath = configPath2;
|
|
42240
|
-
if (configPath2 && !
|
|
42550
|
+
if (configPath2 && !fs56.existsSync(configPath2)) {
|
|
42241
42551
|
const __pFile = fileURLToPath8(import.meta.url);
|
|
42242
|
-
const __pDir =
|
|
42243
|
-
const altPath =
|
|
42244
|
-
if (
|
|
42552
|
+
const __pDir = path56.dirname(__pFile);
|
|
42553
|
+
const altPath = path56.join(path56.resolve(__pDir, "../configs"), path56.basename(configPath2));
|
|
42554
|
+
if (fs56.existsSync(altPath)) {
|
|
42245
42555
|
console.warn(`[${label}] Config not found at ${configPath2}, falling back to ${altPath}`);
|
|
42246
42556
|
writePath = altPath;
|
|
42247
42557
|
}
|
|
@@ -42396,14 +42706,14 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
|
|
|
42396
42706
|
return;
|
|
42397
42707
|
}
|
|
42398
42708
|
try {
|
|
42399
|
-
const raw = await
|
|
42709
|
+
const raw = await fs56.promises.readFile(writePath, "utf-8");
|
|
42400
42710
|
const cfg = JSON.parse(raw);
|
|
42401
42711
|
if (!cfg.agents?.defaults?.model) {
|
|
42402
42712
|
await ctx.reply(`\u26A0\uFE0F Config structure mismatch: agents.defaults.model not found`);
|
|
42403
42713
|
return;
|
|
42404
42714
|
}
|
|
42405
42715
|
cfg.agents.defaults.model.primary = target;
|
|
42406
|
-
await
|
|
42716
|
+
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42407
42717
|
console.log(`[primary] Persisted primary=${target} to ${writePath}`);
|
|
42408
42718
|
await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates[0].name})
|
|
42409
42719
|
Written to config, hot-reloading provider chain now.`);
|
|
@@ -42450,7 +42760,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
|
|
|
42450
42760
|
return;
|
|
42451
42761
|
}
|
|
42452
42762
|
try {
|
|
42453
|
-
const raw = await
|
|
42763
|
+
const raw = await fs56.promises.readFile(writePath, "utf-8");
|
|
42454
42764
|
const cfg = JSON.parse(raw);
|
|
42455
42765
|
if (!cfg.agents?.defaults?.model) {
|
|
42456
42766
|
await ctx.reply(`\u26A0\uFE0F Config structure mismatch: agents.defaults.model not found`);
|
|
@@ -42458,12 +42768,12 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
|
|
|
42458
42768
|
}
|
|
42459
42769
|
if (input === "auto" || input === "reset") {
|
|
42460
42770
|
delete cfg.agents.defaults.model.vision;
|
|
42461
|
-
await
|
|
42771
|
+
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42462
42772
|
console.log(`[vision-primary] Cleared vision primary in ${writePath}`);
|
|
42463
42773
|
await ctx.reply(`\u2705 Vision primary cleared (auto). Written to config. Hot-reload will apply.`);
|
|
42464
42774
|
} else {
|
|
42465
42775
|
cfg.agents.defaults.model.vision = target;
|
|
42466
|
-
await
|
|
42776
|
+
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42467
42777
|
console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
|
|
42468
42778
|
await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates[0].name})
|
|
42469
42779
|
Written to config. Hot-reload will apply.`);
|
|
@@ -42871,7 +43181,7 @@ async function startEngine(config2, opts) {
|
|
|
42871
43181
|
const { initSessionMemory: initSessionMemory2 } = await Promise.resolve().then(() => (init_sessionMemory(), sessionMemory_exports));
|
|
42872
43182
|
initSessionMemory2({
|
|
42873
43183
|
workspace: config2.workspace,
|
|
42874
|
-
stateDir:
|
|
43184
|
+
stateDir: path59.join(config2.stateDir, "session-memory"),
|
|
42875
43185
|
provider,
|
|
42876
43186
|
model: config2.provider.modelId || config2.model || "deepseek-v4-flash",
|
|
42877
43187
|
features: config2.profile.features
|
|
@@ -43400,11 +43710,11 @@ async function startEngine(config2, opts) {
|
|
|
43400
43710
|
}
|
|
43401
43711
|
|
|
43402
43712
|
// src/main.ts
|
|
43403
|
-
import { readFileSync as
|
|
43713
|
+
import { readFileSync as readFileSync48 } from "node:fs";
|
|
43404
43714
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
43405
|
-
import { dirname as dirname16, join as
|
|
43715
|
+
import { dirname as dirname16, join as join66 } from "node:path";
|
|
43406
43716
|
var __dirname2 = dirname16(fileURLToPath9(import.meta.url));
|
|
43407
|
-
var pkg = JSON.parse(
|
|
43717
|
+
var pkg = JSON.parse(readFileSync48(join66(__dirname2, "..", "package.json"), "utf-8"));
|
|
43408
43718
|
var epipeSeen = false;
|
|
43409
43719
|
process.on("uncaughtException", (err) => {
|
|
43410
43720
|
const code = err?.code ?? "";
|