engine7 7.1.112 → 7.1.114
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 +76 -42
- package/dist/engine-startup.mjs +667 -207
- package/dist/main.mjs +692 -243
- package/package.json +2 -2
package/dist/engine-startup.mjs
CHANGED
|
@@ -3083,8 +3083,8 @@ function findShell() {
|
|
|
3083
3083
|
timeout: 5e3,
|
|
3084
3084
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3085
3085
|
});
|
|
3086
|
-
const
|
|
3087
|
-
const nonWsl =
|
|
3086
|
+
const candidates2 = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
3087
|
+
const nonWsl = candidates2.find((p) => !isWslBash(p));
|
|
3088
3088
|
if (nonWsl && fs5.existsSync(nonWsl)) {
|
|
3089
3089
|
cachedShell = { shell: nonWsl, args: ["-c"] };
|
|
3090
3090
|
return cachedShell;
|
|
@@ -4004,7 +4004,7 @@ var init_skill_gate = __esm({
|
|
|
4004
4004
|
DEVELOPMENT_HINTS = /代码|源码|编译|构建|测试|报错|错误|bug|修复|排查|调试|实现|开发|接口|脚本|函数|类型|依赖|部署|代码 review|code|build|test|debug|implement|fix|compile|typescript|javascript|python|git/i;
|
|
4005
4005
|
TECHNICAL_OBJECTS = /代码|源码|仓库|项目|模块|插件|接口|服务|引擎|engine|voice[- ]?chat|docker|容器|llm|模型|延迟|工具|脚本|函数|依赖|部署|分支|commit|api/i;
|
|
4006
4006
|
TECHNICAL_ACTIONS = /优化|瘦身|提速|迁移|重构|接线|收尾|改动|加上|删掉|换成|裁剪|升级|降级|修|接入|拆分|合并|调试|排查/i;
|
|
4007
|
-
USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy"]);
|
|
4007
|
+
USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy", "ccBridge"]);
|
|
4008
4008
|
loadedBySession = /* @__PURE__ */ new Map();
|
|
4009
4009
|
MAX_TRACKED_SESSIONS = 100;
|
|
4010
4010
|
}
|
|
@@ -6717,7 +6717,13 @@ var init_anthropic_provider = __esm({
|
|
|
6717
6717
|
thinking: { type: "enabled", budget_tokens: thinkingBudget }
|
|
6718
6718
|
} : params.disableThinking ? {
|
|
6719
6719
|
// 显式关闭 thinking(智谱 GLM-5.1 需要传 disabled 才真正关闭)
|
|
6720
|
-
|
|
6720
|
+
// 0928 修:MiniMax M3.1-Flash 系强制 adaptive,disabled 直接 400(码 2013,
|
|
6721
|
+
// 实证:proactive 后台调用换 M3.1 后 LLM UNREACHABLE)——该系降级发 adaptive
|
|
6722
|
+
...params.model?.includes("M3.1-Flash") ? {
|
|
6723
|
+
thinking: { type: "adaptive" }
|
|
6724
|
+
} : {
|
|
6725
|
+
thinking: { type: "disabled" }
|
|
6726
|
+
},
|
|
6721
6727
|
...params.temperature !== void 0 ? { temperature: params.temperature } : {}
|
|
6722
6728
|
} : {
|
|
6723
6729
|
...params.temperature !== void 0 ? { temperature: params.temperature } : {}
|
|
@@ -7786,7 +7792,10 @@ function loadConfig(configPath) {
|
|
|
7786
7792
|
channels: agentDefaults.channels || [],
|
|
7787
7793
|
extensions: agentDefaults.extensions,
|
|
7788
7794
|
maxTurns: agentDefaults.maxTurns,
|
|
7789
|
-
|
|
7795
|
+
// ⚠️ **默认 true**(0928 翀哥拍板):开源项目,功能不按 license 分档。
|
|
7796
|
+
// 要收起来才显式配 `agents.defaults.devMode: false`。
|
|
7797
|
+
// 反过来写(=== true)时,任何没手配这一格的配置都会静默落进"基础引擎模式"。
|
|
7798
|
+
devMode: agentDefaults.devMode !== false
|
|
7790
7799
|
};
|
|
7791
7800
|
const rawSession = raw.session || {};
|
|
7792
7801
|
const sessionScope = Object.keys(rawSession).length > 0 ? { ...rawSession } : void 0;
|
|
@@ -10001,9 +10010,9 @@ Follow these principles when generating episodic memories:
|
|
|
10001
10010
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
10002
10011
|
import { mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync6, existsSync as existsSync14, readdirSync as readdirSync2 } from "node:fs";
|
|
10003
10012
|
import { join as join24 } from "node:path";
|
|
10004
|
-
import { createHash } from "node:crypto";
|
|
10013
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10005
10014
|
function contentHash(text) {
|
|
10006
|
-
return
|
|
10015
|
+
return createHash2("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
|
|
10007
10016
|
}
|
|
10008
10017
|
function mem7Root(stateDir) {
|
|
10009
10018
|
return join24(stateDir, "mem7");
|
|
@@ -14181,8 +14190,8 @@ function mapOperation(raw) {
|
|
|
14181
14190
|
}
|
|
14182
14191
|
function extractJson2(text) {
|
|
14183
14192
|
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
14184
|
-
const
|
|
14185
|
-
for (const c of
|
|
14193
|
+
const candidates2 = fenced ? [fenced[1], text] : [text];
|
|
14194
|
+
for (const c of candidates2) {
|
|
14186
14195
|
const start = c.indexOf("{");
|
|
14187
14196
|
const end = c.lastIndexOf("}");
|
|
14188
14197
|
if (start === -1 || end <= start) continue;
|
|
@@ -14853,14 +14862,14 @@ async function runProactiveCycle(deps, now) {
|
|
|
14853
14862
|
for (const act of persist.dueActions("9999-12-31T23:59:59Z", 1e3)) {
|
|
14854
14863
|
if (act.status === "queued") existingTitles.add(normalizeTitle(act.title));
|
|
14855
14864
|
}
|
|
14856
|
-
const
|
|
14865
|
+
const candidates2 = actionable.filter((s) => {
|
|
14857
14866
|
if (queuedIntentIds.has(s.node.id)) return false;
|
|
14858
14867
|
if (cooldownIntentIds.has(s.node.id)) return false;
|
|
14859
14868
|
return true;
|
|
14860
14869
|
});
|
|
14861
|
-
if (
|
|
14870
|
+
if (candidates2.length === 0) return 0;
|
|
14862
14871
|
let enqueued = 0;
|
|
14863
|
-
for (const scored of
|
|
14872
|
+
for (const scored of candidates2) {
|
|
14864
14873
|
const intent = scored.node;
|
|
14865
14874
|
if (intent.data.status === "resolved") continue;
|
|
14866
14875
|
store.updateNode(intent.id, { status: "action_scheduled" });
|
|
@@ -18318,25 +18327,43 @@ var init_tasks2 = __esm({
|
|
|
18318
18327
|
}
|
|
18319
18328
|
});
|
|
18320
18329
|
|
|
18321
|
-
// src/
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
buildEngineStatus: () => buildEngineStatus,
|
|
18325
|
-
readEngineVersion: () => readEngineVersion
|
|
18326
|
-
});
|
|
18327
|
-
import * as fs35 from "node:fs";
|
|
18328
|
-
import * as path35 from "node:path";
|
|
18330
|
+
// src/version.ts
|
|
18331
|
+
import { readFileSync as readFileSync31 } from "node:fs";
|
|
18332
|
+
import { dirname as dirname10, join as join43 } from "node:path";
|
|
18329
18333
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
18330
|
-
function
|
|
18331
|
-
|
|
18332
|
-
|
|
18334
|
+
function candidates() {
|
|
18335
|
+
if (_pkgDirOverride !== null) return [join43(_pkgDirOverride, "package.json")];
|
|
18336
|
+
const here = dirname10(fileURLToPath6(import.meta.url));
|
|
18337
|
+
return [join43(here, "..", "package.json"), join43(here, "..", "..", "package.json")];
|
|
18338
|
+
}
|
|
18339
|
+
function readFromDisk() {
|
|
18340
|
+
for (const p of candidates()) {
|
|
18333
18341
|
try {
|
|
18334
|
-
return JSON.parse(
|
|
18342
|
+
return JSON.parse(readFileSync31(p, "utf-8")).version || "unknown";
|
|
18335
18343
|
} catch {
|
|
18336
18344
|
}
|
|
18337
18345
|
}
|
|
18338
18346
|
return "unknown";
|
|
18339
18347
|
}
|
|
18348
|
+
function readEngineVersion() {
|
|
18349
|
+
if (_loaded === null) _loaded = readFromDisk();
|
|
18350
|
+
return _loaded;
|
|
18351
|
+
}
|
|
18352
|
+
var _loaded, _pkgDirOverride;
|
|
18353
|
+
var init_version = __esm({
|
|
18354
|
+
"src/version.ts"() {
|
|
18355
|
+
"use strict";
|
|
18356
|
+
_loaded = null;
|
|
18357
|
+
_pkgDirOverride = null;
|
|
18358
|
+
}
|
|
18359
|
+
});
|
|
18360
|
+
|
|
18361
|
+
// src/startup/api-status.ts
|
|
18362
|
+
var api_status_exports = {};
|
|
18363
|
+
__export(api_status_exports, {
|
|
18364
|
+
buildEngineStatus: () => buildEngineStatus,
|
|
18365
|
+
readEngineVersion: () => readEngineVersion
|
|
18366
|
+
});
|
|
18340
18367
|
function hasUsable(v) {
|
|
18341
18368
|
return !!v && !isUnresolvedEnvRef(v);
|
|
18342
18369
|
}
|
|
@@ -18359,6 +18386,7 @@ var init_api_status = __esm({
|
|
|
18359
18386
|
"src/startup/api-status.ts"() {
|
|
18360
18387
|
"use strict";
|
|
18361
18388
|
init_loader();
|
|
18389
|
+
init_version();
|
|
18362
18390
|
}
|
|
18363
18391
|
});
|
|
18364
18392
|
|
|
@@ -18935,8 +18963,8 @@ __export(voice_chat_tts_daemon_exports, {
|
|
|
18935
18963
|
});
|
|
18936
18964
|
import { spawn as spawn5 } from "child_process";
|
|
18937
18965
|
import net2 from "net";
|
|
18938
|
-
import
|
|
18939
|
-
import
|
|
18966
|
+
import path38 from "path";
|
|
18967
|
+
import fs38 from "fs";
|
|
18940
18968
|
function _log(msg2) {
|
|
18941
18969
|
console.log(`[tts-daemon] ${msg2}`);
|
|
18942
18970
|
}
|
|
@@ -18981,16 +19009,16 @@ async function startTtsDaemon(warmupCfg) {
|
|
|
18981
19009
|
return;
|
|
18982
19010
|
}
|
|
18983
19011
|
const script = (() => {
|
|
18984
|
-
const distDir =
|
|
18985
|
-
const
|
|
18986
|
-
|
|
18987
|
-
|
|
19012
|
+
const distDir = path38.dirname(new URL(import.meta.url).pathname);
|
|
19013
|
+
const candidates2 = [
|
|
19014
|
+
path38.join(distDir, "..", "python", "tts_daemon.py"),
|
|
19015
|
+
path38.join(process.cwd(), "engine/src/python/tts_daemon.py"),
|
|
18988
19016
|
"/Users/chongzhang/work/twinsun-hearth/engine/src/python/tts_daemon.py"
|
|
18989
19017
|
];
|
|
18990
|
-
for (const c of
|
|
18991
|
-
if (
|
|
19018
|
+
for (const c of candidates2) {
|
|
19019
|
+
if (fs38.existsSync(c)) return c;
|
|
18992
19020
|
}
|
|
18993
|
-
return
|
|
19021
|
+
return candidates2[0];
|
|
18994
19022
|
})();
|
|
18995
19023
|
_log(`spawning ${script}`);
|
|
18996
19024
|
const env = {
|
|
@@ -20457,7 +20485,7 @@ var init_builtInAgents = __esm({
|
|
|
20457
20485
|
});
|
|
20458
20486
|
|
|
20459
20487
|
// src/tools/AgentTool/forkSubagent.ts
|
|
20460
|
-
import { randomUUID as
|
|
20488
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
20461
20489
|
function isForkSubagentEnabled() {
|
|
20462
20490
|
return true;
|
|
20463
20491
|
}
|
|
@@ -20496,7 +20524,7 @@ function buildForkedMessages(directive, parentMessages) {
|
|
|
20496
20524
|
type: "tool_result",
|
|
20497
20525
|
tool_use_id: block.id,
|
|
20498
20526
|
content: FORK_PLACEHOLDER_RESULT,
|
|
20499
|
-
id: `toolr_${
|
|
20527
|
+
id: `toolr_${randomUUID9().replace(/-/g, "").slice(0, 24)}`
|
|
20500
20528
|
}));
|
|
20501
20529
|
const toolResultMessage = msg_user(
|
|
20502
20530
|
buildChildMessage(directive),
|
|
@@ -20559,10 +20587,10 @@ var init_forkSubagent = __esm({
|
|
|
20559
20587
|
});
|
|
20560
20588
|
|
|
20561
20589
|
// src/tools/AgentTool/runAgent.ts
|
|
20562
|
-
import { randomBytes as
|
|
20590
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
20563
20591
|
function generateAgentId() {
|
|
20564
20592
|
const prefix = "a";
|
|
20565
|
-
const bytes =
|
|
20593
|
+
const bytes = randomBytes7(8);
|
|
20566
20594
|
let id = prefix;
|
|
20567
20595
|
for (let i = 0; i < 8; i++) {
|
|
20568
20596
|
id += TASK_ID_ALPHABET2[bytes[i] % TASK_ID_ALPHABET2.length];
|
|
@@ -21693,7 +21721,7 @@ var init_TeamDeleteTool = __esm({
|
|
|
21693
21721
|
},
|
|
21694
21722
|
isEnabled: () => isAgentSwarmsEnabled(),
|
|
21695
21723
|
handler: async (_args, ctx) => {
|
|
21696
|
-
const teamName = ctx.teamName;
|
|
21724
|
+
const teamName = ctx.teamName || getLeaderTeamName() || void 0;
|
|
21697
21725
|
if (teamName) {
|
|
21698
21726
|
const teamFile = readTeamFile(teamName);
|
|
21699
21727
|
if (teamFile) {
|
|
@@ -21777,7 +21805,7 @@ function getAgentName(ctx) {
|
|
|
21777
21805
|
return ctx.agentName || TEAM_LEAD_NAME;
|
|
21778
21806
|
}
|
|
21779
21807
|
function getTeamName(ctx) {
|
|
21780
|
-
return ctx.teamName;
|
|
21808
|
+
return ctx.teamName || getLeaderTeamName() || void 0;
|
|
21781
21809
|
}
|
|
21782
21810
|
async function handleMessage(recipientName, content, summary, ctx) {
|
|
21783
21811
|
const teamName = getTeamName(ctx);
|
|
@@ -23138,16 +23166,32 @@ import * as path10 from "node:path";
|
|
|
23138
23166
|
import { fileURLToPath } from "node:url";
|
|
23139
23167
|
|
|
23140
23168
|
// src/config/secrets-env.ts
|
|
23141
|
-
import {
|
|
23169
|
+
import { createHash } from "node:crypto";
|
|
23170
|
+
import { basename, dirname, join as join9, normalize as normalize3 } from "node:path";
|
|
23142
23171
|
function secretsHome() {
|
|
23143
23172
|
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
23144
23173
|
}
|
|
23174
|
+
function normalizeStateDir(stateDir) {
|
|
23175
|
+
return normalize3(stateDir).split("\\").join("/").replace(/\/+$/, "").toLowerCase();
|
|
23176
|
+
}
|
|
23177
|
+
function stateKey(stateDir) {
|
|
23178
|
+
return createHash("sha256").update(normalizeStateDir(stateDir)).digest("hex").slice(0, 8);
|
|
23179
|
+
}
|
|
23180
|
+
function isDefaultStateDir(stateDir) {
|
|
23181
|
+
return normalizeStateDir(stateDir) === normalizeStateDir(join9(secretsHome(), ".engine7"));
|
|
23182
|
+
}
|
|
23145
23183
|
function secretsCandidates(stateDir, configName) {
|
|
23146
|
-
|
|
23147
|
-
|
|
23184
|
+
const shared = join9(secretsHome(), ".engine7-secrets", `${configName}.env`);
|
|
23185
|
+
const inState = [
|
|
23148
23186
|
join9(stateDir, "configs", `.env.${configName}`),
|
|
23149
23187
|
join9(stateDir, "configs", ".env")
|
|
23150
23188
|
];
|
|
23189
|
+
if (isDefaultStateDir(stateDir)) return [shared, ...inState];
|
|
23190
|
+
return [
|
|
23191
|
+
join9(secretsHome(), ".engine7-secrets", `${configName}-${stateKey(stateDir)}.env`),
|
|
23192
|
+
shared,
|
|
23193
|
+
...inState
|
|
23194
|
+
];
|
|
23151
23195
|
}
|
|
23152
23196
|
|
|
23153
23197
|
// src/reload/orchestrator.ts
|
|
@@ -24327,11 +24371,11 @@ var ALL_PAID_FEATURES = [
|
|
|
24327
24371
|
"report-gen",
|
|
24328
24372
|
"custom-soul"
|
|
24329
24373
|
];
|
|
24330
|
-
function loadLicense(stateDir, devMode) {
|
|
24374
|
+
function loadLicense(stateDir, devMode = true) {
|
|
24331
24375
|
if (_licenseChecked) return _cachedLicense;
|
|
24332
24376
|
_licenseChecked = true;
|
|
24333
24377
|
if (devMode) {
|
|
24334
|
-
console.log("[license]
|
|
24378
|
+
console.log("[license] \u5F00\u653E\u6A21\u5F0F \u2014 \u5168\u90E8 feature \u6FC0\u6D3B\uFF08\u8981\u5206\u6863\uFF1Aagents.defaults.devMode = false\uFF09");
|
|
24335
24379
|
const allActive = {};
|
|
24336
24380
|
for (const id of ALL_PAID_FEATURES) {
|
|
24337
24381
|
allActive[id] = { active: true };
|
|
@@ -24341,7 +24385,7 @@ function loadLicense(stateDir, devMode) {
|
|
|
24341
24385
|
}
|
|
24342
24386
|
const licensePath = path15.join(stateDir, "license.json");
|
|
24343
24387
|
if (!fs16.existsSync(licensePath)) {
|
|
24344
|
-
console.log("[license]
|
|
24388
|
+
console.log("[license] devMode=false \u4E14\u65E0 license.json \u2014\u2014 \u57FA\u7840\u5F15\u64CE\u6A21\u5F0F");
|
|
24345
24389
|
return null;
|
|
24346
24390
|
}
|
|
24347
24391
|
try {
|
|
@@ -25622,9 +25666,9 @@ function readDbEmbeddings(dbPath) {
|
|
|
25622
25666
|
db2.close();
|
|
25623
25667
|
}
|
|
25624
25668
|
}
|
|
25625
|
-
async function llmRerank(query,
|
|
25626
|
-
if (
|
|
25627
|
-
const manifest =
|
|
25669
|
+
async function llmRerank(query, candidates2, topK, ollamaChatUrl, model) {
|
|
25670
|
+
if (candidates2.length <= topK) return candidates2;
|
|
25671
|
+
const manifest = candidates2.map(
|
|
25628
25672
|
(c, i) => `${i + 1}. ${c.filename}
|
|
25629
25673
|
${c.description || c.name}`
|
|
25630
25674
|
).join("\n");
|
|
@@ -25656,7 +25700,7 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
|
|
|
25656
25700
|
});
|
|
25657
25701
|
if (!resp.ok) {
|
|
25658
25702
|
console.warn(`[memdir] rerank: ollama chat failed: ${resp.status}`);
|
|
25659
|
-
return
|
|
25703
|
+
return candidates2.slice(0, topK);
|
|
25660
25704
|
}
|
|
25661
25705
|
const data = await resp.json();
|
|
25662
25706
|
const text = data?.message?.content ?? "";
|
|
@@ -25665,19 +25709,19 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
|
|
|
25665
25709
|
try {
|
|
25666
25710
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
25667
25711
|
if (parsed.selected && Array.isArray(parsed.selected)) {
|
|
25668
|
-
const indices = parsed.selected.filter((n) => n >= 1 && n <=
|
|
25669
|
-
const result = indices.map((i) =>
|
|
25670
|
-
console.log(`[memdir] rerank: ${model} selected ${result.length}/${
|
|
25712
|
+
const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates2.length).map((n) => n - 1);
|
|
25713
|
+
const result = indices.map((i) => candidates2[i]);
|
|
25714
|
+
console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates2.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
|
|
25671
25715
|
return result.slice(0, topK);
|
|
25672
25716
|
}
|
|
25673
25717
|
} catch {
|
|
25674
25718
|
}
|
|
25675
25719
|
}
|
|
25676
25720
|
console.warn(`[memdir] rerank: no valid JSON in response (${text.length} chars), falling back to vector order`);
|
|
25677
|
-
return
|
|
25721
|
+
return candidates2.slice(0, topK);
|
|
25678
25722
|
} catch (e) {
|
|
25679
25723
|
console.warn(`[memdir] rerank: error: ${e?.message ?? e}, falling back to vector order`);
|
|
25680
|
-
return
|
|
25724
|
+
return candidates2.slice(0, topK);
|
|
25681
25725
|
}
|
|
25682
25726
|
}
|
|
25683
25727
|
async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
|
|
@@ -25726,17 +25770,17 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
|
|
|
25726
25770
|
}
|
|
25727
25771
|
}
|
|
25728
25772
|
scored.sort((a, b) => b.score - a.score);
|
|
25729
|
-
const
|
|
25773
|
+
const candidates2 = scored.slice(0, CANDIDATE_POOL);
|
|
25730
25774
|
const vectorMs = Date.now() - t0;
|
|
25731
25775
|
console.log(
|
|
25732
|
-
`[memdir] vector recall: ${
|
|
25776
|
+
`[memdir] vector recall: ${candidates2.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
|
|
25733
25777
|
);
|
|
25734
|
-
if (
|
|
25735
|
-
console.log(`[memdir] vector top scores: ${
|
|
25778
|
+
if (candidates2.length > 0) {
|
|
25779
|
+
console.log(`[memdir] vector top scores: ${candidates2.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates2.length > 5 ? "..." : ""}`);
|
|
25736
25780
|
}
|
|
25737
|
-
if (
|
|
25781
|
+
if (candidates2.length === 0) return [];
|
|
25738
25782
|
const rerankT0 = Date.now();
|
|
25739
|
-
const reranked = await llmRerank(query,
|
|
25783
|
+
const reranked = await llmRerank(query, candidates2, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
|
|
25740
25784
|
const rerankMs = Date.now() - rerankT0;
|
|
25741
25785
|
const totalMs = Date.now() - t0;
|
|
25742
25786
|
console.log(`[memdir] recall total: ${totalMs}ms (vector=${vectorMs}ms, rerank=${rerankMs}ms)`);
|
|
@@ -30504,6 +30548,8 @@ function startInboxPoller(ctx) {
|
|
|
30504
30548
|
const INBOX_POLL_INTERVAL_MS = 2e3;
|
|
30505
30549
|
const INBOX_IDLE_INTERVAL_MS = 3e4;
|
|
30506
30550
|
let pollCycleCount = 0;
|
|
30551
|
+
let lastStatusLine = "";
|
|
30552
|
+
let warnedNoPreview = false;
|
|
30507
30553
|
let inboxPollerTimer = null;
|
|
30508
30554
|
let inboxPollerStandby = false;
|
|
30509
30555
|
function runInboxPollCycle() {
|
|
@@ -30540,7 +30586,11 @@ function startInboxPoller(ctx) {
|
|
|
30540
30586
|
).join(" ");
|
|
30541
30587
|
const isMainActive = dispatcher.isActive("main");
|
|
30542
30588
|
const mainPending = dispatcher.pendingCount("main");
|
|
30543
|
-
|
|
30589
|
+
const statusLine = `teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`;
|
|
30590
|
+
if (statusLine !== lastStatusLine || pollCycleCount % 15 === 0) {
|
|
30591
|
+
lastStatusLine = statusLine;
|
|
30592
|
+
console.log(`[inbox-poller] #${pollCycleCount} ${statusLine}`);
|
|
30593
|
+
}
|
|
30544
30594
|
const now = Date.now();
|
|
30545
30595
|
const allTeamNames = new Set(teammates.filter((t) => t.status !== "killed").map((t) => t.identity.teamName));
|
|
30546
30596
|
const teams = [...allTeamNames];
|
|
@@ -30618,7 +30668,6 @@ function startInboxPoller(ctx) {
|
|
|
30618
30668
|
${notifications}
|
|
30619
30669
|
[/teammate-status]${taskSummary}`;
|
|
30620
30670
|
const inboxTarget = "";
|
|
30621
|
-
const inboxPreview = null;
|
|
30622
30671
|
const submitted = dispatcher.submitMessage({
|
|
30623
30672
|
text: queryContent,
|
|
30624
30673
|
sessionId: "main",
|
|
@@ -30628,21 +30677,12 @@ ${notifications}
|
|
|
30628
30677
|
priority: "next",
|
|
30629
30678
|
// 对齐 CC: idle 时立即提交,不是 'later' 排队
|
|
30630
30679
|
callbacks: {
|
|
30631
|
-
onText: (
|
|
30632
|
-
if (
|
|
30633
|
-
|
|
30634
|
-
|
|
30635
|
-
console.warn(`[inbox-poller:onText] no preview target, Discord output dropped (${text.length} chars)`);
|
|
30636
|
-
}
|
|
30680
|
+
onText: (_text) => {
|
|
30681
|
+
if (warnedNoPreview) return;
|
|
30682
|
+
warnedNoPreview = true;
|
|
30683
|
+
console.warn("[inbox-poller] teammate-status \u7684\u8F93\u51FA\u4E0D\u6295\u9012\u5230\u5916\u90E8\u9891\u9053\uFF08\u8BBE\u8BA1\u5982\u6B64\uFF09\uFF0C\u4EC5\u5904\u7406\u526F\u4F5C\u7528");
|
|
30637
30684
|
},
|
|
30638
|
-
onResult: async (
|
|
30639
|
-
if (inboxPreview) {
|
|
30640
|
-
const delivered = await inboxPreview.finish(content.trim() || "(\u5904\u7406\u5B8C\u6210)");
|
|
30641
|
-
if (!delivered && inboxTarget) {
|
|
30642
|
-
channelManager.send("discord", inboxTarget, content.trim() || "(\u5904\u7406\u5B8C\u6210)").catch(() => {
|
|
30643
|
-
});
|
|
30644
|
-
}
|
|
30645
|
-
}
|
|
30685
|
+
onResult: async (_content) => {
|
|
30646
30686
|
}
|
|
30647
30687
|
},
|
|
30648
30688
|
deps
|
|
@@ -31336,8 +31376,8 @@ var VoiceChatPlugin = class {
|
|
|
31336
31376
|
// src/memory/everos/plugin.ts
|
|
31337
31377
|
import { spawn as spawn4 } from "node:child_process";
|
|
31338
31378
|
import net from "node:net";
|
|
31339
|
-
import
|
|
31340
|
-
import
|
|
31379
|
+
import path35 from "node:path";
|
|
31380
|
+
import fs35 from "node:fs";
|
|
31341
31381
|
|
|
31342
31382
|
// src/memory/everos/config.ts
|
|
31343
31383
|
var DEFAULTS4 = {
|
|
@@ -31571,8 +31611,8 @@ var EverosPlugin = class {
|
|
|
31571
31611
|
}, 3e5);
|
|
31572
31612
|
}
|
|
31573
31613
|
async startEveros() {
|
|
31574
|
-
const pythonDir =
|
|
31575
|
-
const configPath =
|
|
31614
|
+
const pythonDir = path35.dirname(this.config.lancedbPath);
|
|
31615
|
+
const configPath = path35.join(pythonDir, "config.toml");
|
|
31576
31616
|
await this.ensureFcntlCompat();
|
|
31577
31617
|
const venvPython = this.findVenvPython();
|
|
31578
31618
|
const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
|
|
@@ -31662,19 +31702,19 @@ var EverosPlugin = class {
|
|
|
31662
31702
|
return child;
|
|
31663
31703
|
}
|
|
31664
31704
|
findVenvPython() {
|
|
31665
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31705
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
31666
31706
|
if (process.platform === "win32") {
|
|
31667
|
-
return
|
|
31707
|
+
return path35.join(stateDir, "everos-venv", "Scripts", "python.exe");
|
|
31668
31708
|
}
|
|
31669
|
-
return
|
|
31709
|
+
return path35.join(stateDir, "everos-venv", "bin", "python");
|
|
31670
31710
|
}
|
|
31671
31711
|
/** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
|
|
31672
31712
|
async ensureVenv() {
|
|
31673
31713
|
const venvPython = this.findVenvPython();
|
|
31674
|
-
if (
|
|
31675
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31676
|
-
const venvDir =
|
|
31677
|
-
const everosSrc =
|
|
31714
|
+
if (fs35.existsSync(venvPython)) return;
|
|
31715
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
31716
|
+
const venvDir = path35.join(stateDir, "everos-venv");
|
|
31717
|
+
const everosSrc = path35.join(stateDir, "workspace", "research", "EverOS");
|
|
31678
31718
|
console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
|
|
31679
31719
|
console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
|
|
31680
31720
|
const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
|
|
@@ -31696,9 +31736,9 @@ var EverosPlugin = class {
|
|
|
31696
31736
|
console.log(`[everos] Creating venv with ${sysPython}...`);
|
|
31697
31737
|
const { execSync: execSync3 } = await import("node:child_process");
|
|
31698
31738
|
execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
|
|
31699
|
-
const pip = process.platform === "win32" ?
|
|
31700
|
-
const everosReq =
|
|
31701
|
-
if (
|
|
31739
|
+
const pip = process.platform === "win32" ? path35.join(venvDir, "Scripts", "pip.exe") : path35.join(venvDir, "bin", "pip");
|
|
31740
|
+
const everosReq = path35.join(this.getPythonDir(), "requirements.txt");
|
|
31741
|
+
if (fs35.existsSync(everosReq)) {
|
|
31702
31742
|
console.log(`[everos] Installing from requirements.txt...`);
|
|
31703
31743
|
execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
|
|
31704
31744
|
} else {
|
|
@@ -31713,29 +31753,29 @@ var EverosPlugin = class {
|
|
|
31713
31753
|
}
|
|
31714
31754
|
getPythonDir() {
|
|
31715
31755
|
const dir = import.meta.dirname;
|
|
31716
|
-
const
|
|
31717
|
-
|
|
31718
|
-
|
|
31719
|
-
|
|
31756
|
+
const candidates2 = [
|
|
31757
|
+
path35.join(dir, "python"),
|
|
31758
|
+
path35.resolve(dir, "..", "src", "memory", "everos", "python"),
|
|
31759
|
+
path35.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
|
|
31720
31760
|
];
|
|
31721
|
-
for (const candidate of
|
|
31722
|
-
if (
|
|
31761
|
+
for (const candidate of candidates2) {
|
|
31762
|
+
if (fs35.existsSync(path35.join(candidate, "agentic_server.py"))) {
|
|
31723
31763
|
return candidate;
|
|
31724
31764
|
}
|
|
31725
31765
|
}
|
|
31726
|
-
return
|
|
31766
|
+
return candidates2[0];
|
|
31727
31767
|
}
|
|
31728
31768
|
async ensureFcntlCompat() {
|
|
31729
31769
|
if (process.platform !== "win32") return;
|
|
31730
31770
|
const venvPython = this.findVenvPython();
|
|
31731
|
-
const venvDir =
|
|
31732
|
-
const sitePackages =
|
|
31733
|
-
const target =
|
|
31734
|
-
if (
|
|
31735
|
-
const source =
|
|
31736
|
-
if (
|
|
31771
|
+
const venvDir = path35.dirname(path35.dirname(venvPython));
|
|
31772
|
+
const sitePackages = path35.join(venvDir, "Lib", "site-packages");
|
|
31773
|
+
const target = path35.join(sitePackages, "fcntl.py");
|
|
31774
|
+
if (fs35.existsSync(target)) return;
|
|
31775
|
+
const source = path35.join(this.getPythonDir(), "fcntl_compat.py");
|
|
31776
|
+
if (fs35.existsSync(source)) {
|
|
31737
31777
|
try {
|
|
31738
|
-
|
|
31778
|
+
fs35.copyFileSync(source, target);
|
|
31739
31779
|
console.log(`[everos] Installed fcntl compat shim to ${target}`);
|
|
31740
31780
|
} catch (err) {
|
|
31741
31781
|
console.warn(`[everos] Failed to install fcntl shim: ${err.message}`);
|
|
@@ -32344,8 +32384,8 @@ function createVisualBoot(ctx) {
|
|
|
32344
32384
|
}
|
|
32345
32385
|
|
|
32346
32386
|
// src/startup/message-pipeline.ts
|
|
32347
|
-
import * as
|
|
32348
|
-
import * as
|
|
32387
|
+
import * as fs36 from "node:fs";
|
|
32388
|
+
import * as path36 from "node:path";
|
|
32349
32389
|
|
|
32350
32390
|
// src/commands/registry.ts
|
|
32351
32391
|
var commands = /* @__PURE__ */ new Map();
|
|
@@ -33236,7 +33276,7 @@ function setupMessagePipeline(ctx) {
|
|
|
33236
33276
|
console.log(`[vision] Downloading image: ${att.filename}`);
|
|
33237
33277
|
let rawBuffer;
|
|
33238
33278
|
if (att.url.startsWith("file://")) {
|
|
33239
|
-
rawBuffer =
|
|
33279
|
+
rawBuffer = fs36.readFileSync(decodeURIComponent(att.url.slice(7)));
|
|
33240
33280
|
} else {
|
|
33241
33281
|
rawBuffer = await downloadImage2(att.url, attachDispatcher);
|
|
33242
33282
|
}
|
|
@@ -33244,8 +33284,8 @@ function setupMessagePipeline(ctx) {
|
|
|
33244
33284
|
const ext = detected.split("/")[1] || "png";
|
|
33245
33285
|
const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
|
|
33246
33286
|
const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
33247
|
-
const savedPath =
|
|
33248
|
-
|
|
33287
|
+
const savedPath = path36.join(config.mediaDir, `${imageId}.${ext}`);
|
|
33288
|
+
fs36.writeFileSync(savedPath, resized.buffer);
|
|
33249
33289
|
savedPaths.push(savedPath);
|
|
33250
33290
|
console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
|
|
33251
33291
|
imageBlocks.push({
|
|
@@ -33275,8 +33315,8 @@ ${pathStr}` }];
|
|
|
33275
33315
|
}
|
|
33276
33316
|
const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
|
|
33277
33317
|
if (nonImageAttachments && nonImageAttachments.length > 0) {
|
|
33278
|
-
const outDir =
|
|
33279
|
-
|
|
33318
|
+
const outDir = path36.join(config.mediaDir, sessionId);
|
|
33319
|
+
fs36.mkdirSync(outDir, { recursive: true });
|
|
33280
33320
|
const resolved = [];
|
|
33281
33321
|
for (const att of nonImageAttachments) {
|
|
33282
33322
|
console.log(`[file] Downloading: ${att.filename} (${att.contentType}, ${att.size}B)`);
|
|
@@ -33284,9 +33324,9 @@ ${pathStr}` }];
|
|
|
33284
33324
|
const resp = attachDispatcher ? await fetch(att.url, { dispatcher: attachDispatcher, signal: AbortSignal.timeout(3e4) }) : await fetch(att.url, { signal: AbortSignal.timeout(3e4) });
|
|
33285
33325
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
33286
33326
|
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
33287
|
-
const safeName2 =
|
|
33288
|
-
const savedPath =
|
|
33289
|
-
|
|
33327
|
+
const safeName2 = path36.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
|
|
33328
|
+
const savedPath = path36.join(outDir, safeName2);
|
|
33329
|
+
fs36.writeFileSync(savedPath, buffer);
|
|
33290
33330
|
resolved.push(savedPath);
|
|
33291
33331
|
console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
|
|
33292
33332
|
} catch (err) {
|
|
@@ -33535,6 +33575,7 @@ ${pathStr}` }];
|
|
|
33535
33575
|
init_live();
|
|
33536
33576
|
|
|
33537
33577
|
// src/provision-free.ts
|
|
33578
|
+
import { randomBytes as randomBytes4, randomUUID as randomUUID6 } from "node:crypto";
|
|
33538
33579
|
import { chmodSync, existsSync as existsSync23, mkdirSync as mkdirSync15, readFileSync as readFileSync34, writeFileSync as writeFileSync12 } from "node:fs";
|
|
33539
33580
|
import { dirname as dirname11, join as join46 } from "node:path";
|
|
33540
33581
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -33543,19 +33584,68 @@ var IDENTITY_FILE = "install-identity.json";
|
|
|
33543
33584
|
function readContactEmailOf(parsed) {
|
|
33544
33585
|
return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
|
|
33545
33586
|
}
|
|
33546
|
-
function
|
|
33547
|
-
|
|
33548
|
-
|
|
33549
|
-
|
|
33587
|
+
function loadOrCreateInstallIdentity(stateDir) {
|
|
33588
|
+
const file2 = join46(stateDir, IDENTITY_FILE);
|
|
33589
|
+
if (existsSync23(file2)) {
|
|
33590
|
+
let parsed;
|
|
33591
|
+
try {
|
|
33592
|
+
parsed = JSON.parse(readFileSync34(file2, "utf8"));
|
|
33593
|
+
} catch (e) {
|
|
33594
|
+
throw new Error(
|
|
33595
|
+
`${IDENTITY_FILE} \u5B58\u5728\u4F46\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e.message}\uFF09\u2014\u2014\u5220\u6389\u5B83\u91CD\u8DD1\u4F1A\u9886\u4E00\u628A\u65B0\u4EE4\u724C\uFF0C\u65E7\u7684\u90A3\u628A\u53D6\u4E0D\u56DE\u6765`
|
|
33596
|
+
);
|
|
33597
|
+
}
|
|
33598
|
+
if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
|
|
33599
|
+
throw new Error(`${IDENTITY_FILE} \u7F3A installId / installSecret \u5B57\u6BB5`);
|
|
33600
|
+
}
|
|
33550
33601
|
const email = readContactEmailOf(parsed);
|
|
33551
33602
|
return {
|
|
33552
33603
|
installId: parsed.installId,
|
|
33553
33604
|
installSecret: parsed.installSecret,
|
|
33554
33605
|
...email ? { contactEmail: email } : {}
|
|
33555
33606
|
};
|
|
33607
|
+
}
|
|
33608
|
+
const identity = {
|
|
33609
|
+
// 服务端校验 installId 长度为 8..36、只允许字母数字和连字符;uuid 正好符合
|
|
33610
|
+
installId: randomUUID6(),
|
|
33611
|
+
// 服务端校验 installSecret 为 32..64 位十六进制
|
|
33612
|
+
installSecret: randomBytes4(32).toString("hex")
|
|
33613
|
+
};
|
|
33614
|
+
mkdirSync15(stateDir, { recursive: true });
|
|
33615
|
+
writeFileSync12(file2, JSON.stringify(identity, null, 2) + "\n", { mode: 384 });
|
|
33616
|
+
try {
|
|
33617
|
+
chmodSync(file2, 384);
|
|
33556
33618
|
} catch {
|
|
33557
|
-
return null;
|
|
33558
33619
|
}
|
|
33620
|
+
return identity;
|
|
33621
|
+
}
|
|
33622
|
+
function readInstallIdentityDetailed(stateDir) {
|
|
33623
|
+
const file2 = join46(stateDir, IDENTITY_FILE);
|
|
33624
|
+
if (!existsSync23(file2)) return { ok: false, reason: "missing" };
|
|
33625
|
+
let text;
|
|
33626
|
+
try {
|
|
33627
|
+
text = readFileSync34(file2, "utf8");
|
|
33628
|
+
} catch (e) {
|
|
33629
|
+
return { ok: false, reason: "corrupt", detail: `\u5B58\u5728\u4F46\u8BFB\u4E0D\u4E86\uFF08${e?.message ?? e}\uFF09` };
|
|
33630
|
+
}
|
|
33631
|
+
let parsed;
|
|
33632
|
+
try {
|
|
33633
|
+
parsed = JSON.parse(text);
|
|
33634
|
+
} catch (e) {
|
|
33635
|
+
return { ok: false, reason: "corrupt", detail: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e?.message ?? e}\uFF09` };
|
|
33636
|
+
}
|
|
33637
|
+
if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
|
|
33638
|
+
return { ok: false, reason: "corrupt", detail: "\u7F3A installId / installSecret \u5B57\u6BB5" };
|
|
33639
|
+
}
|
|
33640
|
+
const email = readContactEmailOf(parsed);
|
|
33641
|
+
return {
|
|
33642
|
+
ok: true,
|
|
33643
|
+
identity: {
|
|
33644
|
+
installId: parsed.installId,
|
|
33645
|
+
installSecret: parsed.installSecret,
|
|
33646
|
+
...email ? { contactEmail: email } : {}
|
|
33647
|
+
}
|
|
33648
|
+
};
|
|
33559
33649
|
}
|
|
33560
33650
|
|
|
33561
33651
|
// src/optional/reporting/state.ts
|
|
@@ -33582,10 +33672,10 @@ function writeReportState(stateDir, s) {
|
|
|
33582
33672
|
}
|
|
33583
33673
|
|
|
33584
33674
|
// src/optional/reporting/payload.ts
|
|
33585
|
-
import { createHash as
|
|
33675
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
33586
33676
|
import * as os3 from "node:os";
|
|
33587
33677
|
function installKeyOf(installId, installSecret) {
|
|
33588
|
-
return
|
|
33678
|
+
return createHash3("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
|
|
33589
33679
|
}
|
|
33590
33680
|
function buildReportPayload(opts) {
|
|
33591
33681
|
const p = {
|
|
@@ -33731,13 +33821,23 @@ function startReporting(opts) {
|
|
|
33731
33821
|
const log = opts.log ?? (() => {
|
|
33732
33822
|
});
|
|
33733
33823
|
const startedAt = nowFn3();
|
|
33734
|
-
const
|
|
33735
|
-
|
|
33736
|
-
|
|
33737
|
-
|
|
33738
|
-
|
|
33739
|
-
|
|
33824
|
+
const identityRead = readInstallIdentityDetailed(stateDir);
|
|
33825
|
+
let maybeIdentity = null;
|
|
33826
|
+
if (identityRead.ok) {
|
|
33827
|
+
maybeIdentity = identityRead.identity;
|
|
33828
|
+
} else if (identityRead.reason === "missing") {
|
|
33829
|
+
try {
|
|
33830
|
+
maybeIdentity = loadOrCreateInstallIdentity(stateDir);
|
|
33831
|
+
log("[reporting] \u672C\u673A\u6CA1\u6709\u88C5\u673A\u8EAB\u4EFD\uFF080921 \u524D\u88C5\u7684 / \u4EE4\u724C\u662F\u4ECE\u522B\u5904\u62F7\u6765\u7684\uFF09\u2014\u2014 \u5DF2\u5C31\u5730\u8865\u5EFA\uFF0C\u6B64\u540E\u5F00\u59CB\u4E0A\u62A5");
|
|
33832
|
+
} catch (e) {
|
|
33833
|
+
log(`[reporting] \u8865\u5EFA\u88C5\u673A\u8EAB\u4EFD\u5931\u8D25\uFF1A${e?.message ?? e} \u2014\u2014 \u4E0A\u62A5\u5173\u95ED`);
|
|
33834
|
+
}
|
|
33835
|
+
} else {
|
|
33836
|
+
log(`[reporting] install-identity.json \u635F\u574F\uFF08${identityRead.detail}\uFF09\u2014\u2014 \u4E0A\u62A5\u5173\u95ED\u3002\u523B\u610F\u4E0D\u91CD\u5EFA\uFF1A\u91CD\u5EFA\u7B49\u4E8E\u6362\u4E00\u53F0\u673A\u5668\uFF0C\u65E7\u4EE4\u724C\u518D\u4E5F\u53D6\u4E0D\u56DE\u6765`);
|
|
33740
33837
|
}
|
|
33838
|
+
if (!maybeIdentity) return { stop() {
|
|
33839
|
+
}, async beatNow() {
|
|
33840
|
+
} };
|
|
33741
33841
|
const identity = maybeIdentity;
|
|
33742
33842
|
const bootState = readReportState(stateDir);
|
|
33743
33843
|
restoreCounters(bootState);
|
|
@@ -35020,8 +35120,8 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
35020
35120
|
|
|
35021
35121
|
// src/channels/wechat.ts
|
|
35022
35122
|
import * as crypto5 from "crypto";
|
|
35023
|
-
import * as
|
|
35024
|
-
import * as
|
|
35123
|
+
import * as fs37 from "fs";
|
|
35124
|
+
import * as path37 from "path";
|
|
35025
35125
|
import * as os4 from "os";
|
|
35026
35126
|
import { promises as dns } from "dns";
|
|
35027
35127
|
function sleep3(ms) {
|
|
@@ -35245,7 +35345,7 @@ function guessChatType(msg2, accountId) {
|
|
|
35245
35345
|
return isGroup ? { type: "group", chatId: roomId || toId || String(msg2.from_user_id || "") } : { type: "dm", chatId: String(msg2.from_user_id || "") };
|
|
35246
35346
|
}
|
|
35247
35347
|
function mimeFromFilename(filename) {
|
|
35248
|
-
const ext =
|
|
35348
|
+
const ext = path37.extname(filename).toLowerCase();
|
|
35249
35349
|
const map = {
|
|
35250
35350
|
".jpg": "image/jpeg",
|
|
35251
35351
|
".jpeg": "image/jpeg",
|
|
@@ -35312,15 +35412,15 @@ var ContextTokenStore = class {
|
|
|
35312
35412
|
cache = /* @__PURE__ */ new Map();
|
|
35313
35413
|
filePath;
|
|
35314
35414
|
constructor(stateDir, accountId) {
|
|
35315
|
-
this.filePath =
|
|
35415
|
+
this.filePath = path37.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
|
|
35316
35416
|
}
|
|
35317
35417
|
k(a, p) {
|
|
35318
35418
|
return `${a}:${p}`;
|
|
35319
35419
|
}
|
|
35320
35420
|
restore() {
|
|
35321
35421
|
try {
|
|
35322
|
-
if (
|
|
35323
|
-
for (const [k, v] of Object.entries(JSON.parse(
|
|
35422
|
+
if (fs37.existsSync(this.filePath)) {
|
|
35423
|
+
for (const [k, v] of Object.entries(JSON.parse(fs37.readFileSync(this.filePath, "utf-8"))))
|
|
35324
35424
|
this.cache.set(k, String(v));
|
|
35325
35425
|
}
|
|
35326
35426
|
} catch {
|
|
@@ -35328,11 +35428,11 @@ var ContextTokenStore = class {
|
|
|
35328
35428
|
}
|
|
35329
35429
|
save() {
|
|
35330
35430
|
try {
|
|
35331
|
-
const dir =
|
|
35332
|
-
if (!
|
|
35431
|
+
const dir = path37.dirname(this.filePath);
|
|
35432
|
+
if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
|
|
35333
35433
|
const obj = {};
|
|
35334
35434
|
this.cache.forEach((v, k) => obj[k] = v);
|
|
35335
|
-
|
|
35435
|
+
fs37.writeFileSync(this.filePath, JSON.stringify(obj));
|
|
35336
35436
|
} catch {
|
|
35337
35437
|
}
|
|
35338
35438
|
}
|
|
@@ -35352,18 +35452,18 @@ var ContextTokenStore = class {
|
|
|
35352
35452
|
};
|
|
35353
35453
|
function loadSyncBuf(stateDir, accountId) {
|
|
35354
35454
|
try {
|
|
35355
|
-
const fp =
|
|
35356
|
-
if (
|
|
35455
|
+
const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35456
|
+
if (fs37.existsSync(fp)) return String(JSON.parse(fs37.readFileSync(fp, "utf-8")).sync_buf || "");
|
|
35357
35457
|
} catch {
|
|
35358
35458
|
}
|
|
35359
35459
|
return "";
|
|
35360
35460
|
}
|
|
35361
35461
|
function saveSyncBuf(stateDir, accountId, buf) {
|
|
35362
35462
|
try {
|
|
35363
|
-
const fp =
|
|
35364
|
-
const dir =
|
|
35365
|
-
if (!
|
|
35366
|
-
|
|
35463
|
+
const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35464
|
+
const dir = path37.dirname(fp);
|
|
35465
|
+
if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
|
|
35466
|
+
fs37.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
|
|
35367
35467
|
} catch {
|
|
35368
35468
|
}
|
|
35369
35469
|
}
|
|
@@ -35405,8 +35505,8 @@ var WechatAdapter = class {
|
|
|
35405
35505
|
this.config = config;
|
|
35406
35506
|
this.baseUrl = config.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
|
|
35407
35507
|
this.cdnBaseUrl = config.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
|
|
35408
|
-
this.stateDir = config.stateDir ||
|
|
35409
|
-
if (!
|
|
35508
|
+
this.stateDir = config.stateDir || path37.join(os4.homedir?.() || "/tmp", ".engine7");
|
|
35509
|
+
if (!fs37.existsSync(this.stateDir)) fs37.mkdirSync(this.stateDir, { recursive: true });
|
|
35410
35510
|
}
|
|
35411
35511
|
// --- ChannelAdapter interface ---
|
|
35412
35512
|
onMessage(handler2) {
|
|
@@ -35459,8 +35559,8 @@ var WechatAdapter = class {
|
|
|
35459
35559
|
}
|
|
35460
35560
|
async sendFile(target, message, attachment) {
|
|
35461
35561
|
const filePath = attachment.path;
|
|
35462
|
-
if (!
|
|
35463
|
-
const plaintext =
|
|
35562
|
+
if (!fs37.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
35563
|
+
const plaintext = fs37.readFileSync(filePath);
|
|
35464
35564
|
const mediaType = this.guessMediaType(filePath, attachment.mimeType);
|
|
35465
35565
|
const filekey = crypto5.randomBytes(16).toString("hex");
|
|
35466
35566
|
const aesKey = crypto5.randomBytes(16);
|
|
@@ -35492,7 +35592,7 @@ var WechatAdapter = class {
|
|
|
35492
35592
|
aesKeyForApi,
|
|
35493
35593
|
ciphertextSize: ciphertext.length,
|
|
35494
35594
|
plaintextSize: rawsize,
|
|
35495
|
-
filename:
|
|
35595
|
+
filename: path37.basename(filePath),
|
|
35496
35596
|
rawfilemd5,
|
|
35497
35597
|
voiceDurationSec: attachment.voiceDurationSec
|
|
35498
35598
|
});
|
|
@@ -35751,10 +35851,10 @@ var WechatAdapter = class {
|
|
|
35751
35851
|
mediaRef.full_url,
|
|
35752
35852
|
timeoutSec
|
|
35753
35853
|
);
|
|
35754
|
-
const tmpDir =
|
|
35755
|
-
if (!
|
|
35756
|
-
const tmpPath =
|
|
35757
|
-
|
|
35854
|
+
const tmpDir = path37.join(os4.tmpdir(), "weixin-media");
|
|
35855
|
+
if (!fs37.existsSync(tmpDir)) fs37.mkdirSync(tmpDir, { recursive: true });
|
|
35856
|
+
const tmpPath = path37.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
|
|
35857
|
+
fs37.writeFileSync(tmpPath, data);
|
|
35758
35858
|
return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
|
|
35759
35859
|
}
|
|
35760
35860
|
// --- Internal: send helpers ---
|
|
@@ -35804,7 +35904,7 @@ var WechatAdapter = class {
|
|
|
35804
35904
|
throw lastError;
|
|
35805
35905
|
}
|
|
35806
35906
|
guessMediaType(filePath, mimeType) {
|
|
35807
|
-
const ext =
|
|
35907
|
+
const ext = path37.extname(filePath).toLowerCase();
|
|
35808
35908
|
const mime = mimeType || mimeFromFilename(filePath);
|
|
35809
35909
|
if (mime.startsWith("image/")) return MEDIA_IMAGE;
|
|
35810
35910
|
if (mime.startsWith("video/")) return MEDIA_VIDEO;
|
|
@@ -35850,12 +35950,12 @@ init_voice_chat_tts_daemon();
|
|
|
35850
35950
|
import { execFile as execFile2 } from "node:child_process";
|
|
35851
35951
|
import { createInterface as createInterface2 } from "node:readline";
|
|
35852
35952
|
import { promisify as promisify2 } from "node:util";
|
|
35853
|
-
import * as
|
|
35854
|
-
import * as
|
|
35953
|
+
import * as fs39 from "node:fs";
|
|
35954
|
+
import * as path39 from "node:path";
|
|
35855
35955
|
import * as os5 from "node:os";
|
|
35856
35956
|
import net3 from "net";
|
|
35857
35957
|
var execFileAsync2 = promisify2(execFile2);
|
|
35858
|
-
var VOICE_DIR =
|
|
35958
|
+
var VOICE_DIR = path39.join(os5.tmpdir(), "engine-voice");
|
|
35859
35959
|
function wavDurationSec(buf) {
|
|
35860
35960
|
if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
35861
35961
|
throw new Error("wavDurationSec: not a RIFF/WAVE file");
|
|
@@ -35904,17 +36004,17 @@ function resolvePython(configuredPath) {
|
|
|
35904
36004
|
}
|
|
35905
36005
|
if (_pyCache) return _pyCache;
|
|
35906
36006
|
const localAppData = process.env.LOCALAPPDATA;
|
|
35907
|
-
const
|
|
36007
|
+
const candidates2 = [
|
|
35908
36008
|
"python3",
|
|
35909
36009
|
"python",
|
|
35910
36010
|
// LOCALAPPDATA 缺席(非 Windows / 精简环境)时**不加这两个候选**:
|
|
35911
36011
|
// 拼出来会是相对路径 'Microsoft/WindowsApps/python.exe',那可能撞上 cwd 里的东西。
|
|
35912
36012
|
...localAppData ? [
|
|
35913
|
-
|
|
35914
|
-
|
|
36013
|
+
path39.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
|
|
36014
|
+
path39.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
|
|
35915
36015
|
] : []
|
|
35916
36016
|
];
|
|
35917
|
-
for (const c of
|
|
36017
|
+
for (const c of candidates2) {
|
|
35918
36018
|
try {
|
|
35919
36019
|
__require("child_process").execFileSync(c, ["-c", "1"], { timeout: 5e3, stdio: "ignore" });
|
|
35920
36020
|
_pyCache = c;
|
|
@@ -35931,8 +36031,8 @@ async function ttsCosyvoice(text, apiKey, model, voice, workspaceId, instruction
|
|
|
35931
36031
|
}
|
|
35932
36032
|
async function ttsCosyvoiceStream(text, cfg, onChunk) {
|
|
35933
36033
|
const { apiKey, model, voice, workspaceId, instruction } = cfg;
|
|
35934
|
-
|
|
35935
|
-
const output =
|
|
36034
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36035
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
35936
36036
|
const script = `
|
|
35937
36037
|
import sys, json, base64, threading
|
|
35938
36038
|
import dashscope
|
|
@@ -36017,7 +36117,7 @@ print('EOS')
|
|
|
36017
36117
|
header.writeUInt16LE(16, 34);
|
|
36018
36118
|
header.write("data", 36);
|
|
36019
36119
|
header.writeUInt32LE(pcm.length, 40);
|
|
36020
|
-
|
|
36120
|
+
fs39.writeFileSync(output, Buffer.concat([header, pcm]));
|
|
36021
36121
|
return output;
|
|
36022
36122
|
}
|
|
36023
36123
|
async function ttsCosyvoiceWorker(text, cfg, onChunk) {
|
|
@@ -36127,9 +36227,9 @@ async function ttsQwen3Opus(text, cfg) {
|
|
|
36127
36227
|
const ogg = Buffer.from(await res.arrayBuffer());
|
|
36128
36228
|
if (ogg.length < 20) throw new Error("qwen3-3060 opus: empty output");
|
|
36129
36229
|
const durationMs = parseInt(res.headers.get("x-duration-ms") || "0", 10);
|
|
36130
|
-
|
|
36131
|
-
const oggPath =
|
|
36132
|
-
|
|
36230
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36231
|
+
const oggPath = path39.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
|
|
36232
|
+
fs39.writeFileSync(oggPath, ogg);
|
|
36133
36233
|
return { oggPath, durationMs };
|
|
36134
36234
|
}
|
|
36135
36235
|
async function ttsGptsovits(text, c) {
|
|
@@ -36141,8 +36241,8 @@ async function ttsGptsovits(text, c) {
|
|
|
36141
36241
|
"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"
|
|
36142
36242
|
);
|
|
36143
36243
|
}
|
|
36144
|
-
|
|
36145
|
-
const output =
|
|
36244
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36245
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36146
36246
|
const params = new URLSearchParams({
|
|
36147
36247
|
text,
|
|
36148
36248
|
text_language: "zh",
|
|
@@ -36153,13 +36253,13 @@ async function ttsGptsovits(text, c) {
|
|
|
36153
36253
|
const res = await fetch(`${url}/?${params}`);
|
|
36154
36254
|
if (!res.ok) throw new Error(`GPT-SoVITS API ${res.status}`);
|
|
36155
36255
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
36156
|
-
|
|
36256
|
+
fs39.writeFileSync(output, buf);
|
|
36157
36257
|
return output;
|
|
36158
36258
|
}
|
|
36159
36259
|
var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
|
|
36160
36260
|
async function ttsEdge(text) {
|
|
36161
|
-
|
|
36162
|
-
const output =
|
|
36261
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36262
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
|
|
36163
36263
|
const script = `
|
|
36164
36264
|
import asyncio, edge_tts, sys
|
|
36165
36265
|
async def main():
|
|
@@ -36204,8 +36304,8 @@ async function synthQwen3(text, _sink) {
|
|
|
36204
36304
|
const pcmChunks = [];
|
|
36205
36305
|
await ttsQwen3Local(text, { url: c.url, voice: c.voice, lang: c.lang }, (pcm2) => pcmChunks.push(pcm2));
|
|
36206
36306
|
const pcm = Buffer.concat(pcmChunks);
|
|
36207
|
-
|
|
36208
|
-
const wavPath =
|
|
36307
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36308
|
+
const wavPath = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36209
36309
|
const header = Buffer.alloc(44);
|
|
36210
36310
|
header.write("RIFF", 0);
|
|
36211
36311
|
header.writeUInt32LE(36 + pcm.length, 4);
|
|
@@ -36220,7 +36320,7 @@ async function synthQwen3(text, _sink) {
|
|
|
36220
36320
|
header.writeUInt16LE(16, 34);
|
|
36221
36321
|
header.write("data", 36);
|
|
36222
36322
|
header.writeUInt32LE(pcm.length, 40);
|
|
36223
|
-
|
|
36323
|
+
fs39.writeFileSync(wavPath, Buffer.concat([header, pcm]));
|
|
36224
36324
|
return { path: wavPath, format: "wav", unit: unit(text), voice: c.voice };
|
|
36225
36325
|
}
|
|
36226
36326
|
}
|
|
@@ -36539,8 +36639,365 @@ var DeskBuddyAdapter = class {
|
|
|
36539
36639
|
}
|
|
36540
36640
|
};
|
|
36541
36641
|
|
|
36642
|
+
// src/channels/cc-bridge.ts
|
|
36643
|
+
init_live();
|
|
36644
|
+
import fs40 from "node:fs";
|
|
36645
|
+
import path40 from "node:path";
|
|
36646
|
+
import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
|
|
36647
|
+
var DEFAULT_PORT = 16996;
|
|
36648
|
+
var CcBridgeAdapter = class {
|
|
36649
|
+
name = "ccBridge";
|
|
36650
|
+
config;
|
|
36651
|
+
wss = null;
|
|
36652
|
+
clients = /* @__PURE__ */ new Set();
|
|
36653
|
+
messageHandler = null;
|
|
36654
|
+
seq = 0;
|
|
36655
|
+
constructor(config) {
|
|
36656
|
+
this.config = config;
|
|
36657
|
+
}
|
|
36658
|
+
/** 工具流水默认不推(见 CcBridgeConfig.showToolDisplay) */
|
|
36659
|
+
get suppressToolDisplay() {
|
|
36660
|
+
return this.config.showToolDisplay !== true;
|
|
36661
|
+
}
|
|
36662
|
+
/** 实际监听的端口。配 `port: 0` 时由内核分配 —— 测试用它拿真端口,不用赌某个数字没被占 */
|
|
36663
|
+
get boundPort() {
|
|
36664
|
+
const a = this.wss?.address();
|
|
36665
|
+
return a && typeof a === "object" ? a.port : null;
|
|
36666
|
+
}
|
|
36667
|
+
onMessage(handler2) {
|
|
36668
|
+
this.messageHandler = handler2;
|
|
36669
|
+
}
|
|
36670
|
+
// ── outbox(持久队列)──
|
|
36671
|
+
//
|
|
36672
|
+
// 每次现读 stateDir,不缓存 —— stateDir 的 effect 是 restart,缓存下来的
|
|
36673
|
+
// 只会在热加载后变成指向旧目录的幽灵路径(配置访问铁律)。
|
|
36674
|
+
outboxFile() {
|
|
36675
|
+
const sd = liveConfig.get("stateDir") || process.cwd();
|
|
36676
|
+
return path40.join(sd, "state", "cc-bridge", "outbox.jsonl");
|
|
36677
|
+
}
|
|
36678
|
+
/**
|
|
36679
|
+
* 读队列。**只有 ENOENT 才等于"空队列"。**
|
|
36680
|
+
*
|
|
36681
|
+
* ⚠️ 这里原来写的是 `catch { return [] }` —— 那是**会丢数据的**:
|
|
36682
|
+
* 读失败(Windows 上 AV/同步盘/搜索索引的瞬时共享锁 EBUSY,或 EPERM/EACCES)
|
|
36683
|
+
* 返回空数组,紧接着 `enqueue` 拿这份空数组**整份覆写**,待投递的消息全没,
|
|
36684
|
+
* 而且一句警告都没有。而"读不了"和"不存在"是完全不同的两件事 ——
|
|
36685
|
+
* 这个区分正是"宁可重复,不可丢失"能不能成立的支点。
|
|
36686
|
+
*/
|
|
36687
|
+
readOutbox() {
|
|
36688
|
+
let raw;
|
|
36689
|
+
try {
|
|
36690
|
+
raw = fs40.readFileSync(this.outboxFile(), "utf8");
|
|
36691
|
+
} catch (e) {
|
|
36692
|
+
if (e?.code === "ENOENT") return [];
|
|
36693
|
+
throw e;
|
|
36694
|
+
}
|
|
36695
|
+
const out = [];
|
|
36696
|
+
for (const line of raw.split("\n")) {
|
|
36697
|
+
if (!line.trim()) continue;
|
|
36698
|
+
try {
|
|
36699
|
+
out.push(JSON.parse(line));
|
|
36700
|
+
} catch {
|
|
36701
|
+
}
|
|
36702
|
+
}
|
|
36703
|
+
return out;
|
|
36704
|
+
}
|
|
36705
|
+
/**
|
|
36706
|
+
* 写队列:**先写 .tmp 再 rename**。
|
|
36707
|
+
*
|
|
36708
|
+
* `writeFileSync(file, ...)` 是原地截断(`w` 模式先清零再写)——
|
|
36709
|
+
* 在这中间被 taskkill / OOM / 断电,文件就是空的或半截,
|
|
36710
|
+
* 下次读回来是 [] 且无从分辨,最多 maxQueue 条待投递消息整体消失。
|
|
36711
|
+
* rename 到已存在文件走的是替换语义,落盘要么全旧要么全新。
|
|
36712
|
+
*
|
|
36713
|
+
* **失败要抛**:调用方(cron/heartbeat 经 `manager.send`)必须知道没存住 ——
|
|
36714
|
+
* 吞成 warning 的话,磁盘满/只读时客户端在线还能收到实时那条,
|
|
36715
|
+
* 客户端离线就是真丢,而这恰恰是这个通道要保的场景。
|
|
36716
|
+
*/
|
|
36717
|
+
writeOutbox(items) {
|
|
36718
|
+
const file2 = this.outboxFile();
|
|
36719
|
+
const tmp = `${file2}.tmp`;
|
|
36720
|
+
fs40.mkdirSync(path40.dirname(file2), { recursive: true });
|
|
36721
|
+
fs40.writeFileSync(tmp, items.map((i) => JSON.stringify(i)).join("\n") + (items.length ? "\n" : ""), "utf8");
|
|
36722
|
+
fs40.renameSync(tmp, file2);
|
|
36723
|
+
}
|
|
36724
|
+
/** 入队(dedupe 不做 —— id 是新的,重复投递由 ack 收敛)。读/写失败都往外抛,不吞。 */
|
|
36725
|
+
enqueue(item) {
|
|
36726
|
+
const items = this.readOutbox();
|
|
36727
|
+
items.push(item);
|
|
36728
|
+
const cap = this.config.maxQueue ?? 500;
|
|
36729
|
+
if (items.length > cap) {
|
|
36730
|
+
const dropped = items.length - cap;
|
|
36731
|
+
items.splice(0, dropped);
|
|
36732
|
+
console.warn(`[ccBridge] outbox \u8D85\u8FC7 ${cap} \u6761\uFF0C\u4E22\u4E86\u6700\u8001\u7684 ${dropped} \u6761 \u2014\u2014 CC \u592A\u4E45\u6CA1\u6765\u6536`);
|
|
36733
|
+
}
|
|
36734
|
+
this.writeOutbox(items);
|
|
36735
|
+
}
|
|
36736
|
+
/**
|
|
36737
|
+
* ack:从队列删掉客户端确认过的。
|
|
36738
|
+
*
|
|
36739
|
+
* 读失败时**返回 0 而不是抛** —— 这里宁可"删不掉"也不能"删错":
|
|
36740
|
+
* ack 的语义是收敛,失败留给下一轮再收敛,代价只是重复投递。
|
|
36741
|
+
*/
|
|
36742
|
+
ack(ids) {
|
|
36743
|
+
if (!ids.length) return 0;
|
|
36744
|
+
const set = new Set(ids);
|
|
36745
|
+
let items;
|
|
36746
|
+
try {
|
|
36747
|
+
items = this.readOutbox();
|
|
36748
|
+
} catch (e) {
|
|
36749
|
+
console.warn(`[ccBridge] ack \u8BFB\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF08\u6D88\u606F\u4F1A\u91CD\u590D\u6295\u9012\uFF09\uFF1A${e.message}`);
|
|
36750
|
+
return 0;
|
|
36751
|
+
}
|
|
36752
|
+
const kept = items.filter((i) => !set.has(i.id));
|
|
36753
|
+
const removed = items.length - kept.length;
|
|
36754
|
+
if (removed) {
|
|
36755
|
+
try {
|
|
36756
|
+
this.writeOutbox(kept);
|
|
36757
|
+
} catch (e) {
|
|
36758
|
+
console.warn(`[ccBridge] ack \u5199\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF1A${e.message}`);
|
|
36759
|
+
return 0;
|
|
36760
|
+
}
|
|
36761
|
+
}
|
|
36762
|
+
return removed;
|
|
36763
|
+
}
|
|
36764
|
+
/** 队列条数(读不到就算 0 —— 只用于日志/握手,不能用来做删除决策) */
|
|
36765
|
+
queuedCount() {
|
|
36766
|
+
try {
|
|
36767
|
+
return this.readOutbox().length;
|
|
36768
|
+
} catch {
|
|
36769
|
+
return 0;
|
|
36770
|
+
}
|
|
36771
|
+
}
|
|
36772
|
+
/** 读队列但**不因读失败而中断**(握手补发、日志这类"读不到也能继续"的场合) */
|
|
36773
|
+
safeReadOutbox(where) {
|
|
36774
|
+
try {
|
|
36775
|
+
return this.readOutbox();
|
|
36776
|
+
} catch (e) {
|
|
36777
|
+
console.warn(`[ccBridge] ${where}\uFF1A\u8BFB\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u5F53\u4F5C\u7A7A\uFF08**\u672A\u5220\u9664\u4EFB\u4F55\u4E1C\u897F**\uFF09\uFF1A${e.message}`);
|
|
36778
|
+
return [];
|
|
36779
|
+
}
|
|
36780
|
+
}
|
|
36781
|
+
/**
|
|
36782
|
+
* 升级握手校验。见 `connect()` 里那段注释 —— 绑回环挡不住浏览器。
|
|
36783
|
+
* 返回 false 时 `ws` 会直接拒绝升级(HTTP 401),连接根本建立不起来。
|
|
36784
|
+
*/
|
|
36785
|
+
verifyUpgrade(info) {
|
|
36786
|
+
const h = info?.req?.headers ?? {};
|
|
36787
|
+
if (h.origin) {
|
|
36788
|
+
console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u5E26 Origin \u7684\u8FDE\u63A5\uFF1A${h.origin}\uFF08\u7591\u4F3C\u7F51\u9875\u53D1\u8D77\uFF09`);
|
|
36789
|
+
return false;
|
|
36790
|
+
}
|
|
36791
|
+
const host = String(h.host || "").replace(/:\d+$/, "").toLowerCase();
|
|
36792
|
+
const ok = host === "127.0.0.1" || host === "localhost" || host === "[::1]" || host === "::1" || host === "";
|
|
36793
|
+
if (!ok) console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u975E\u56DE\u73AF Host\uFF1A${h.host}\uFF08\u7591\u4F3C DNS rebinding\uFF09`);
|
|
36794
|
+
return ok;
|
|
36795
|
+
}
|
|
36796
|
+
// ── 连接 ──
|
|
36797
|
+
async connect() {
|
|
36798
|
+
const port = this.config.port ?? DEFAULT_PORT;
|
|
36799
|
+
try {
|
|
36800
|
+
fs40.mkdirSync(path40.dirname(this.outboxFile()), { recursive: true });
|
|
36801
|
+
} catch {
|
|
36802
|
+
}
|
|
36803
|
+
this.wss = new WebSocketServer2({
|
|
36804
|
+
port,
|
|
36805
|
+
host: "127.0.0.1",
|
|
36806
|
+
maxPayload: 4 * 1024 * 1024,
|
|
36807
|
+
// ⛔ **绑 127.0.0.1 不是安全边界。** WebSocket 不受同源策略约束,
|
|
36808
|
+
// 用户浏览器里**任意** http 页面都能 `new WebSocket('ws://127.0.0.1:16996')`
|
|
36809
|
+
// 连上来 —— 而这里连上就能以任意 `from` 往 agent 主会话注入消息
|
|
36810
|
+
// (实测过:伪造 Origin/Host 能成功注入一条 `from: 翀哥` 的消息)。
|
|
36811
|
+
// 所以必须自己校验:
|
|
36812
|
+
// · 带 `Origin` 头 = 网页发起的 → 直接拒(`scripts/cc-inbox.mjs` 是 Node
|
|
36813
|
+
// 客户端,本来就不发这个头,不受影响)
|
|
36814
|
+
// · `Host` 必须是本机回环 → 挡 DNS rebinding(域名解析到 127.0.0.1 时
|
|
36815
|
+
// Host 会是那个域名,而不是 127.0.0.1)
|
|
36816
|
+
verifyClient: (info) => this.verifyUpgrade(info)
|
|
36817
|
+
});
|
|
36818
|
+
this.wss.on("connection", (ws) => {
|
|
36819
|
+
this.clients.add(ws);
|
|
36820
|
+
console.log(`[ccBridge] CC connected (${this.clients.size} online)`);
|
|
36821
|
+
const pending2 = this.safeReadOutbox("\u63E1\u624B\u8865\u53D1");
|
|
36822
|
+
this.sendTo(ws, { type: "hello", channel: this.name, queued: pending2.length });
|
|
36823
|
+
if (pending2.length) {
|
|
36824
|
+
console.log(`[ccBridge] \u8865\u53D1 ${pending2.length} \u6761\u79EF\u538B`);
|
|
36825
|
+
this.sendTo(ws, { type: "backlog", messages: pending2 });
|
|
36826
|
+
}
|
|
36827
|
+
ws.on("message", (data) => this.handleFrame(ws, data));
|
|
36828
|
+
ws.on("close", () => {
|
|
36829
|
+
this.clients.delete(ws);
|
|
36830
|
+
console.log(`[ccBridge] CC disconnected (${this.clients.size} online)`);
|
|
36831
|
+
});
|
|
36832
|
+
ws.on("error", () => this.clients.delete(ws));
|
|
36833
|
+
});
|
|
36834
|
+
this.wss.on("error", (e) => {
|
|
36835
|
+
console.error(`[ccBridge] server error on 127.0.0.1:${port}: ${e.message}`);
|
|
36836
|
+
});
|
|
36837
|
+
await new Promise((resolve12, reject) => {
|
|
36838
|
+
const wss = this.wss;
|
|
36839
|
+
const ok = () => {
|
|
36840
|
+
wss.off("error", bad);
|
|
36841
|
+
resolve12();
|
|
36842
|
+
};
|
|
36843
|
+
const bad = (e) => {
|
|
36844
|
+
wss.off("listening", ok);
|
|
36845
|
+
reject(e);
|
|
36846
|
+
};
|
|
36847
|
+
wss.once("listening", ok);
|
|
36848
|
+
wss.once("error", bad);
|
|
36849
|
+
});
|
|
36850
|
+
console.log(`[ccBridge] listening on 127.0.0.1:${this.boundPort} (${this.queuedCount()} queued)`);
|
|
36851
|
+
}
|
|
36852
|
+
handleFrame(ws, data) {
|
|
36853
|
+
let msg2;
|
|
36854
|
+
try {
|
|
36855
|
+
msg2 = JSON.parse(String(data));
|
|
36856
|
+
} catch {
|
|
36857
|
+
this.sendTo(ws, { type: "error", error: "bad json" });
|
|
36858
|
+
return;
|
|
36859
|
+
}
|
|
36860
|
+
if (msg2.type === "ping") {
|
|
36861
|
+
this.sendTo(ws, { type: "pong" });
|
|
36862
|
+
return;
|
|
36863
|
+
}
|
|
36864
|
+
if (msg2.type === "ack") {
|
|
36865
|
+
const ids = Array.isArray(msg2.ids) ? msg2.ids.map(String) : [];
|
|
36866
|
+
const n = this.ack(ids);
|
|
36867
|
+
this.sendTo(ws, { type: "ack-ok", removed: n, queued: this.queuedCount() });
|
|
36868
|
+
return;
|
|
36869
|
+
}
|
|
36870
|
+
if (msg2.type === "chat") {
|
|
36871
|
+
const text = String(msg2.text ?? "").trim();
|
|
36872
|
+
if (!text) {
|
|
36873
|
+
this.sendTo(ws, { type: "error", error: "empty text" });
|
|
36874
|
+
return;
|
|
36875
|
+
}
|
|
36876
|
+
const from = String(msg2.from || this.config.userId || "cc");
|
|
36877
|
+
const inbound = {
|
|
36878
|
+
content: text,
|
|
36879
|
+
from,
|
|
36880
|
+
fromName: String(msg2.fromName || from),
|
|
36881
|
+
channel_id: this.name,
|
|
36882
|
+
channel: this.name,
|
|
36883
|
+
channelType: "dm",
|
|
36884
|
+
isBot: false,
|
|
36885
|
+
messageId: `ccb-${Date.now()}-${++this.seq}`,
|
|
36886
|
+
recvAt: Date.now()
|
|
36887
|
+
};
|
|
36888
|
+
console.log(`[ccBridge] \u2190 ${inbound.fromName}: ${text.slice(0, 80)}`);
|
|
36889
|
+
this.messageHandler?.(inbound);
|
|
36890
|
+
return;
|
|
36891
|
+
}
|
|
36892
|
+
this.sendTo(ws, { type: "error", error: `unknown type: ${msg2.type}` });
|
|
36893
|
+
}
|
|
36894
|
+
sendTo(ws, payload) {
|
|
36895
|
+
if (ws.readyState !== WebSocket2.OPEN) return;
|
|
36896
|
+
try {
|
|
36897
|
+
ws.send(JSON.stringify(payload));
|
|
36898
|
+
} catch {
|
|
36899
|
+
}
|
|
36900
|
+
}
|
|
36901
|
+
/** 广播给出站消息之外的控制帧(握手/pong 走 sendTo 单播) */
|
|
36902
|
+
broadcast(payload) {
|
|
36903
|
+
const s = JSON.stringify(payload);
|
|
36904
|
+
let n = 0;
|
|
36905
|
+
for (const ws of [...this.clients]) {
|
|
36906
|
+
if (ws.readyState !== WebSocket2.OPEN) {
|
|
36907
|
+
this.clients.delete(ws);
|
|
36908
|
+
continue;
|
|
36909
|
+
}
|
|
36910
|
+
try {
|
|
36911
|
+
ws.send(s);
|
|
36912
|
+
n++;
|
|
36913
|
+
} catch {
|
|
36914
|
+
this.clients.delete(ws);
|
|
36915
|
+
}
|
|
36916
|
+
}
|
|
36917
|
+
return n;
|
|
36918
|
+
}
|
|
36919
|
+
async disconnect() {
|
|
36920
|
+
for (const ws of [...this.clients]) {
|
|
36921
|
+
try {
|
|
36922
|
+
ws.close();
|
|
36923
|
+
} catch {
|
|
36924
|
+
}
|
|
36925
|
+
}
|
|
36926
|
+
this.clients.clear();
|
|
36927
|
+
this.wss?.close();
|
|
36928
|
+
this.wss = null;
|
|
36929
|
+
}
|
|
36930
|
+
// ── 出站 ──
|
|
36931
|
+
/**
|
|
36932
|
+
* 发消息给 CC。
|
|
36933
|
+
*
|
|
36934
|
+
* **先落 outbox 再推实时** —— 顺序反过来的话,推送成功但 CC 恰好这时断开,
|
|
36935
|
+
* 这条消息就既不在队列里也没送达,静默消失。宁可重复,不可丢失:
|
|
36936
|
+
* 重复只是多读一遍,丢失是 agent 主动说了话而没人知道。
|
|
36937
|
+
*/
|
|
36938
|
+
async send(_target, message, _options) {
|
|
36939
|
+
if (!message.trim()) return;
|
|
36940
|
+
const item = {
|
|
36941
|
+
id: `m${Date.now()}-${++this.seq}`,
|
|
36942
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36943
|
+
text: message,
|
|
36944
|
+
agent: this.config.agentName || "agent"
|
|
36945
|
+
};
|
|
36946
|
+
this.enqueue(item);
|
|
36947
|
+
const n = this.broadcast({ type: "message", message: item });
|
|
36948
|
+
if (n === 0) console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u5DF2\u5165\u961F\uFF08${this.queuedCount()} \u6761\u5F85\u6536\uFF09`);
|
|
36949
|
+
}
|
|
36950
|
+
/**
|
|
36951
|
+
* 发文件。
|
|
36952
|
+
*
|
|
36953
|
+
* **只送路径,不送内容**(`mode: 'path-ref'`)。这个通道两端都在 127.0.0.1 上
|
|
36954
|
+
* (引擎与 CC 同机),把文件 base64 塞进 WS 帧纯属浪费 —— 大文件还会把 outbox 撑爆。
|
|
36955
|
+
*
|
|
36956
|
+
* ⚠️ **但"只送路径"是把解析推迟到接收端,推迟不等于可以不做。**
|
|
36957
|
+
* 它有一个很难看的退化形态:发送端返回「已发送」(路径形式上没问题),
|
|
36958
|
+
* 接收端拿着路径在**它自己的机器上**找不到 —— **失败发生在接收端,
|
|
36959
|
+
* 而发送端日志一切正常**。(张小T 0928 指出的,她是对的。)
|
|
36960
|
+
*
|
|
36961
|
+
* 所以这里补两道:
|
|
36962
|
+
* 1. **入队前 `statSync`** —— 路径打错/文件被挪走,**在本机就报错**,
|
|
36963
|
+
* 而不是等接收端去找一个不存在的路径
|
|
36964
|
+
* 2. 帧里带 `size`/`mtimeMs` + `mode: 'path-ref'` —— 接收端知道这是**引用不是内容**,
|
|
36965
|
+
* 且能对一下"拿到的是不是你说的那份"
|
|
36966
|
+
*
|
|
36967
|
+
* 客户端那侧对应地**读不到就不 ack**(见 `scripts/cc-inbox.mjs`)——
|
|
36968
|
+
* ack 的语义是"我拿到了",拿到一半不能算。
|
|
36969
|
+
*/
|
|
36970
|
+
async sendFile(_target, message, attachment) {
|
|
36971
|
+
let st;
|
|
36972
|
+
try {
|
|
36973
|
+
st = fs40.statSync(attachment.path);
|
|
36974
|
+
} catch (e) {
|
|
36975
|
+
throw new Error(`\u9644\u4EF6\u4E0D\u5B58\u5728\uFF0C\u672A\u53D1\u9001\uFF1A${attachment.path}\uFF08${e?.code || e?.message}\uFF09`);
|
|
36976
|
+
}
|
|
36977
|
+
const file2 = {
|
|
36978
|
+
mode: "path-ref",
|
|
36979
|
+
path: attachment.path,
|
|
36980
|
+
filename: attachment.filename || path40.basename(attachment.path),
|
|
36981
|
+
mimeType: attachment.mimeType || "application/octet-stream",
|
|
36982
|
+
size: st.size,
|
|
36983
|
+
mtimeMs: Math.round(st.mtimeMs)
|
|
36984
|
+
};
|
|
36985
|
+
const item = {
|
|
36986
|
+
id: `f${Date.now()}-${++this.seq}`,
|
|
36987
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
36988
|
+
text: message || "",
|
|
36989
|
+
agent: this.config.agentName || "agent",
|
|
36990
|
+
file: file2
|
|
36991
|
+
};
|
|
36992
|
+
this.enqueue(item);
|
|
36993
|
+
if (this.broadcast({ type: "message", message: item }) === 0) {
|
|
36994
|
+
console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u9644\u4EF6\u5DF2\u5165\u961F\uFF1A${file2.filename}`);
|
|
36995
|
+
}
|
|
36996
|
+
}
|
|
36997
|
+
};
|
|
36998
|
+
|
|
36542
36999
|
// src/channels/manager.ts
|
|
36543
|
-
var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy"];
|
|
37000
|
+
var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"];
|
|
36544
37001
|
var defaultFactories = {
|
|
36545
37002
|
discord: (block) => {
|
|
36546
37003
|
const accounts = block.accounts || {};
|
|
@@ -36602,6 +37059,9 @@ var defaultFactories = {
|
|
|
36602
37059
|
},
|
|
36603
37060
|
deskBuddy: (block) => {
|
|
36604
37061
|
return new DeskBuddyAdapter(block);
|
|
37062
|
+
},
|
|
37063
|
+
ccBridge: (block) => {
|
|
37064
|
+
return new CcBridgeAdapter(block);
|
|
36605
37065
|
}
|
|
36606
37066
|
};
|
|
36607
37067
|
var ChannelManager = class {
|
|
@@ -38553,7 +39013,7 @@ Examples:
|
|
|
38553
39013
|
properties: {
|
|
38554
39014
|
to: { type: "string", description: "\u63A5\u6536\u8005ID\u3002\u586B channel_id \u65F6\u81EA\u52A8 @\u5BF9\u65B9\uFF1B\u4E0D\u586B channel_id \u65F6\u53D1 DM\u3002\u591A\u4E2A\u6536\u4EF6\u4EBA\u7528\u9017\u53F7\u5206\u9694\u3002" },
|
|
38555
39015
|
channel_id: { type: "string", description: "\u9891\u9053 ID\uFF08\u53EF\u9009\uFF09\u3002\u4E0D\u586B\u5219\u53D1 DM \u7ED9 to\u3002\u90FD\u4E0D\u586B\u65F6\u56DE\u590D\u6765\u6E90\u9891\u9053\u3002" },
|
|
38556
|
-
source: { type: "string", description: "\u6765\u6E90\u901A\u9053\uFF1Adiscord / feishu / wechat / deskBuddy\uFF08\u684C\u9762\u5C0F\u96EF\u8BED\u97F3\uFF09\u3002\u4E0D\u586B\u65F6\u4F7F\u7528\u5F53\u524D\u6765\u6E90\u901A\u9053\u3002", enum: ["discord", "feishu", "wechat", "deskBuddy"] },
|
|
39016
|
+
source: { type: "string", description: "\u6765\u6E90\u901A\u9053\uFF1Adiscord / feishu / wechat / deskBuddy\uFF08\u684C\u9762\u5C0F\u96EF\u8BED\u97F3\uFF09/ ccBridge\uFF08CC\uFF0C\u672C\u673A\u5F00\u53D1\u901A\u9053\uFF09\u3002\u4E0D\u586B\u65F6\u4F7F\u7528\u5F53\u524D\u6765\u6E90\u901A\u9053\u3002", enum: ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"] },
|
|
38557
39017
|
content: { type: "string", description: "\u6D88\u606F\u6B63\u6587" }
|
|
38558
39018
|
},
|
|
38559
39019
|
required: ["content", "to"]
|
|
@@ -42498,22 +42958,22 @@ ${lines.join("\n")}`);
|
|
|
42498
42958
|
await ctx.reply(wasOverride ? `Reset to default: **${defaultRef}** (auto-routing restored, fallback cooldowns cleared)` : `Already using default: **${defaultRef}**`);
|
|
42499
42959
|
return;
|
|
42500
42960
|
}
|
|
42501
|
-
const
|
|
42502
|
-
if (
|
|
42961
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
42962
|
+
if (candidates2.length === 0) {
|
|
42503
42963
|
await ctx.reply(`No model matching "${input}". Use \`/model\` to see list.`);
|
|
42504
42964
|
return;
|
|
42505
42965
|
}
|
|
42506
|
-
if (
|
|
42507
|
-
const lines =
|
|
42508
|
-
const more =
|
|
42509
|
-
... and ${
|
|
42966
|
+
if (candidates2.length > 1) {
|
|
42967
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42968
|
+
const more = candidates2.length > 10 ? `
|
|
42969
|
+
... and ${candidates2.length - 10} more` : "";
|
|
42510
42970
|
await ctx.reply(`Multiple matches for "${input}":
|
|
42511
42971
|
${lines.join("\n")}${more}
|
|
42512
42972
|
|
|
42513
|
-
Use full ref like \`/model ${
|
|
42973
|
+
Use full ref like \`/model ${candidates2[0].ref}\``);
|
|
42514
42974
|
return;
|
|
42515
42975
|
}
|
|
42516
|
-
const target =
|
|
42976
|
+
const target = candidates2[0].ref;
|
|
42517
42977
|
let targetDeps = deps.modelDepsCache.get(target);
|
|
42518
42978
|
if (!targetDeps) {
|
|
42519
42979
|
targetDeps = deps.createModelDeps(target);
|
|
@@ -42551,8 +43011,8 @@ Use full ref like \`/model ${candidates[0].ref}\``);
|
|
|
42551
43011
|
const writer = deps.sessions.getWriter(sid);
|
|
42552
43012
|
writer.writeModelChange(pid, mid);
|
|
42553
43013
|
}
|
|
42554
|
-
console.log(`[model] Switched to ${target} (${
|
|
42555
|
-
await ctx.reply(`Switched to **${target}** (${
|
|
43014
|
+
console.log(`[model] Switched to ${target} (${candidates2[0].name})`);
|
|
43015
|
+
await ctx.reply(`Switched to **${target}** (${candidates2[0].name})
|
|
42556
43016
|
Auto-routing disabled \u2014 all messages use this model.
|
|
42557
43017
|
\`/model auto\` to restore default.`);
|
|
42558
43018
|
}
|
|
@@ -42575,20 +43035,20 @@ Usage: \`/primary provider/model\` to persist (restart required)`);
|
|
|
42575
43035
|
return;
|
|
42576
43036
|
}
|
|
42577
43037
|
const { aliases, available } = collectModels(deps.config);
|
|
42578
|
-
const
|
|
42579
|
-
if (
|
|
43038
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43039
|
+
if (candidates2.length === 0) {
|
|
42580
43040
|
await ctx.reply(`No model matching "${input}".`);
|
|
42581
43041
|
return;
|
|
42582
43042
|
}
|
|
42583
|
-
if (
|
|
42584
|
-
const lines =
|
|
43043
|
+
if (candidates2.length > 1) {
|
|
43044
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42585
43045
|
await ctx.reply(`Multiple matches:
|
|
42586
43046
|
${lines.join("\n")}
|
|
42587
43047
|
|
|
42588
|
-
Use full ref like \`/primary ${
|
|
43048
|
+
Use full ref like \`/primary ${candidates2[0].ref}\``);
|
|
42589
43049
|
return;
|
|
42590
43050
|
}
|
|
42591
|
-
const target =
|
|
43051
|
+
const target = candidates2[0].ref;
|
|
42592
43052
|
if (!configPath || !writePath) {
|
|
42593
43053
|
await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
|
|
42594
43054
|
return;
|
|
@@ -42603,7 +43063,7 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
|
|
|
42603
43063
|
cfg.agents.defaults.model.primary = target;
|
|
42604
43064
|
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42605
43065
|
console.log(`[primary] Persisted primary=${target} to ${writePath}`);
|
|
42606
|
-
await ctx.reply(`\u2705 Primary model set to **${target}** (${
|
|
43066
|
+
await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates2[0].name})
|
|
42607
43067
|
Written to config, hot-reloading provider chain now.`);
|
|
42608
43068
|
} catch (err) {
|
|
42609
43069
|
await ctx.reply(`\u26A0\uFE0F Failed to write config: ${err.message}`);
|
|
@@ -42629,20 +43089,20 @@ Usage: \`/vision-primary provider/model\` to persist (hot-reload)
|
|
|
42629
43089
|
return;
|
|
42630
43090
|
}
|
|
42631
43091
|
const { aliases, available } = collectModels(deps.config);
|
|
42632
|
-
const
|
|
42633
|
-
if (
|
|
43092
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43093
|
+
if (candidates2.length === 0) {
|
|
42634
43094
|
await ctx.reply(`No model matching "${input}".`);
|
|
42635
43095
|
return;
|
|
42636
43096
|
}
|
|
42637
|
-
if (
|
|
42638
|
-
const lines =
|
|
43097
|
+
if (candidates2.length > 1) {
|
|
43098
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42639
43099
|
await ctx.reply(`Multiple matches:
|
|
42640
43100
|
${lines.join("\n")}
|
|
42641
43101
|
|
|
42642
|
-
Use full ref like \`/vision-primary ${
|
|
43102
|
+
Use full ref like \`/vision-primary ${candidates2[0].ref}\``);
|
|
42643
43103
|
return;
|
|
42644
43104
|
}
|
|
42645
|
-
const target =
|
|
43105
|
+
const target = candidates2[0].ref;
|
|
42646
43106
|
if (!configPath || !writePath) {
|
|
42647
43107
|
await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
|
|
42648
43108
|
return;
|
|
@@ -42663,7 +43123,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
|
|
|
42663
43123
|
cfg.agents.defaults.model.vision = target;
|
|
42664
43124
|
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42665
43125
|
console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
|
|
42666
|
-
await ctx.reply(`\u2705 Vision primary set to **${target}** (${
|
|
43126
|
+
await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates2[0].name})
|
|
42667
43127
|
Written to config. Hot-reload will apply.`);
|
|
42668
43128
|
}
|
|
42669
43129
|
} catch (err) {
|
|
@@ -42695,20 +43155,20 @@ Available models: use \`/model\` to see full list
|
|
|
42695
43155
|
return;
|
|
42696
43156
|
}
|
|
42697
43157
|
const { aliases, available } = collectModels(deps.config);
|
|
42698
|
-
const
|
|
42699
|
-
if (
|
|
43158
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43159
|
+
if (candidates2.length === 0) {
|
|
42700
43160
|
await ctx.reply(`No model matching "${input}".`);
|
|
42701
43161
|
return;
|
|
42702
43162
|
}
|
|
42703
|
-
if (
|
|
42704
|
-
const lines =
|
|
43163
|
+
if (candidates2.length > 1) {
|
|
43164
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42705
43165
|
await ctx.reply(`Multiple matches:
|
|
42706
43166
|
${lines.join("\n")}
|
|
42707
43167
|
|
|
42708
|
-
Use full ref like \`/vision-model ${
|
|
43168
|
+
Use full ref like \`/vision-model ${candidates2[0].ref}\``);
|
|
42709
43169
|
return;
|
|
42710
43170
|
}
|
|
42711
|
-
const target =
|
|
43171
|
+
const target = candidates2[0].ref;
|
|
42712
43172
|
let targetDeps = deps.modelDepsCache.get(`vision:${target}`);
|
|
42713
43173
|
if (!targetDeps) {
|
|
42714
43174
|
targetDeps = deps.createModelDeps(target);
|
|
@@ -42721,7 +43181,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
|
|
|
42721
43181
|
deps.setVisionOverride(target);
|
|
42722
43182
|
deps.setVisionDeps(targetDeps);
|
|
42723
43183
|
console.log(`[vision-model] Override: ${target}`);
|
|
42724
|
-
await ctx.reply(`Vision switched to **${target}** (${
|
|
43184
|
+
await ctx.reply(`Vision switched to **${target}** (${candidates2[0].name}) (immediate)
|
|
42725
43185
|
\`/vision-model auto\` to restore default.`);
|
|
42726
43186
|
}
|
|
42727
43187
|
});
|
|
@@ -43540,7 +44000,7 @@ async function startEngine(config, opts) {
|
|
|
43540
44000
|
};
|
|
43541
44001
|
const startReportingNow = () => {
|
|
43542
44002
|
stopReporting();
|
|
43543
|
-
reportingHandle = startReporting({ stateDir: config.stateDir });
|
|
44003
|
+
reportingHandle = startReporting({ stateDir: config.stateDir, log: (m) => console.log(m) });
|
|
43544
44004
|
};
|
|
43545
44005
|
const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
|
|
43546
44006
|
reloadOrchestrator.register({
|