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/main.mjs
CHANGED
|
@@ -575,7 +575,10 @@ function loadConfig(configPath2) {
|
|
|
575
575
|
channels: agentDefaults.channels || [],
|
|
576
576
|
extensions: agentDefaults.extensions,
|
|
577
577
|
maxTurns: agentDefaults.maxTurns,
|
|
578
|
-
|
|
578
|
+
// ⚠️ **默认 true**(0928 翀哥拍板):开源项目,功能不按 license 分档。
|
|
579
|
+
// 要收起来才显式配 `agents.defaults.devMode: false`。
|
|
580
|
+
// 反过来写(=== true)时,任何没手配这一格的配置都会静默落进"基础引擎模式"。
|
|
581
|
+
devMode: agentDefaults.devMode !== false
|
|
579
582
|
};
|
|
580
583
|
const rawSession = raw.session || {};
|
|
581
584
|
const sessionScope = Object.keys(rawSession).length > 0 ? { ...rawSession } : void 0;
|
|
@@ -3644,8 +3647,8 @@ function findShell() {
|
|
|
3644
3647
|
timeout: 5e3,
|
|
3645
3648
|
stdio: ["ignore", "pipe", "ignore"]
|
|
3646
3649
|
});
|
|
3647
|
-
const
|
|
3648
|
-
const nonWsl =
|
|
3650
|
+
const candidates2 = result.trim().split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
3651
|
+
const nonWsl = candidates2.find((p) => !isWslBash(p));
|
|
3649
3652
|
if (nonWsl && fs6.existsSync(nonWsl)) {
|
|
3650
3653
|
cachedShell = { shell: nonWsl, args: ["-c"] };
|
|
3651
3654
|
return cachedShell;
|
|
@@ -4565,7 +4568,7 @@ var init_skill_gate = __esm({
|
|
|
4565
4568
|
DEVELOPMENT_HINTS = /代码|源码|编译|构建|测试|报错|错误|bug|修复|排查|调试|实现|开发|接口|脚本|函数|类型|依赖|部署|代码 review|code|build|test|debug|implement|fix|compile|typescript|javascript|python|git/i;
|
|
4566
4569
|
TECHNICAL_OBJECTS = /代码|源码|仓库|项目|模块|插件|接口|服务|引擎|engine|voice[- ]?chat|docker|容器|llm|模型|延迟|工具|脚本|函数|依赖|部署|分支|commit|api/i;
|
|
4567
4570
|
TECHNICAL_ACTIONS = /优化|瘦身|提速|迁移|重构|接线|收尾|改动|加上|删掉|换成|裁剪|升级|降级|修|接入|拆分|合并|调试|排查/i;
|
|
4568
|
-
USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy"]);
|
|
4571
|
+
USER_CHANNELS = /* @__PURE__ */ new Set(["discord", "feishu", "wechat", "telegram", "cli", "console", "voice-chat", "deskBuddy", "ccBridge"]);
|
|
4569
4572
|
loadedBySession = /* @__PURE__ */ new Map();
|
|
4570
4573
|
MAX_TRACKED_SESSIONS = 100;
|
|
4571
4574
|
}
|
|
@@ -4582,20 +4585,20 @@ __export(paths_exports, {
|
|
|
4582
4585
|
isAutoMemPath: () => isAutoMemPath,
|
|
4583
4586
|
setMemoryDirOverride: () => setMemoryDirOverride
|
|
4584
4587
|
});
|
|
4585
|
-
import { join as join7, normalize as
|
|
4588
|
+
import { join as join7, normalize as normalize3, sep as sep2, isAbsolute as isAbsolute2, resolve as resolve3 } from "node:path";
|
|
4586
4589
|
function setMemoryDirOverride(dir) {
|
|
4587
4590
|
_overrideMemoryDir = dir;
|
|
4588
4591
|
}
|
|
4589
4592
|
function getAutoMemPath(workspace) {
|
|
4590
4593
|
const dir = _overrideMemoryDir ? isAbsolute2(_overrideMemoryDir) ? _overrideMemoryDir : resolve3(workspace, _overrideMemoryDir) : join7(workspace, AUTO_MEM_DIRNAME);
|
|
4591
|
-
const normalized =
|
|
4594
|
+
const normalized = normalize3(dir);
|
|
4592
4595
|
return (normalized + sep2).normalize("NFC");
|
|
4593
4596
|
}
|
|
4594
4597
|
function getAutoMemEntrypoint(workspace) {
|
|
4595
4598
|
return join7(getAutoMemPath(workspace), ENTRYPOINT_NAME);
|
|
4596
4599
|
}
|
|
4597
4600
|
function isAutoMemPath(absolutePath, workspace) {
|
|
4598
|
-
const normalizedPath =
|
|
4601
|
+
const normalizedPath = normalize3(absolutePath);
|
|
4599
4602
|
return normalizedPath.startsWith(getAutoMemPath(workspace));
|
|
4600
4603
|
}
|
|
4601
4604
|
async function ensureMemoryDirExists(memoryDir) {
|
|
@@ -7278,7 +7281,13 @@ var init_anthropic_provider = __esm({
|
|
|
7278
7281
|
thinking: { type: "enabled", budget_tokens: thinkingBudget }
|
|
7279
7282
|
} : params.disableThinking ? {
|
|
7280
7283
|
// 显式关闭 thinking(智谱 GLM-5.1 需要传 disabled 才真正关闭)
|
|
7281
|
-
|
|
7284
|
+
// 0928 修:MiniMax M3.1-Flash 系强制 adaptive,disabled 直接 400(码 2013,
|
|
7285
|
+
// 实证:proactive 后台调用换 M3.1 后 LLM UNREACHABLE)——该系降级发 adaptive
|
|
7286
|
+
...params.model?.includes("M3.1-Flash") ? {
|
|
7287
|
+
thinking: { type: "adaptive" }
|
|
7288
|
+
} : {
|
|
7289
|
+
thinking: { type: "disabled" }
|
|
7290
|
+
},
|
|
7282
7291
|
...params.temperature !== void 0 ? { temperature: params.temperature } : {}
|
|
7283
7292
|
} : {
|
|
7284
7293
|
...params.temperature !== void 0 ? { temperature: params.temperature } : {}
|
|
@@ -8108,11 +8117,11 @@ __export(license_exports, {
|
|
|
8108
8117
|
import * as crypto2 from "node:crypto";
|
|
8109
8118
|
import * as fs16 from "node:fs";
|
|
8110
8119
|
import * as path15 from "node:path";
|
|
8111
|
-
function loadLicense(stateDir, devMode) {
|
|
8120
|
+
function loadLicense(stateDir, devMode = true) {
|
|
8112
8121
|
if (_licenseChecked) return _cachedLicense;
|
|
8113
8122
|
_licenseChecked = true;
|
|
8114
8123
|
if (devMode) {
|
|
8115
|
-
console.log("[license]
|
|
8124
|
+
console.log("[license] \u5F00\u653E\u6A21\u5F0F \u2014 \u5168\u90E8 feature \u6FC0\u6D3B\uFF08\u8981\u5206\u6863\uFF1Aagents.defaults.devMode = false\uFF09");
|
|
8116
8125
|
const allActive = {};
|
|
8117
8126
|
for (const id of ALL_PAID_FEATURES) {
|
|
8118
8127
|
allActive[id] = { active: true };
|
|
@@ -8122,7 +8131,7 @@ function loadLicense(stateDir, devMode) {
|
|
|
8122
8131
|
}
|
|
8123
8132
|
const licensePath = path15.join(stateDir, "license.json");
|
|
8124
8133
|
if (!fs16.existsSync(licensePath)) {
|
|
8125
|
-
console.log("[license]
|
|
8134
|
+
console.log("[license] devMode=false \u4E14\u65E0 license.json \u2014\u2014 \u57FA\u7840\u5F15\u64CE\u6A21\u5F0F");
|
|
8126
8135
|
return null;
|
|
8127
8136
|
}
|
|
8128
8137
|
try {
|
|
@@ -8175,25 +8184,21 @@ function isFeatureLicensed(featureId) {
|
|
|
8175
8184
|
const f = _cachedLicense[featureId];
|
|
8176
8185
|
return f?.active === true;
|
|
8177
8186
|
}
|
|
8178
|
-
function getLicenseStatus(stateDir) {
|
|
8179
|
-
const
|
|
8180
|
-
|
|
8181
|
-
return { licensed: false, features: {} };
|
|
8182
|
-
}
|
|
8187
|
+
function getLicenseStatus(stateDir, devMode = true) {
|
|
8188
|
+
const active = loadLicense(stateDir, devMode);
|
|
8189
|
+
let payload = {};
|
|
8183
8190
|
try {
|
|
8184
|
-
|
|
8185
|
-
const license = JSON.parse(raw);
|
|
8186
|
-
const active = loadLicense(stateDir);
|
|
8187
|
-
return {
|
|
8188
|
-
licensed: active !== null,
|
|
8189
|
-
customer: license.customer,
|
|
8190
|
-
licenseId: license.licenseId,
|
|
8191
|
-
expiresAt: license.expiresAt,
|
|
8192
|
-
features: active || {}
|
|
8193
|
-
};
|
|
8191
|
+
payload = JSON.parse(fs16.readFileSync(path15.join(stateDir, "license.json"), "utf-8"));
|
|
8194
8192
|
} catch {
|
|
8195
|
-
return { licensed: false, features: {} };
|
|
8196
8193
|
}
|
|
8194
|
+
return {
|
|
8195
|
+
licensed: active !== null,
|
|
8196
|
+
customer: payload.customer,
|
|
8197
|
+
licenseId: payload.licenseId,
|
|
8198
|
+
expiresAt: payload.expiresAt,
|
|
8199
|
+
openMode: devMode,
|
|
8200
|
+
features: active || {}
|
|
8201
|
+
};
|
|
8197
8202
|
}
|
|
8198
8203
|
function resetLicenseCache() {
|
|
8199
8204
|
_cachedLicense = null;
|
|
@@ -10139,9 +10144,9 @@ Follow these principles when generating episodic memories:
|
|
|
10139
10144
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
10140
10145
|
import { mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync7, existsSync as existsSync15, readdirSync as readdirSync2 } from "node:fs";
|
|
10141
10146
|
import { join as join24 } from "node:path";
|
|
10142
|
-
import { createHash } from "node:crypto";
|
|
10147
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10143
10148
|
function contentHash(text) {
|
|
10144
|
-
return
|
|
10149
|
+
return createHash2("sha256").update(text.replace(/\s+/g, "")).digest("hex").slice(0, 40);
|
|
10145
10150
|
}
|
|
10146
10151
|
function mem7Root(stateDir) {
|
|
10147
10152
|
return join24(stateDir, "mem7");
|
|
@@ -14319,8 +14324,8 @@ function mapOperation(raw) {
|
|
|
14319
14324
|
}
|
|
14320
14325
|
function extractJson2(text) {
|
|
14321
14326
|
const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
14322
|
-
const
|
|
14323
|
-
for (const c of
|
|
14327
|
+
const candidates2 = fenced ? [fenced[1], text] : [text];
|
|
14328
|
+
for (const c of candidates2) {
|
|
14324
14329
|
const start = c.indexOf("{");
|
|
14325
14330
|
const end = c.lastIndexOf("}");
|
|
14326
14331
|
if (start === -1 || end <= start) continue;
|
|
@@ -14991,14 +14996,14 @@ async function runProactiveCycle(deps, now) {
|
|
|
14991
14996
|
for (const act of persist.dueActions("9999-12-31T23:59:59Z", 1e3)) {
|
|
14992
14997
|
if (act.status === "queued") existingTitles.add(normalizeTitle(act.title));
|
|
14993
14998
|
}
|
|
14994
|
-
const
|
|
14999
|
+
const candidates2 = actionable.filter((s) => {
|
|
14995
15000
|
if (queuedIntentIds.has(s.node.id)) return false;
|
|
14996
15001
|
if (cooldownIntentIds.has(s.node.id)) return false;
|
|
14997
15002
|
return true;
|
|
14998
15003
|
});
|
|
14999
|
-
if (
|
|
15004
|
+
if (candidates2.length === 0) return 0;
|
|
15000
15005
|
let enqueued = 0;
|
|
15001
|
-
for (const scored of
|
|
15006
|
+
for (const scored of candidates2) {
|
|
15002
15007
|
const intent = scored.node;
|
|
15003
15008
|
if (intent.data.status === "resolved") continue;
|
|
15004
15009
|
store.updateNode(intent.id, { status: "action_scheduled" });
|
|
@@ -18456,25 +18461,43 @@ var init_tasks2 = __esm({
|
|
|
18456
18461
|
}
|
|
18457
18462
|
});
|
|
18458
18463
|
|
|
18459
|
-
// src/
|
|
18460
|
-
|
|
18461
|
-
|
|
18462
|
-
buildEngineStatus: () => buildEngineStatus,
|
|
18463
|
-
readEngineVersion: () => readEngineVersion
|
|
18464
|
-
});
|
|
18465
|
-
import * as fs35 from "node:fs";
|
|
18466
|
-
import * as path35 from "node:path";
|
|
18464
|
+
// src/version.ts
|
|
18465
|
+
import { readFileSync as readFileSync32 } from "node:fs";
|
|
18466
|
+
import { dirname as dirname10, join as join43 } from "node:path";
|
|
18467
18467
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
18468
|
-
function
|
|
18469
|
-
|
|
18470
|
-
|
|
18468
|
+
function candidates() {
|
|
18469
|
+
if (_pkgDirOverride !== null) return [join43(_pkgDirOverride, "package.json")];
|
|
18470
|
+
const here = dirname10(fileURLToPath6(import.meta.url));
|
|
18471
|
+
return [join43(here, "..", "package.json"), join43(here, "..", "..", "package.json")];
|
|
18472
|
+
}
|
|
18473
|
+
function readFromDisk() {
|
|
18474
|
+
for (const p of candidates()) {
|
|
18471
18475
|
try {
|
|
18472
|
-
return JSON.parse(
|
|
18476
|
+
return JSON.parse(readFileSync32(p, "utf-8")).version || "unknown";
|
|
18473
18477
|
} catch {
|
|
18474
18478
|
}
|
|
18475
18479
|
}
|
|
18476
18480
|
return "unknown";
|
|
18477
18481
|
}
|
|
18482
|
+
function readEngineVersion() {
|
|
18483
|
+
if (_loaded === null) _loaded = readFromDisk();
|
|
18484
|
+
return _loaded;
|
|
18485
|
+
}
|
|
18486
|
+
var _loaded, _pkgDirOverride;
|
|
18487
|
+
var init_version = __esm({
|
|
18488
|
+
"src/version.ts"() {
|
|
18489
|
+
"use strict";
|
|
18490
|
+
_loaded = null;
|
|
18491
|
+
_pkgDirOverride = null;
|
|
18492
|
+
}
|
|
18493
|
+
});
|
|
18494
|
+
|
|
18495
|
+
// src/startup/api-status.ts
|
|
18496
|
+
var api_status_exports = {};
|
|
18497
|
+
__export(api_status_exports, {
|
|
18498
|
+
buildEngineStatus: () => buildEngineStatus,
|
|
18499
|
+
readEngineVersion: () => readEngineVersion
|
|
18500
|
+
});
|
|
18478
18501
|
function hasUsable(v) {
|
|
18479
18502
|
return !!v && !isUnresolvedEnvRef(v);
|
|
18480
18503
|
}
|
|
@@ -18497,6 +18520,7 @@ var init_api_status = __esm({
|
|
|
18497
18520
|
"src/startup/api-status.ts"() {
|
|
18498
18521
|
"use strict";
|
|
18499
18522
|
init_loader();
|
|
18523
|
+
init_version();
|
|
18500
18524
|
}
|
|
18501
18525
|
});
|
|
18502
18526
|
|
|
@@ -19073,8 +19097,8 @@ __export(voice_chat_tts_daemon_exports, {
|
|
|
19073
19097
|
});
|
|
19074
19098
|
import { spawn as spawn5 } from "child_process";
|
|
19075
19099
|
import net2 from "net";
|
|
19076
|
-
import
|
|
19077
|
-
import
|
|
19100
|
+
import path38 from "path";
|
|
19101
|
+
import fs38 from "fs";
|
|
19078
19102
|
function _log(msg2) {
|
|
19079
19103
|
console.log(`[tts-daemon] ${msg2}`);
|
|
19080
19104
|
}
|
|
@@ -19119,16 +19143,16 @@ async function startTtsDaemon(warmupCfg) {
|
|
|
19119
19143
|
return;
|
|
19120
19144
|
}
|
|
19121
19145
|
const script = (() => {
|
|
19122
|
-
const distDir =
|
|
19123
|
-
const
|
|
19124
|
-
|
|
19125
|
-
|
|
19146
|
+
const distDir = path38.dirname(new URL(import.meta.url).pathname);
|
|
19147
|
+
const candidates2 = [
|
|
19148
|
+
path38.join(distDir, "..", "python", "tts_daemon.py"),
|
|
19149
|
+
path38.join(process.cwd(), "engine/src/python/tts_daemon.py"),
|
|
19126
19150
|
"/Users/chongzhang/work/twinsun-hearth/engine/src/python/tts_daemon.py"
|
|
19127
19151
|
];
|
|
19128
|
-
for (const c of
|
|
19129
|
-
if (
|
|
19152
|
+
for (const c of candidates2) {
|
|
19153
|
+
if (fs38.existsSync(c)) return c;
|
|
19130
19154
|
}
|
|
19131
|
-
return
|
|
19155
|
+
return candidates2[0];
|
|
19132
19156
|
})();
|
|
19133
19157
|
_log(`spawning ${script}`);
|
|
19134
19158
|
const env = {
|
|
@@ -20595,7 +20619,7 @@ var init_builtInAgents = __esm({
|
|
|
20595
20619
|
});
|
|
20596
20620
|
|
|
20597
20621
|
// src/tools/AgentTool/forkSubagent.ts
|
|
20598
|
-
import { randomUUID as
|
|
20622
|
+
import { randomUUID as randomUUID9 } from "crypto";
|
|
20599
20623
|
function isForkSubagentEnabled() {
|
|
20600
20624
|
return true;
|
|
20601
20625
|
}
|
|
@@ -20634,7 +20658,7 @@ function buildForkedMessages(directive, parentMessages) {
|
|
|
20634
20658
|
type: "tool_result",
|
|
20635
20659
|
tool_use_id: block.id,
|
|
20636
20660
|
content: FORK_PLACEHOLDER_RESULT,
|
|
20637
|
-
id: `toolr_${
|
|
20661
|
+
id: `toolr_${randomUUID9().replace(/-/g, "").slice(0, 24)}`
|
|
20638
20662
|
}));
|
|
20639
20663
|
const toolResultMessage = msg_user(
|
|
20640
20664
|
buildChildMessage(directive),
|
|
@@ -20697,10 +20721,10 @@ var init_forkSubagent = __esm({
|
|
|
20697
20721
|
});
|
|
20698
20722
|
|
|
20699
20723
|
// src/tools/AgentTool/runAgent.ts
|
|
20700
|
-
import { randomBytes as
|
|
20724
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
20701
20725
|
function generateAgentId() {
|
|
20702
20726
|
const prefix = "a";
|
|
20703
|
-
const bytes =
|
|
20727
|
+
const bytes = randomBytes7(8);
|
|
20704
20728
|
let id = prefix;
|
|
20705
20729
|
for (let i = 0; i < 8; i++) {
|
|
20706
20730
|
id += TASK_ID_ALPHABET2[bytes[i] % TASK_ID_ALPHABET2.length];
|
|
@@ -21831,7 +21855,7 @@ var init_TeamDeleteTool = __esm({
|
|
|
21831
21855
|
},
|
|
21832
21856
|
isEnabled: () => isAgentSwarmsEnabled(),
|
|
21833
21857
|
handler: async (_args, ctx) => {
|
|
21834
|
-
const teamName = ctx.teamName;
|
|
21858
|
+
const teamName = ctx.teamName || getLeaderTeamName() || void 0;
|
|
21835
21859
|
if (teamName) {
|
|
21836
21860
|
const teamFile = readTeamFile(teamName);
|
|
21837
21861
|
if (teamFile) {
|
|
@@ -21915,7 +21939,7 @@ function getAgentName(ctx) {
|
|
|
21915
21939
|
return ctx.agentName || TEAM_LEAD_NAME;
|
|
21916
21940
|
}
|
|
21917
21941
|
function getTeamName(ctx) {
|
|
21918
|
-
return ctx.teamName;
|
|
21942
|
+
return ctx.teamName || getLeaderTeamName() || void 0;
|
|
21919
21943
|
}
|
|
21920
21944
|
async function handleMessage(recipientName, content, summary, ctx) {
|
|
21921
21945
|
const teamName = getTeamName(ctx);
|
|
@@ -23247,17 +23271,33 @@ ${lines.join("\n\n")}`;
|
|
|
23247
23271
|
init_loader();
|
|
23248
23272
|
|
|
23249
23273
|
// src/config/secrets-env.ts
|
|
23274
|
+
import { createHash } from "node:crypto";
|
|
23250
23275
|
import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
23251
|
-
import { basename, dirname, join as join2 } from "node:path";
|
|
23276
|
+
import { basename, dirname, join as join2, normalize } from "node:path";
|
|
23252
23277
|
function secretsHome() {
|
|
23253
23278
|
return process.env.HOME || process.env.USERPROFILE || ".";
|
|
23254
23279
|
}
|
|
23280
|
+
function normalizeStateDir(stateDir) {
|
|
23281
|
+
return normalize(stateDir).split("\\").join("/").replace(/\/+$/, "").toLowerCase();
|
|
23282
|
+
}
|
|
23283
|
+
function stateKey(stateDir) {
|
|
23284
|
+
return createHash("sha256").update(normalizeStateDir(stateDir)).digest("hex").slice(0, 8);
|
|
23285
|
+
}
|
|
23286
|
+
function isDefaultStateDir(stateDir) {
|
|
23287
|
+
return normalizeStateDir(stateDir) === normalizeStateDir(join2(secretsHome(), ".engine7"));
|
|
23288
|
+
}
|
|
23255
23289
|
function secretsCandidates(stateDir, configName) {
|
|
23256
|
-
|
|
23257
|
-
|
|
23290
|
+
const shared = join2(secretsHome(), ".engine7-secrets", `${configName}.env`);
|
|
23291
|
+
const inState = [
|
|
23258
23292
|
join2(stateDir, "configs", `.env.${configName}`),
|
|
23259
23293
|
join2(stateDir, "configs", ".env")
|
|
23260
23294
|
];
|
|
23295
|
+
if (isDefaultStateDir(stateDir)) return [shared, ...inState];
|
|
23296
|
+
return [
|
|
23297
|
+
join2(secretsHome(), ".engine7-secrets", `${configName}-${stateKey(stateDir)}.env`),
|
|
23298
|
+
shared,
|
|
23299
|
+
...inState
|
|
23300
|
+
];
|
|
23261
23301
|
}
|
|
23262
23302
|
function loadSecretsEnv(stateDir, configName) {
|
|
23263
23303
|
for (const p of secretsCandidates(stateDir, configName)) {
|
|
@@ -23819,8 +23859,8 @@ function startConfigWatcher(config2, deps, provider) {
|
|
|
23819
23859
|
}
|
|
23820
23860
|
if (!fs10.existsSync(configPath2)) {
|
|
23821
23861
|
const __filename2 = fileURLToPath(import.meta.url);
|
|
23822
|
-
const
|
|
23823
|
-
configPath2 = path10.resolve(
|
|
23862
|
+
const __dirname2 = path10.dirname(__filename2);
|
|
23863
|
+
configPath2 = path10.resolve(__dirname2, "..", raw);
|
|
23824
23864
|
}
|
|
23825
23865
|
if (!fs10.existsSync(configPath2)) {
|
|
23826
23866
|
console.warn(`[config-watch] config path invalid: ${configPath2}, watcher disabled`);
|
|
@@ -25911,9 +25951,9 @@ function readDbEmbeddings(dbPath) {
|
|
|
25911
25951
|
db2.close();
|
|
25912
25952
|
}
|
|
25913
25953
|
}
|
|
25914
|
-
async function llmRerank(query,
|
|
25915
|
-
if (
|
|
25916
|
-
const manifest =
|
|
25954
|
+
async function llmRerank(query, candidates2, topK, ollamaChatUrl, model) {
|
|
25955
|
+
if (candidates2.length <= topK) return candidates2;
|
|
25956
|
+
const manifest = candidates2.map(
|
|
25917
25957
|
(c, i) => `${i + 1}. ${c.filename}
|
|
25918
25958
|
${c.description || c.name}`
|
|
25919
25959
|
).join("\n");
|
|
@@ -25945,7 +25985,7 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
|
|
|
25945
25985
|
});
|
|
25946
25986
|
if (!resp.ok) {
|
|
25947
25987
|
console.warn(`[memdir] rerank: ollama chat failed: ${resp.status}`);
|
|
25948
|
-
return
|
|
25988
|
+
return candidates2.slice(0, topK);
|
|
25949
25989
|
}
|
|
25950
25990
|
const data = await resp.json();
|
|
25951
25991
|
const text = data?.message?.content ?? "";
|
|
@@ -25954,19 +25994,19 @@ Select up to ${topK} most relevant (return their 1-indexed numbers as JSON {"sel
|
|
|
25954
25994
|
try {
|
|
25955
25995
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
25956
25996
|
if (parsed.selected && Array.isArray(parsed.selected)) {
|
|
25957
|
-
const indices = parsed.selected.filter((n) => n >= 1 && n <=
|
|
25958
|
-
const result = indices.map((i) =>
|
|
25959
|
-
console.log(`[memdir] rerank: ${model} selected ${result.length}/${
|
|
25997
|
+
const indices = parsed.selected.filter((n) => n >= 1 && n <= candidates2.length).map((n) => n - 1);
|
|
25998
|
+
const result = indices.map((i) => candidates2[i]);
|
|
25999
|
+
console.log(`[memdir] rerank: ${model} selected ${result.length}/${candidates2.length} candidates (indices: ${indices.map((i) => i + 1).join(",")})`);
|
|
25960
26000
|
return result.slice(0, topK);
|
|
25961
26001
|
}
|
|
25962
26002
|
} catch {
|
|
25963
26003
|
}
|
|
25964
26004
|
}
|
|
25965
26005
|
console.warn(`[memdir] rerank: no valid JSON in response (${text.length} chars), falling back to vector order`);
|
|
25966
|
-
return
|
|
26006
|
+
return candidates2.slice(0, topK);
|
|
25967
26007
|
} catch (e) {
|
|
25968
26008
|
console.warn(`[memdir] rerank: error: ${e?.message ?? e}, falling back to vector order`);
|
|
25969
|
-
return
|
|
26009
|
+
return candidates2.slice(0, topK);
|
|
25970
26010
|
}
|
|
25971
26011
|
}
|
|
25972
26012
|
async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /* @__PURE__ */ new Set(), options) {
|
|
@@ -26015,17 +26055,17 @@ async function findRelevantMemoriesVector(query, memoryDir, alreadySurfaced = /*
|
|
|
26015
26055
|
}
|
|
26016
26056
|
}
|
|
26017
26057
|
scored.sort((a, b) => b.score - a.score);
|
|
26018
|
-
const
|
|
26058
|
+
const candidates2 = scored.slice(0, CANDIDATE_POOL);
|
|
26019
26059
|
const vectorMs = Date.now() - t0;
|
|
26020
26060
|
console.log(
|
|
26021
|
-
`[memdir] vector recall: ${
|
|
26061
|
+
`[memdir] vector recall: ${candidates2.length} candidates in ${vectorMs}ms (embed=${embedMs}ms, scanned=${rows.length} files, minScore=${minScore})`
|
|
26022
26062
|
);
|
|
26023
|
-
if (
|
|
26024
|
-
console.log(`[memdir] vector top scores: ${
|
|
26063
|
+
if (candidates2.length > 0) {
|
|
26064
|
+
console.log(`[memdir] vector top scores: ${candidates2.slice(0, 5).map((r) => `${r.filename}=${r.score.toFixed(3)}`).join(", ")}${candidates2.length > 5 ? "..." : ""}`);
|
|
26025
26065
|
}
|
|
26026
|
-
if (
|
|
26066
|
+
if (candidates2.length === 0) return [];
|
|
26027
26067
|
const rerankT0 = Date.now();
|
|
26028
|
-
const reranked = await llmRerank(query,
|
|
26068
|
+
const reranked = await llmRerank(query, candidates2, topK, DEFAULT_OLLAMA_CHAT_URL, rerankModel);
|
|
26029
26069
|
const rerankMs = Date.now() - rerankT0;
|
|
26030
26070
|
const totalMs = Date.now() - t0;
|
|
26031
26071
|
console.log(`[memdir] recall total: ${totalMs}ms (vector=${vectorMs}ms, rerank=${rerankMs}ms)`);
|
|
@@ -30793,6 +30833,8 @@ function startInboxPoller(ctx) {
|
|
|
30793
30833
|
const INBOX_POLL_INTERVAL_MS = 2e3;
|
|
30794
30834
|
const INBOX_IDLE_INTERVAL_MS = 3e4;
|
|
30795
30835
|
let pollCycleCount = 0;
|
|
30836
|
+
let lastStatusLine = "";
|
|
30837
|
+
let warnedNoPreview = false;
|
|
30796
30838
|
let inboxPollerTimer = null;
|
|
30797
30839
|
let inboxPollerStandby = false;
|
|
30798
30840
|
function runInboxPollCycle() {
|
|
@@ -30829,7 +30871,11 @@ function startInboxPoller(ctx) {
|
|
|
30829
30871
|
).join(" ");
|
|
30830
30872
|
const isMainActive = dispatcher.isActive("main");
|
|
30831
30873
|
const mainPending = dispatcher.pendingCount("main");
|
|
30832
|
-
|
|
30874
|
+
const statusLine = `teammates=[${statusSummary}] mainActive=${isMainActive} mainPending=${mainPending}`;
|
|
30875
|
+
if (statusLine !== lastStatusLine || pollCycleCount % 15 === 0) {
|
|
30876
|
+
lastStatusLine = statusLine;
|
|
30877
|
+
console.log(`[inbox-poller] #${pollCycleCount} ${statusLine}`);
|
|
30878
|
+
}
|
|
30833
30879
|
const now = Date.now();
|
|
30834
30880
|
const allTeamNames = new Set(teammates.filter((t) => t.status !== "killed").map((t) => t.identity.teamName));
|
|
30835
30881
|
const teams = [...allTeamNames];
|
|
@@ -30907,7 +30953,6 @@ function startInboxPoller(ctx) {
|
|
|
30907
30953
|
${notifications}
|
|
30908
30954
|
[/teammate-status]${taskSummary}`;
|
|
30909
30955
|
const inboxTarget = "";
|
|
30910
|
-
const inboxPreview = null;
|
|
30911
30956
|
const submitted = dispatcher.submitMessage({
|
|
30912
30957
|
text: queryContent,
|
|
30913
30958
|
sessionId: "main",
|
|
@@ -30917,21 +30962,12 @@ ${notifications}
|
|
|
30917
30962
|
priority: "next",
|
|
30918
30963
|
// 对齐 CC: idle 时立即提交,不是 'later' 排队
|
|
30919
30964
|
callbacks: {
|
|
30920
|
-
onText: (
|
|
30921
|
-
if (
|
|
30922
|
-
|
|
30923
|
-
|
|
30924
|
-
console.warn(`[inbox-poller:onText] no preview target, Discord output dropped (${text.length} chars)`);
|
|
30925
|
-
}
|
|
30965
|
+
onText: (_text) => {
|
|
30966
|
+
if (warnedNoPreview) return;
|
|
30967
|
+
warnedNoPreview = true;
|
|
30968
|
+
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");
|
|
30926
30969
|
},
|
|
30927
|
-
onResult: async (
|
|
30928
|
-
if (inboxPreview) {
|
|
30929
|
-
const delivered = await inboxPreview.finish(content.trim() || "(\u5904\u7406\u5B8C\u6210)");
|
|
30930
|
-
if (!delivered && inboxTarget) {
|
|
30931
|
-
channelManager.send("discord", inboxTarget, content.trim() || "(\u5904\u7406\u5B8C\u6210)").catch(() => {
|
|
30932
|
-
});
|
|
30933
|
-
}
|
|
30934
|
-
}
|
|
30970
|
+
onResult: async (_content) => {
|
|
30935
30971
|
}
|
|
30936
30972
|
},
|
|
30937
30973
|
deps
|
|
@@ -31625,8 +31661,8 @@ var VoiceChatPlugin = class {
|
|
|
31625
31661
|
// src/memory/everos/plugin.ts
|
|
31626
31662
|
import { spawn as spawn4 } from "node:child_process";
|
|
31627
31663
|
import net from "node:net";
|
|
31628
|
-
import
|
|
31629
|
-
import
|
|
31664
|
+
import path35 from "node:path";
|
|
31665
|
+
import fs35 from "node:fs";
|
|
31630
31666
|
|
|
31631
31667
|
// src/memory/everos/config.ts
|
|
31632
31668
|
var DEFAULTS4 = {
|
|
@@ -31860,8 +31896,8 @@ var EverosPlugin = class {
|
|
|
31860
31896
|
}, 3e5);
|
|
31861
31897
|
}
|
|
31862
31898
|
async startEveros() {
|
|
31863
|
-
const pythonDir =
|
|
31864
|
-
const configPath2 =
|
|
31899
|
+
const pythonDir = path35.dirname(this.config.lancedbPath);
|
|
31900
|
+
const configPath2 = path35.join(pythonDir, "config.toml");
|
|
31865
31901
|
await this.ensureFcntlCompat();
|
|
31866
31902
|
const venvPython = this.findVenvPython();
|
|
31867
31903
|
const everosBin = venvPython.replace(/python\.exe$/, "everos.exe");
|
|
@@ -31951,19 +31987,19 @@ var EverosPlugin = class {
|
|
|
31951
31987
|
return child;
|
|
31952
31988
|
}
|
|
31953
31989
|
findVenvPython() {
|
|
31954
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31990
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
31955
31991
|
if (process.platform === "win32") {
|
|
31956
|
-
return
|
|
31992
|
+
return path35.join(stateDir, "everos-venv", "Scripts", "python.exe");
|
|
31957
31993
|
}
|
|
31958
|
-
return
|
|
31994
|
+
return path35.join(stateDir, "everos-venv", "bin", "python");
|
|
31959
31995
|
}
|
|
31960
31996
|
/** 检测 venv 是否存在,不存在就自动创建 + 装 EverOS */
|
|
31961
31997
|
async ensureVenv() {
|
|
31962
31998
|
const venvPython = this.findVenvPython();
|
|
31963
|
-
if (
|
|
31964
|
-
const stateDir = process.env.ENGINE7_STATE_DIR ||
|
|
31965
|
-
const venvDir =
|
|
31966
|
-
const everosSrc =
|
|
31999
|
+
if (fs35.existsSync(venvPython)) return;
|
|
32000
|
+
const stateDir = process.env.ENGINE7_STATE_DIR || path35.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
|
|
32001
|
+
const venvDir = path35.join(stateDir, "everos-venv");
|
|
32002
|
+
const everosSrc = path35.join(stateDir, "workspace", "research", "EverOS");
|
|
31967
32003
|
console.log(`[everos] venv not found at ${venvDir}, auto-creating...`);
|
|
31968
32004
|
console.log(`[everos] \u23F3 This may take a few minutes on first run...`);
|
|
31969
32005
|
const pyCandidates = process.platform === "win32" ? ["python", "python3", "C:\\Python314\\python.exe", "C:\\Python313\\python.exe", "C:\\Python312\\python.exe"] : ["python3", "python"];
|
|
@@ -31985,9 +32021,9 @@ var EverosPlugin = class {
|
|
|
31985
32021
|
console.log(`[everos] Creating venv with ${sysPython}...`);
|
|
31986
32022
|
const { execSync: execSync3 } = await import("node:child_process");
|
|
31987
32023
|
execSync3(`"${sysPython}" -m venv "${venvDir}"`, { stdio: "pipe", shell: true });
|
|
31988
|
-
const pip = process.platform === "win32" ?
|
|
31989
|
-
const everosReq =
|
|
31990
|
-
if (
|
|
32024
|
+
const pip = process.platform === "win32" ? path35.join(venvDir, "Scripts", "pip.exe") : path35.join(venvDir, "bin", "pip");
|
|
32025
|
+
const everosReq = path35.join(this.getPythonDir(), "requirements.txt");
|
|
32026
|
+
if (fs35.existsSync(everosReq)) {
|
|
31991
32027
|
console.log(`[everos] Installing from requirements.txt...`);
|
|
31992
32028
|
execSync3(`"${pip}" install -r "${everosReq}" -q`, { stdio: "pipe", shell: true, timeout: 3e5 });
|
|
31993
32029
|
} else {
|
|
@@ -32002,29 +32038,29 @@ var EverosPlugin = class {
|
|
|
32002
32038
|
}
|
|
32003
32039
|
getPythonDir() {
|
|
32004
32040
|
const dir = import.meta.dirname;
|
|
32005
|
-
const
|
|
32006
|
-
|
|
32007
|
-
|
|
32008
|
-
|
|
32041
|
+
const candidates2 = [
|
|
32042
|
+
path35.join(dir, "python"),
|
|
32043
|
+
path35.resolve(dir, "..", "src", "memory", "everos", "python"),
|
|
32044
|
+
path35.resolve(dir, "..", "..", "..", "src", "memory", "everos", "python")
|
|
32009
32045
|
];
|
|
32010
|
-
for (const candidate of
|
|
32011
|
-
if (
|
|
32046
|
+
for (const candidate of candidates2) {
|
|
32047
|
+
if (fs35.existsSync(path35.join(candidate, "agentic_server.py"))) {
|
|
32012
32048
|
return candidate;
|
|
32013
32049
|
}
|
|
32014
32050
|
}
|
|
32015
|
-
return
|
|
32051
|
+
return candidates2[0];
|
|
32016
32052
|
}
|
|
32017
32053
|
async ensureFcntlCompat() {
|
|
32018
32054
|
if (process.platform !== "win32") return;
|
|
32019
32055
|
const venvPython = this.findVenvPython();
|
|
32020
|
-
const venvDir =
|
|
32021
|
-
const sitePackages =
|
|
32022
|
-
const target =
|
|
32023
|
-
if (
|
|
32024
|
-
const source =
|
|
32025
|
-
if (
|
|
32056
|
+
const venvDir = path35.dirname(path35.dirname(venvPython));
|
|
32057
|
+
const sitePackages = path35.join(venvDir, "Lib", "site-packages");
|
|
32058
|
+
const target = path35.join(sitePackages, "fcntl.py");
|
|
32059
|
+
if (fs35.existsSync(target)) return;
|
|
32060
|
+
const source = path35.join(this.getPythonDir(), "fcntl_compat.py");
|
|
32061
|
+
if (fs35.existsSync(source)) {
|
|
32026
32062
|
try {
|
|
32027
|
-
|
|
32063
|
+
fs35.copyFileSync(source, target);
|
|
32028
32064
|
console.log(`[everos] Installed fcntl compat shim to ${target}`);
|
|
32029
32065
|
} catch (err) {
|
|
32030
32066
|
console.warn(`[everos] Failed to install fcntl shim: ${err.message}`);
|
|
@@ -32633,8 +32669,8 @@ function createVisualBoot(ctx) {
|
|
|
32633
32669
|
}
|
|
32634
32670
|
|
|
32635
32671
|
// src/startup/message-pipeline.ts
|
|
32636
|
-
import * as
|
|
32637
|
-
import * as
|
|
32672
|
+
import * as fs36 from "node:fs";
|
|
32673
|
+
import * as path36 from "node:path";
|
|
32638
32674
|
|
|
32639
32675
|
// src/commands/registry.ts
|
|
32640
32676
|
var commands = /* @__PURE__ */ new Map();
|
|
@@ -33525,7 +33561,7 @@ function setupMessagePipeline(ctx) {
|
|
|
33525
33561
|
console.log(`[vision] Downloading image: ${att.filename}`);
|
|
33526
33562
|
let rawBuffer;
|
|
33527
33563
|
if (att.url.startsWith("file://")) {
|
|
33528
|
-
rawBuffer =
|
|
33564
|
+
rawBuffer = fs36.readFileSync(decodeURIComponent(att.url.slice(7)));
|
|
33529
33565
|
} else {
|
|
33530
33566
|
rawBuffer = await downloadImage2(att.url, attachDispatcher);
|
|
33531
33567
|
}
|
|
@@ -33533,8 +33569,8 @@ function setupMessagePipeline(ctx) {
|
|
|
33533
33569
|
const ext = detected.split("/")[1] || "png";
|
|
33534
33570
|
const resized = await maybeResizeAndDownsampleImageBuffer2(rawBuffer, rawBuffer.length, ext);
|
|
33535
33571
|
const imageId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
33536
|
-
const savedPath =
|
|
33537
|
-
|
|
33572
|
+
const savedPath = path36.join(config2.mediaDir, `${imageId}.${ext}`);
|
|
33573
|
+
fs36.writeFileSync(savedPath, resized.buffer);
|
|
33538
33574
|
savedPaths.push(savedPath);
|
|
33539
33575
|
console.log(`[vision] Saved: ${savedPath} (${resized.buffer.length}B)`);
|
|
33540
33576
|
imageBlocks.push({
|
|
@@ -33564,8 +33600,8 @@ ${pathStr}` }];
|
|
|
33564
33600
|
}
|
|
33565
33601
|
const nonImageAttachments = inbound.attachments?.filter((a) => !a.contentType.startsWith("image/"));
|
|
33566
33602
|
if (nonImageAttachments && nonImageAttachments.length > 0) {
|
|
33567
|
-
const outDir =
|
|
33568
|
-
|
|
33603
|
+
const outDir = path36.join(config2.mediaDir, sessionId);
|
|
33604
|
+
fs36.mkdirSync(outDir, { recursive: true });
|
|
33569
33605
|
const resolved = [];
|
|
33570
33606
|
for (const att of nonImageAttachments) {
|
|
33571
33607
|
console.log(`[file] Downloading: ${att.filename} (${att.contentType}, ${att.size}B)`);
|
|
@@ -33573,9 +33609,9 @@ ${pathStr}` }];
|
|
|
33573
33609
|
const resp = attachDispatcher ? await fetch(att.url, { dispatcher: attachDispatcher, signal: AbortSignal.timeout(3e4) }) : await fetch(att.url, { signal: AbortSignal.timeout(3e4) });
|
|
33574
33610
|
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
|
33575
33611
|
const buffer = Buffer.from(await resp.arrayBuffer());
|
|
33576
|
-
const safeName2 =
|
|
33577
|
-
const savedPath =
|
|
33578
|
-
|
|
33612
|
+
const safeName2 = path36.basename(att.filename).replace(/[<>:"/\\|?*\x00-\x1f]/g, "_") || "attachment";
|
|
33613
|
+
const savedPath = path36.join(outDir, safeName2);
|
|
33614
|
+
fs36.writeFileSync(savedPath, buffer);
|
|
33579
33615
|
resolved.push(savedPath);
|
|
33580
33616
|
console.log(`[file] Saved: ${savedPath} (${buffer.length}B)`);
|
|
33581
33617
|
} catch (err) {
|
|
@@ -33824,6 +33860,7 @@ ${pathStr}` }];
|
|
|
33824
33860
|
init_live();
|
|
33825
33861
|
|
|
33826
33862
|
// src/provision-free.ts
|
|
33863
|
+
import { randomBytes as randomBytes4, randomUUID as randomUUID6 } from "node:crypto";
|
|
33827
33864
|
import { chmodSync as chmodSync2, existsSync as existsSync24, mkdirSync as mkdirSync16, readFileSync as readFileSync35, writeFileSync as writeFileSync13 } from "node:fs";
|
|
33828
33865
|
import { dirname as dirname11, join as join46 } from "node:path";
|
|
33829
33866
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -33832,19 +33869,68 @@ var IDENTITY_FILE = "install-identity.json";
|
|
|
33832
33869
|
function readContactEmailOf(parsed) {
|
|
33833
33870
|
return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
|
|
33834
33871
|
}
|
|
33835
|
-
function
|
|
33836
|
-
|
|
33837
|
-
|
|
33838
|
-
|
|
33872
|
+
function loadOrCreateInstallIdentity(stateDir) {
|
|
33873
|
+
const file2 = join46(stateDir, IDENTITY_FILE);
|
|
33874
|
+
if (existsSync24(file2)) {
|
|
33875
|
+
let parsed;
|
|
33876
|
+
try {
|
|
33877
|
+
parsed = JSON.parse(readFileSync35(file2, "utf8"));
|
|
33878
|
+
} catch (e) {
|
|
33879
|
+
throw new Error(
|
|
33880
|
+
`${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`
|
|
33881
|
+
);
|
|
33882
|
+
}
|
|
33883
|
+
if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
|
|
33884
|
+
throw new Error(`${IDENTITY_FILE} \u7F3A installId / installSecret \u5B57\u6BB5`);
|
|
33885
|
+
}
|
|
33839
33886
|
const email = readContactEmailOf(parsed);
|
|
33840
33887
|
return {
|
|
33841
33888
|
installId: parsed.installId,
|
|
33842
33889
|
installSecret: parsed.installSecret,
|
|
33843
33890
|
...email ? { contactEmail: email } : {}
|
|
33844
33891
|
};
|
|
33892
|
+
}
|
|
33893
|
+
const identity = {
|
|
33894
|
+
// 服务端校验 installId 长度为 8..36、只允许字母数字和连字符;uuid 正好符合
|
|
33895
|
+
installId: randomUUID6(),
|
|
33896
|
+
// 服务端校验 installSecret 为 32..64 位十六进制
|
|
33897
|
+
installSecret: randomBytes4(32).toString("hex")
|
|
33898
|
+
};
|
|
33899
|
+
mkdirSync16(stateDir, { recursive: true });
|
|
33900
|
+
writeFileSync13(file2, JSON.stringify(identity, null, 2) + "\n", { mode: 384 });
|
|
33901
|
+
try {
|
|
33902
|
+
chmodSync2(file2, 384);
|
|
33845
33903
|
} catch {
|
|
33846
|
-
return null;
|
|
33847
33904
|
}
|
|
33905
|
+
return identity;
|
|
33906
|
+
}
|
|
33907
|
+
function readInstallIdentityDetailed(stateDir) {
|
|
33908
|
+
const file2 = join46(stateDir, IDENTITY_FILE);
|
|
33909
|
+
if (!existsSync24(file2)) return { ok: false, reason: "missing" };
|
|
33910
|
+
let text;
|
|
33911
|
+
try {
|
|
33912
|
+
text = readFileSync35(file2, "utf8");
|
|
33913
|
+
} catch (e) {
|
|
33914
|
+
return { ok: false, reason: "corrupt", detail: `\u5B58\u5728\u4F46\u8BFB\u4E0D\u4E86\uFF08${e?.message ?? e}\uFF09` };
|
|
33915
|
+
}
|
|
33916
|
+
let parsed;
|
|
33917
|
+
try {
|
|
33918
|
+
parsed = JSON.parse(text);
|
|
33919
|
+
} catch (e) {
|
|
33920
|
+
return { ok: false, reason: "corrupt", detail: `\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e?.message ?? e}\uFF09` };
|
|
33921
|
+
}
|
|
33922
|
+
if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") {
|
|
33923
|
+
return { ok: false, reason: "corrupt", detail: "\u7F3A installId / installSecret \u5B57\u6BB5" };
|
|
33924
|
+
}
|
|
33925
|
+
const email = readContactEmailOf(parsed);
|
|
33926
|
+
return {
|
|
33927
|
+
ok: true,
|
|
33928
|
+
identity: {
|
|
33929
|
+
installId: parsed.installId,
|
|
33930
|
+
installSecret: parsed.installSecret,
|
|
33931
|
+
...email ? { contactEmail: email } : {}
|
|
33932
|
+
}
|
|
33933
|
+
};
|
|
33848
33934
|
}
|
|
33849
33935
|
|
|
33850
33936
|
// src/optional/reporting/state.ts
|
|
@@ -33871,10 +33957,10 @@ function writeReportState(stateDir, s) {
|
|
|
33871
33957
|
}
|
|
33872
33958
|
|
|
33873
33959
|
// src/optional/reporting/payload.ts
|
|
33874
|
-
import { createHash as
|
|
33960
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
33875
33961
|
import * as os3 from "node:os";
|
|
33876
33962
|
function installKeyOf(installId, installSecret) {
|
|
33877
|
-
return
|
|
33963
|
+
return createHash3("sha256").update(installId + installSecret).digest("hex").slice(0, 32);
|
|
33878
33964
|
}
|
|
33879
33965
|
function buildReportPayload(opts) {
|
|
33880
33966
|
const p = {
|
|
@@ -34020,13 +34106,23 @@ function startReporting(opts) {
|
|
|
34020
34106
|
const log = opts.log ?? (() => {
|
|
34021
34107
|
});
|
|
34022
34108
|
const startedAt = nowFn3();
|
|
34023
|
-
const
|
|
34024
|
-
|
|
34025
|
-
|
|
34026
|
-
|
|
34027
|
-
|
|
34028
|
-
|
|
34109
|
+
const identityRead = readInstallIdentityDetailed(stateDir);
|
|
34110
|
+
let maybeIdentity = null;
|
|
34111
|
+
if (identityRead.ok) {
|
|
34112
|
+
maybeIdentity = identityRead.identity;
|
|
34113
|
+
} else if (identityRead.reason === "missing") {
|
|
34114
|
+
try {
|
|
34115
|
+
maybeIdentity = loadOrCreateInstallIdentity(stateDir);
|
|
34116
|
+
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");
|
|
34117
|
+
} catch (e) {
|
|
34118
|
+
log(`[reporting] \u8865\u5EFA\u88C5\u673A\u8EAB\u4EFD\u5931\u8D25\uFF1A${e?.message ?? e} \u2014\u2014 \u4E0A\u62A5\u5173\u95ED`);
|
|
34119
|
+
}
|
|
34120
|
+
} else {
|
|
34121
|
+
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`);
|
|
34029
34122
|
}
|
|
34123
|
+
if (!maybeIdentity) return { stop() {
|
|
34124
|
+
}, async beatNow() {
|
|
34125
|
+
} };
|
|
34030
34126
|
const identity = maybeIdentity;
|
|
34031
34127
|
const bootState = readReportState(stateDir);
|
|
34032
34128
|
restoreCounters(bootState);
|
|
@@ -35309,8 +35405,8 @@ var FeishuAdapter = class _FeishuAdapter {
|
|
|
35309
35405
|
|
|
35310
35406
|
// src/channels/wechat.ts
|
|
35311
35407
|
import * as crypto5 from "crypto";
|
|
35312
|
-
import * as
|
|
35313
|
-
import * as
|
|
35408
|
+
import * as fs37 from "fs";
|
|
35409
|
+
import * as path37 from "path";
|
|
35314
35410
|
import * as os4 from "os";
|
|
35315
35411
|
import { promises as dns } from "dns";
|
|
35316
35412
|
function sleep3(ms) {
|
|
@@ -35534,7 +35630,7 @@ function guessChatType(msg2, accountId) {
|
|
|
35534
35630
|
return isGroup ? { type: "group", chatId: roomId || toId || String(msg2.from_user_id || "") } : { type: "dm", chatId: String(msg2.from_user_id || "") };
|
|
35535
35631
|
}
|
|
35536
35632
|
function mimeFromFilename(filename) {
|
|
35537
|
-
const ext =
|
|
35633
|
+
const ext = path37.extname(filename).toLowerCase();
|
|
35538
35634
|
const map = {
|
|
35539
35635
|
".jpg": "image/jpeg",
|
|
35540
35636
|
".jpeg": "image/jpeg",
|
|
@@ -35601,15 +35697,15 @@ var ContextTokenStore = class {
|
|
|
35601
35697
|
cache = /* @__PURE__ */ new Map();
|
|
35602
35698
|
filePath;
|
|
35603
35699
|
constructor(stateDir, accountId) {
|
|
35604
|
-
this.filePath =
|
|
35700
|
+
this.filePath = path37.join(stateDir, "weixin", `${accountId}.context-tokens.json`);
|
|
35605
35701
|
}
|
|
35606
35702
|
k(a, p) {
|
|
35607
35703
|
return `${a}:${p}`;
|
|
35608
35704
|
}
|
|
35609
35705
|
restore() {
|
|
35610
35706
|
try {
|
|
35611
|
-
if (
|
|
35612
|
-
for (const [k, v] of Object.entries(JSON.parse(
|
|
35707
|
+
if (fs37.existsSync(this.filePath)) {
|
|
35708
|
+
for (const [k, v] of Object.entries(JSON.parse(fs37.readFileSync(this.filePath, "utf-8"))))
|
|
35613
35709
|
this.cache.set(k, String(v));
|
|
35614
35710
|
}
|
|
35615
35711
|
} catch {
|
|
@@ -35617,11 +35713,11 @@ var ContextTokenStore = class {
|
|
|
35617
35713
|
}
|
|
35618
35714
|
save() {
|
|
35619
35715
|
try {
|
|
35620
|
-
const dir =
|
|
35621
|
-
if (!
|
|
35716
|
+
const dir = path37.dirname(this.filePath);
|
|
35717
|
+
if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
|
|
35622
35718
|
const obj = {};
|
|
35623
35719
|
this.cache.forEach((v, k) => obj[k] = v);
|
|
35624
|
-
|
|
35720
|
+
fs37.writeFileSync(this.filePath, JSON.stringify(obj));
|
|
35625
35721
|
} catch {
|
|
35626
35722
|
}
|
|
35627
35723
|
}
|
|
@@ -35641,18 +35737,18 @@ var ContextTokenStore = class {
|
|
|
35641
35737
|
};
|
|
35642
35738
|
function loadSyncBuf(stateDir, accountId) {
|
|
35643
35739
|
try {
|
|
35644
|
-
const fp =
|
|
35645
|
-
if (
|
|
35740
|
+
const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35741
|
+
if (fs37.existsSync(fp)) return String(JSON.parse(fs37.readFileSync(fp, "utf-8")).sync_buf || "");
|
|
35646
35742
|
} catch {
|
|
35647
35743
|
}
|
|
35648
35744
|
return "";
|
|
35649
35745
|
}
|
|
35650
35746
|
function saveSyncBuf(stateDir, accountId, buf) {
|
|
35651
35747
|
try {
|
|
35652
|
-
const fp =
|
|
35653
|
-
const dir =
|
|
35654
|
-
if (!
|
|
35655
|
-
|
|
35748
|
+
const fp = path37.join(stateDir, "weixin", `${accountId}.sync.json`);
|
|
35749
|
+
const dir = path37.dirname(fp);
|
|
35750
|
+
if (!fs37.existsSync(dir)) fs37.mkdirSync(dir, { recursive: true });
|
|
35751
|
+
fs37.writeFileSync(fp, JSON.stringify({ sync_buf: buf }));
|
|
35656
35752
|
} catch {
|
|
35657
35753
|
}
|
|
35658
35754
|
}
|
|
@@ -35694,8 +35790,8 @@ var WechatAdapter = class {
|
|
|
35694
35790
|
this.config = config2;
|
|
35695
35791
|
this.baseUrl = config2.baseUrl?.replace(/\/$/, "") || ILINK_BASE_URL;
|
|
35696
35792
|
this.cdnBaseUrl = config2.cdnBaseUrl?.replace(/\/$/, "") || WEIXIN_CDN_BASE_URL;
|
|
35697
|
-
this.stateDir = config2.stateDir ||
|
|
35698
|
-
if (!
|
|
35793
|
+
this.stateDir = config2.stateDir || path37.join(os4.homedir?.() || "/tmp", ".engine7");
|
|
35794
|
+
if (!fs37.existsSync(this.stateDir)) fs37.mkdirSync(this.stateDir, { recursive: true });
|
|
35699
35795
|
}
|
|
35700
35796
|
// --- ChannelAdapter interface ---
|
|
35701
35797
|
onMessage(handler2) {
|
|
@@ -35748,8 +35844,8 @@ var WechatAdapter = class {
|
|
|
35748
35844
|
}
|
|
35749
35845
|
async sendFile(target, message, attachment) {
|
|
35750
35846
|
const filePath = attachment.path;
|
|
35751
|
-
if (!
|
|
35752
|
-
const plaintext =
|
|
35847
|
+
if (!fs37.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
35848
|
+
const plaintext = fs37.readFileSync(filePath);
|
|
35753
35849
|
const mediaType = this.guessMediaType(filePath, attachment.mimeType);
|
|
35754
35850
|
const filekey = crypto5.randomBytes(16).toString("hex");
|
|
35755
35851
|
const aesKey = crypto5.randomBytes(16);
|
|
@@ -35781,7 +35877,7 @@ var WechatAdapter = class {
|
|
|
35781
35877
|
aesKeyForApi,
|
|
35782
35878
|
ciphertextSize: ciphertext.length,
|
|
35783
35879
|
plaintextSize: rawsize,
|
|
35784
|
-
filename:
|
|
35880
|
+
filename: path37.basename(filePath),
|
|
35785
35881
|
rawfilemd5,
|
|
35786
35882
|
voiceDurationSec: attachment.voiceDurationSec
|
|
35787
35883
|
});
|
|
@@ -36040,10 +36136,10 @@ var WechatAdapter = class {
|
|
|
36040
36136
|
mediaRef.full_url,
|
|
36041
36137
|
timeoutSec
|
|
36042
36138
|
);
|
|
36043
|
-
const tmpDir =
|
|
36044
|
-
if (!
|
|
36045
|
-
const tmpPath =
|
|
36046
|
-
|
|
36139
|
+
const tmpDir = path37.join(os4.tmpdir(), "weixin-media");
|
|
36140
|
+
if (!fs37.existsSync(tmpDir)) fs37.mkdirSync(tmpDir, { recursive: true });
|
|
36141
|
+
const tmpPath = path37.join(tmpDir, `${crypto5.randomUUID().replace(/-/g, "")}-${filename}`);
|
|
36142
|
+
fs37.writeFileSync(tmpPath, data);
|
|
36047
36143
|
return { url: `file://${tmpPath}`, filename, contentType, size: data.length };
|
|
36048
36144
|
}
|
|
36049
36145
|
// --- Internal: send helpers ---
|
|
@@ -36093,7 +36189,7 @@ var WechatAdapter = class {
|
|
|
36093
36189
|
throw lastError;
|
|
36094
36190
|
}
|
|
36095
36191
|
guessMediaType(filePath, mimeType) {
|
|
36096
|
-
const ext =
|
|
36192
|
+
const ext = path37.extname(filePath).toLowerCase();
|
|
36097
36193
|
const mime = mimeType || mimeFromFilename(filePath);
|
|
36098
36194
|
if (mime.startsWith("image/")) return MEDIA_IMAGE;
|
|
36099
36195
|
if (mime.startsWith("video/")) return MEDIA_VIDEO;
|
|
@@ -36139,12 +36235,12 @@ init_voice_chat_tts_daemon();
|
|
|
36139
36235
|
import { execFile as execFile2 } from "node:child_process";
|
|
36140
36236
|
import { createInterface as createInterface2 } from "node:readline";
|
|
36141
36237
|
import { promisify as promisify2 } from "node:util";
|
|
36142
|
-
import * as
|
|
36143
|
-
import * as
|
|
36238
|
+
import * as fs39 from "node:fs";
|
|
36239
|
+
import * as path39 from "node:path";
|
|
36144
36240
|
import * as os5 from "node:os";
|
|
36145
36241
|
import net3 from "net";
|
|
36146
36242
|
var execFileAsync2 = promisify2(execFile2);
|
|
36147
|
-
var VOICE_DIR =
|
|
36243
|
+
var VOICE_DIR = path39.join(os5.tmpdir(), "engine-voice");
|
|
36148
36244
|
function wavDurationSec(buf) {
|
|
36149
36245
|
if (buf.length < 12 || buf.toString("ascii", 0, 4) !== "RIFF" || buf.toString("ascii", 8, 12) !== "WAVE") {
|
|
36150
36246
|
throw new Error("wavDurationSec: not a RIFF/WAVE file");
|
|
@@ -36193,17 +36289,17 @@ function resolvePython(configuredPath) {
|
|
|
36193
36289
|
}
|
|
36194
36290
|
if (_pyCache) return _pyCache;
|
|
36195
36291
|
const localAppData = process.env.LOCALAPPDATA;
|
|
36196
|
-
const
|
|
36292
|
+
const candidates2 = [
|
|
36197
36293
|
"python3",
|
|
36198
36294
|
"python",
|
|
36199
36295
|
// LOCALAPPDATA 缺席(非 Windows / 精简环境)时**不加这两个候选**:
|
|
36200
36296
|
// 拼出来会是相对路径 'Microsoft/WindowsApps/python.exe',那可能撞上 cwd 里的东西。
|
|
36201
36297
|
...localAppData ? [
|
|
36202
|
-
|
|
36203
|
-
|
|
36298
|
+
path39.join(localAppData, "Microsoft", "WindowsApps", "python3.exe"),
|
|
36299
|
+
path39.join(localAppData, "Microsoft", "WindowsApps", "python.exe")
|
|
36204
36300
|
] : []
|
|
36205
36301
|
];
|
|
36206
|
-
for (const c of
|
|
36302
|
+
for (const c of candidates2) {
|
|
36207
36303
|
try {
|
|
36208
36304
|
__require("child_process").execFileSync(c, ["-c", "1"], { timeout: 5e3, stdio: "ignore" });
|
|
36209
36305
|
_pyCache = c;
|
|
@@ -36220,8 +36316,8 @@ async function ttsCosyvoice(text, apiKey, model, voice, workspaceId, instruction
|
|
|
36220
36316
|
}
|
|
36221
36317
|
async function ttsCosyvoiceStream(text, cfg, onChunk) {
|
|
36222
36318
|
const { apiKey, model, voice, workspaceId, instruction } = cfg;
|
|
36223
|
-
|
|
36224
|
-
const output =
|
|
36319
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36320
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36225
36321
|
const script = `
|
|
36226
36322
|
import sys, json, base64, threading
|
|
36227
36323
|
import dashscope
|
|
@@ -36306,7 +36402,7 @@ print('EOS')
|
|
|
36306
36402
|
header.writeUInt16LE(16, 34);
|
|
36307
36403
|
header.write("data", 36);
|
|
36308
36404
|
header.writeUInt32LE(pcm.length, 40);
|
|
36309
|
-
|
|
36405
|
+
fs39.writeFileSync(output, Buffer.concat([header, pcm]));
|
|
36310
36406
|
return output;
|
|
36311
36407
|
}
|
|
36312
36408
|
async function ttsCosyvoiceWorker(text, cfg, onChunk) {
|
|
@@ -36416,9 +36512,9 @@ async function ttsQwen3Opus(text, cfg) {
|
|
|
36416
36512
|
const ogg = Buffer.from(await res.arrayBuffer());
|
|
36417
36513
|
if (ogg.length < 20) throw new Error("qwen3-3060 opus: empty output");
|
|
36418
36514
|
const durationMs = parseInt(res.headers.get("x-duration-ms") || "0", 10);
|
|
36419
|
-
|
|
36420
|
-
const oggPath =
|
|
36421
|
-
|
|
36515
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36516
|
+
const oggPath = path39.join(VOICE_DIR, `tts_${Date.now()}.ogg`);
|
|
36517
|
+
fs39.writeFileSync(oggPath, ogg);
|
|
36422
36518
|
return { oggPath, durationMs };
|
|
36423
36519
|
}
|
|
36424
36520
|
async function ttsGptsovits(text, c) {
|
|
@@ -36430,8 +36526,8 @@ async function ttsGptsovits(text, c) {
|
|
|
36430
36526
|
"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
36527
|
);
|
|
36432
36528
|
}
|
|
36433
|
-
|
|
36434
|
-
const output =
|
|
36529
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36530
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36435
36531
|
const params = new URLSearchParams({
|
|
36436
36532
|
text,
|
|
36437
36533
|
text_language: "zh",
|
|
@@ -36442,13 +36538,13 @@ async function ttsGptsovits(text, c) {
|
|
|
36442
36538
|
const res = await fetch(`${url}/?${params}`);
|
|
36443
36539
|
if (!res.ok) throw new Error(`GPT-SoVITS API ${res.status}`);
|
|
36444
36540
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
36445
|
-
|
|
36541
|
+
fs39.writeFileSync(output, buf);
|
|
36446
36542
|
return output;
|
|
36447
36543
|
}
|
|
36448
36544
|
var EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
|
|
36449
36545
|
async function ttsEdge(text) {
|
|
36450
|
-
|
|
36451
|
-
const output =
|
|
36546
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36547
|
+
const output = path39.join(VOICE_DIR, `tts_${Date.now()}.mp3`);
|
|
36452
36548
|
const script = `
|
|
36453
36549
|
import asyncio, edge_tts, sys
|
|
36454
36550
|
async def main():
|
|
@@ -36493,8 +36589,8 @@ async function synthQwen3(text, _sink) {
|
|
|
36493
36589
|
const pcmChunks = [];
|
|
36494
36590
|
await ttsQwen3Local(text, { url: c.url, voice: c.voice, lang: c.lang }, (pcm2) => pcmChunks.push(pcm2));
|
|
36495
36591
|
const pcm = Buffer.concat(pcmChunks);
|
|
36496
|
-
|
|
36497
|
-
const wavPath =
|
|
36592
|
+
fs39.mkdirSync(VOICE_DIR, { recursive: true });
|
|
36593
|
+
const wavPath = path39.join(VOICE_DIR, `tts_${Date.now()}.wav`);
|
|
36498
36594
|
const header = Buffer.alloc(44);
|
|
36499
36595
|
header.write("RIFF", 0);
|
|
36500
36596
|
header.writeUInt32LE(36 + pcm.length, 4);
|
|
@@ -36509,7 +36605,7 @@ async function synthQwen3(text, _sink) {
|
|
|
36509
36605
|
header.writeUInt16LE(16, 34);
|
|
36510
36606
|
header.write("data", 36);
|
|
36511
36607
|
header.writeUInt32LE(pcm.length, 40);
|
|
36512
|
-
|
|
36608
|
+
fs39.writeFileSync(wavPath, Buffer.concat([header, pcm]));
|
|
36513
36609
|
return { path: wavPath, format: "wav", unit: unit(text), voice: c.voice };
|
|
36514
36610
|
}
|
|
36515
36611
|
}
|
|
@@ -36828,8 +36924,365 @@ var DeskBuddyAdapter = class {
|
|
|
36828
36924
|
}
|
|
36829
36925
|
};
|
|
36830
36926
|
|
|
36927
|
+
// src/channels/cc-bridge.ts
|
|
36928
|
+
init_live();
|
|
36929
|
+
import fs40 from "node:fs";
|
|
36930
|
+
import path40 from "node:path";
|
|
36931
|
+
import { WebSocketServer as WebSocketServer2, WebSocket as WebSocket2 } from "ws";
|
|
36932
|
+
var DEFAULT_PORT = 16996;
|
|
36933
|
+
var CcBridgeAdapter = class {
|
|
36934
|
+
name = "ccBridge";
|
|
36935
|
+
config;
|
|
36936
|
+
wss = null;
|
|
36937
|
+
clients = /* @__PURE__ */ new Set();
|
|
36938
|
+
messageHandler = null;
|
|
36939
|
+
seq = 0;
|
|
36940
|
+
constructor(config2) {
|
|
36941
|
+
this.config = config2;
|
|
36942
|
+
}
|
|
36943
|
+
/** 工具流水默认不推(见 CcBridgeConfig.showToolDisplay) */
|
|
36944
|
+
get suppressToolDisplay() {
|
|
36945
|
+
return this.config.showToolDisplay !== true;
|
|
36946
|
+
}
|
|
36947
|
+
/** 实际监听的端口。配 `port: 0` 时由内核分配 —— 测试用它拿真端口,不用赌某个数字没被占 */
|
|
36948
|
+
get boundPort() {
|
|
36949
|
+
const a = this.wss?.address();
|
|
36950
|
+
return a && typeof a === "object" ? a.port : null;
|
|
36951
|
+
}
|
|
36952
|
+
onMessage(handler2) {
|
|
36953
|
+
this.messageHandler = handler2;
|
|
36954
|
+
}
|
|
36955
|
+
// ── outbox(持久队列)──
|
|
36956
|
+
//
|
|
36957
|
+
// 每次现读 stateDir,不缓存 —— stateDir 的 effect 是 restart,缓存下来的
|
|
36958
|
+
// 只会在热加载后变成指向旧目录的幽灵路径(配置访问铁律)。
|
|
36959
|
+
outboxFile() {
|
|
36960
|
+
const sd = liveConfig.get("stateDir") || process.cwd();
|
|
36961
|
+
return path40.join(sd, "state", "cc-bridge", "outbox.jsonl");
|
|
36962
|
+
}
|
|
36963
|
+
/**
|
|
36964
|
+
* 读队列。**只有 ENOENT 才等于"空队列"。**
|
|
36965
|
+
*
|
|
36966
|
+
* ⚠️ 这里原来写的是 `catch { return [] }` —— 那是**会丢数据的**:
|
|
36967
|
+
* 读失败(Windows 上 AV/同步盘/搜索索引的瞬时共享锁 EBUSY,或 EPERM/EACCES)
|
|
36968
|
+
* 返回空数组,紧接着 `enqueue` 拿这份空数组**整份覆写**,待投递的消息全没,
|
|
36969
|
+
* 而且一句警告都没有。而"读不了"和"不存在"是完全不同的两件事 ——
|
|
36970
|
+
* 这个区分正是"宁可重复,不可丢失"能不能成立的支点。
|
|
36971
|
+
*/
|
|
36972
|
+
readOutbox() {
|
|
36973
|
+
let raw;
|
|
36974
|
+
try {
|
|
36975
|
+
raw = fs40.readFileSync(this.outboxFile(), "utf8");
|
|
36976
|
+
} catch (e) {
|
|
36977
|
+
if (e?.code === "ENOENT") return [];
|
|
36978
|
+
throw e;
|
|
36979
|
+
}
|
|
36980
|
+
const out = [];
|
|
36981
|
+
for (const line of raw.split("\n")) {
|
|
36982
|
+
if (!line.trim()) continue;
|
|
36983
|
+
try {
|
|
36984
|
+
out.push(JSON.parse(line));
|
|
36985
|
+
} catch {
|
|
36986
|
+
}
|
|
36987
|
+
}
|
|
36988
|
+
return out;
|
|
36989
|
+
}
|
|
36990
|
+
/**
|
|
36991
|
+
* 写队列:**先写 .tmp 再 rename**。
|
|
36992
|
+
*
|
|
36993
|
+
* `writeFileSync(file, ...)` 是原地截断(`w` 模式先清零再写)——
|
|
36994
|
+
* 在这中间被 taskkill / OOM / 断电,文件就是空的或半截,
|
|
36995
|
+
* 下次读回来是 [] 且无从分辨,最多 maxQueue 条待投递消息整体消失。
|
|
36996
|
+
* rename 到已存在文件走的是替换语义,落盘要么全旧要么全新。
|
|
36997
|
+
*
|
|
36998
|
+
* **失败要抛**:调用方(cron/heartbeat 经 `manager.send`)必须知道没存住 ——
|
|
36999
|
+
* 吞成 warning 的话,磁盘满/只读时客户端在线还能收到实时那条,
|
|
37000
|
+
* 客户端离线就是真丢,而这恰恰是这个通道要保的场景。
|
|
37001
|
+
*/
|
|
37002
|
+
writeOutbox(items) {
|
|
37003
|
+
const file2 = this.outboxFile();
|
|
37004
|
+
const tmp = `${file2}.tmp`;
|
|
37005
|
+
fs40.mkdirSync(path40.dirname(file2), { recursive: true });
|
|
37006
|
+
fs40.writeFileSync(tmp, items.map((i) => JSON.stringify(i)).join("\n") + (items.length ? "\n" : ""), "utf8");
|
|
37007
|
+
fs40.renameSync(tmp, file2);
|
|
37008
|
+
}
|
|
37009
|
+
/** 入队(dedupe 不做 —— id 是新的,重复投递由 ack 收敛)。读/写失败都往外抛,不吞。 */
|
|
37010
|
+
enqueue(item) {
|
|
37011
|
+
const items = this.readOutbox();
|
|
37012
|
+
items.push(item);
|
|
37013
|
+
const cap = this.config.maxQueue ?? 500;
|
|
37014
|
+
if (items.length > cap) {
|
|
37015
|
+
const dropped = items.length - cap;
|
|
37016
|
+
items.splice(0, dropped);
|
|
37017
|
+
console.warn(`[ccBridge] outbox \u8D85\u8FC7 ${cap} \u6761\uFF0C\u4E22\u4E86\u6700\u8001\u7684 ${dropped} \u6761 \u2014\u2014 CC \u592A\u4E45\u6CA1\u6765\u6536`);
|
|
37018
|
+
}
|
|
37019
|
+
this.writeOutbox(items);
|
|
37020
|
+
}
|
|
37021
|
+
/**
|
|
37022
|
+
* ack:从队列删掉客户端确认过的。
|
|
37023
|
+
*
|
|
37024
|
+
* 读失败时**返回 0 而不是抛** —— 这里宁可"删不掉"也不能"删错":
|
|
37025
|
+
* ack 的语义是收敛,失败留给下一轮再收敛,代价只是重复投递。
|
|
37026
|
+
*/
|
|
37027
|
+
ack(ids) {
|
|
37028
|
+
if (!ids.length) return 0;
|
|
37029
|
+
const set = new Set(ids);
|
|
37030
|
+
let items;
|
|
37031
|
+
try {
|
|
37032
|
+
items = this.readOutbox();
|
|
37033
|
+
} catch (e) {
|
|
37034
|
+
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}`);
|
|
37035
|
+
return 0;
|
|
37036
|
+
}
|
|
37037
|
+
const kept = items.filter((i) => !set.has(i.id));
|
|
37038
|
+
const removed = items.length - kept.length;
|
|
37039
|
+
if (removed) {
|
|
37040
|
+
try {
|
|
37041
|
+
this.writeOutbox(kept);
|
|
37042
|
+
} catch (e) {
|
|
37043
|
+
console.warn(`[ccBridge] ack \u5199\u961F\u5217\u5931\u8D25\uFF0C\u672C\u8F6E\u4E0D\u5220\uFF1A${e.message}`);
|
|
37044
|
+
return 0;
|
|
37045
|
+
}
|
|
37046
|
+
}
|
|
37047
|
+
return removed;
|
|
37048
|
+
}
|
|
37049
|
+
/** 队列条数(读不到就算 0 —— 只用于日志/握手,不能用来做删除决策) */
|
|
37050
|
+
queuedCount() {
|
|
37051
|
+
try {
|
|
37052
|
+
return this.readOutbox().length;
|
|
37053
|
+
} catch {
|
|
37054
|
+
return 0;
|
|
37055
|
+
}
|
|
37056
|
+
}
|
|
37057
|
+
/** 读队列但**不因读失败而中断**(握手补发、日志这类"读不到也能继续"的场合) */
|
|
37058
|
+
safeReadOutbox(where) {
|
|
37059
|
+
try {
|
|
37060
|
+
return this.readOutbox();
|
|
37061
|
+
} catch (e) {
|
|
37062
|
+
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}`);
|
|
37063
|
+
return [];
|
|
37064
|
+
}
|
|
37065
|
+
}
|
|
37066
|
+
/**
|
|
37067
|
+
* 升级握手校验。见 `connect()` 里那段注释 —— 绑回环挡不住浏览器。
|
|
37068
|
+
* 返回 false 时 `ws` 会直接拒绝升级(HTTP 401),连接根本建立不起来。
|
|
37069
|
+
*/
|
|
37070
|
+
verifyUpgrade(info) {
|
|
37071
|
+
const h = info?.req?.headers ?? {};
|
|
37072
|
+
if (h.origin) {
|
|
37073
|
+
console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u5E26 Origin \u7684\u8FDE\u63A5\uFF1A${h.origin}\uFF08\u7591\u4F3C\u7F51\u9875\u53D1\u8D77\uFF09`);
|
|
37074
|
+
return false;
|
|
37075
|
+
}
|
|
37076
|
+
const host = String(h.host || "").replace(/:\d+$/, "").toLowerCase();
|
|
37077
|
+
const ok = host === "127.0.0.1" || host === "localhost" || host === "[::1]" || host === "::1" || host === "";
|
|
37078
|
+
if (!ok) console.warn(`[ccBridge] \u26D4 \u62D2\u7EDD\u975E\u56DE\u73AF Host\uFF1A${h.host}\uFF08\u7591\u4F3C DNS rebinding\uFF09`);
|
|
37079
|
+
return ok;
|
|
37080
|
+
}
|
|
37081
|
+
// ── 连接 ──
|
|
37082
|
+
async connect() {
|
|
37083
|
+
const port = this.config.port ?? DEFAULT_PORT;
|
|
37084
|
+
try {
|
|
37085
|
+
fs40.mkdirSync(path40.dirname(this.outboxFile()), { recursive: true });
|
|
37086
|
+
} catch {
|
|
37087
|
+
}
|
|
37088
|
+
this.wss = new WebSocketServer2({
|
|
37089
|
+
port,
|
|
37090
|
+
host: "127.0.0.1",
|
|
37091
|
+
maxPayload: 4 * 1024 * 1024,
|
|
37092
|
+
// ⛔ **绑 127.0.0.1 不是安全边界。** WebSocket 不受同源策略约束,
|
|
37093
|
+
// 用户浏览器里**任意** http 页面都能 `new WebSocket('ws://127.0.0.1:16996')`
|
|
37094
|
+
// 连上来 —— 而这里连上就能以任意 `from` 往 agent 主会话注入消息
|
|
37095
|
+
// (实测过:伪造 Origin/Host 能成功注入一条 `from: 翀哥` 的消息)。
|
|
37096
|
+
// 所以必须自己校验:
|
|
37097
|
+
// · 带 `Origin` 头 = 网页发起的 → 直接拒(`scripts/cc-inbox.mjs` 是 Node
|
|
37098
|
+
// 客户端,本来就不发这个头,不受影响)
|
|
37099
|
+
// · `Host` 必须是本机回环 → 挡 DNS rebinding(域名解析到 127.0.0.1 时
|
|
37100
|
+
// Host 会是那个域名,而不是 127.0.0.1)
|
|
37101
|
+
verifyClient: (info) => this.verifyUpgrade(info)
|
|
37102
|
+
});
|
|
37103
|
+
this.wss.on("connection", (ws) => {
|
|
37104
|
+
this.clients.add(ws);
|
|
37105
|
+
console.log(`[ccBridge] CC connected (${this.clients.size} online)`);
|
|
37106
|
+
const pending2 = this.safeReadOutbox("\u63E1\u624B\u8865\u53D1");
|
|
37107
|
+
this.sendTo(ws, { type: "hello", channel: this.name, queued: pending2.length });
|
|
37108
|
+
if (pending2.length) {
|
|
37109
|
+
console.log(`[ccBridge] \u8865\u53D1 ${pending2.length} \u6761\u79EF\u538B`);
|
|
37110
|
+
this.sendTo(ws, { type: "backlog", messages: pending2 });
|
|
37111
|
+
}
|
|
37112
|
+
ws.on("message", (data) => this.handleFrame(ws, data));
|
|
37113
|
+
ws.on("close", () => {
|
|
37114
|
+
this.clients.delete(ws);
|
|
37115
|
+
console.log(`[ccBridge] CC disconnected (${this.clients.size} online)`);
|
|
37116
|
+
});
|
|
37117
|
+
ws.on("error", () => this.clients.delete(ws));
|
|
37118
|
+
});
|
|
37119
|
+
this.wss.on("error", (e) => {
|
|
37120
|
+
console.error(`[ccBridge] server error on 127.0.0.1:${port}: ${e.message}`);
|
|
37121
|
+
});
|
|
37122
|
+
await new Promise((resolve12, reject) => {
|
|
37123
|
+
const wss = this.wss;
|
|
37124
|
+
const ok = () => {
|
|
37125
|
+
wss.off("error", bad);
|
|
37126
|
+
resolve12();
|
|
37127
|
+
};
|
|
37128
|
+
const bad = (e) => {
|
|
37129
|
+
wss.off("listening", ok);
|
|
37130
|
+
reject(e);
|
|
37131
|
+
};
|
|
37132
|
+
wss.once("listening", ok);
|
|
37133
|
+
wss.once("error", bad);
|
|
37134
|
+
});
|
|
37135
|
+
console.log(`[ccBridge] listening on 127.0.0.1:${this.boundPort} (${this.queuedCount()} queued)`);
|
|
37136
|
+
}
|
|
37137
|
+
handleFrame(ws, data) {
|
|
37138
|
+
let msg2;
|
|
37139
|
+
try {
|
|
37140
|
+
msg2 = JSON.parse(String(data));
|
|
37141
|
+
} catch {
|
|
37142
|
+
this.sendTo(ws, { type: "error", error: "bad json" });
|
|
37143
|
+
return;
|
|
37144
|
+
}
|
|
37145
|
+
if (msg2.type === "ping") {
|
|
37146
|
+
this.sendTo(ws, { type: "pong" });
|
|
37147
|
+
return;
|
|
37148
|
+
}
|
|
37149
|
+
if (msg2.type === "ack") {
|
|
37150
|
+
const ids = Array.isArray(msg2.ids) ? msg2.ids.map(String) : [];
|
|
37151
|
+
const n = this.ack(ids);
|
|
37152
|
+
this.sendTo(ws, { type: "ack-ok", removed: n, queued: this.queuedCount() });
|
|
37153
|
+
return;
|
|
37154
|
+
}
|
|
37155
|
+
if (msg2.type === "chat") {
|
|
37156
|
+
const text = String(msg2.text ?? "").trim();
|
|
37157
|
+
if (!text) {
|
|
37158
|
+
this.sendTo(ws, { type: "error", error: "empty text" });
|
|
37159
|
+
return;
|
|
37160
|
+
}
|
|
37161
|
+
const from = String(msg2.from || this.config.userId || "cc");
|
|
37162
|
+
const inbound = {
|
|
37163
|
+
content: text,
|
|
37164
|
+
from,
|
|
37165
|
+
fromName: String(msg2.fromName || from),
|
|
37166
|
+
channel_id: this.name,
|
|
37167
|
+
channel: this.name,
|
|
37168
|
+
channelType: "dm",
|
|
37169
|
+
isBot: false,
|
|
37170
|
+
messageId: `ccb-${Date.now()}-${++this.seq}`,
|
|
37171
|
+
recvAt: Date.now()
|
|
37172
|
+
};
|
|
37173
|
+
console.log(`[ccBridge] \u2190 ${inbound.fromName}: ${text.slice(0, 80)}`);
|
|
37174
|
+
this.messageHandler?.(inbound);
|
|
37175
|
+
return;
|
|
37176
|
+
}
|
|
37177
|
+
this.sendTo(ws, { type: "error", error: `unknown type: ${msg2.type}` });
|
|
37178
|
+
}
|
|
37179
|
+
sendTo(ws, payload) {
|
|
37180
|
+
if (ws.readyState !== WebSocket2.OPEN) return;
|
|
37181
|
+
try {
|
|
37182
|
+
ws.send(JSON.stringify(payload));
|
|
37183
|
+
} catch {
|
|
37184
|
+
}
|
|
37185
|
+
}
|
|
37186
|
+
/** 广播给出站消息之外的控制帧(握手/pong 走 sendTo 单播) */
|
|
37187
|
+
broadcast(payload) {
|
|
37188
|
+
const s = JSON.stringify(payload);
|
|
37189
|
+
let n = 0;
|
|
37190
|
+
for (const ws of [...this.clients]) {
|
|
37191
|
+
if (ws.readyState !== WebSocket2.OPEN) {
|
|
37192
|
+
this.clients.delete(ws);
|
|
37193
|
+
continue;
|
|
37194
|
+
}
|
|
37195
|
+
try {
|
|
37196
|
+
ws.send(s);
|
|
37197
|
+
n++;
|
|
37198
|
+
} catch {
|
|
37199
|
+
this.clients.delete(ws);
|
|
37200
|
+
}
|
|
37201
|
+
}
|
|
37202
|
+
return n;
|
|
37203
|
+
}
|
|
37204
|
+
async disconnect() {
|
|
37205
|
+
for (const ws of [...this.clients]) {
|
|
37206
|
+
try {
|
|
37207
|
+
ws.close();
|
|
37208
|
+
} catch {
|
|
37209
|
+
}
|
|
37210
|
+
}
|
|
37211
|
+
this.clients.clear();
|
|
37212
|
+
this.wss?.close();
|
|
37213
|
+
this.wss = null;
|
|
37214
|
+
}
|
|
37215
|
+
// ── 出站 ──
|
|
37216
|
+
/**
|
|
37217
|
+
* 发消息给 CC。
|
|
37218
|
+
*
|
|
37219
|
+
* **先落 outbox 再推实时** —— 顺序反过来的话,推送成功但 CC 恰好这时断开,
|
|
37220
|
+
* 这条消息就既不在队列里也没送达,静默消失。宁可重复,不可丢失:
|
|
37221
|
+
* 重复只是多读一遍,丢失是 agent 主动说了话而没人知道。
|
|
37222
|
+
*/
|
|
37223
|
+
async send(_target, message, _options) {
|
|
37224
|
+
if (!message.trim()) return;
|
|
37225
|
+
const item = {
|
|
37226
|
+
id: `m${Date.now()}-${++this.seq}`,
|
|
37227
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37228
|
+
text: message,
|
|
37229
|
+
agent: this.config.agentName || "agent"
|
|
37230
|
+
};
|
|
37231
|
+
this.enqueue(item);
|
|
37232
|
+
const n = this.broadcast({ type: "message", message: item });
|
|
37233
|
+
if (n === 0) console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u5DF2\u5165\u961F\uFF08${this.queuedCount()} \u6761\u5F85\u6536\uFF09`);
|
|
37234
|
+
}
|
|
37235
|
+
/**
|
|
37236
|
+
* 发文件。
|
|
37237
|
+
*
|
|
37238
|
+
* **只送路径,不送内容**(`mode: 'path-ref'`)。这个通道两端都在 127.0.0.1 上
|
|
37239
|
+
* (引擎与 CC 同机),把文件 base64 塞进 WS 帧纯属浪费 —— 大文件还会把 outbox 撑爆。
|
|
37240
|
+
*
|
|
37241
|
+
* ⚠️ **但"只送路径"是把解析推迟到接收端,推迟不等于可以不做。**
|
|
37242
|
+
* 它有一个很难看的退化形态:发送端返回「已发送」(路径形式上没问题),
|
|
37243
|
+
* 接收端拿着路径在**它自己的机器上**找不到 —— **失败发生在接收端,
|
|
37244
|
+
* 而发送端日志一切正常**。(张小T 0928 指出的,她是对的。)
|
|
37245
|
+
*
|
|
37246
|
+
* 所以这里补两道:
|
|
37247
|
+
* 1. **入队前 `statSync`** —— 路径打错/文件被挪走,**在本机就报错**,
|
|
37248
|
+
* 而不是等接收端去找一个不存在的路径
|
|
37249
|
+
* 2. 帧里带 `size`/`mtimeMs` + `mode: 'path-ref'` —— 接收端知道这是**引用不是内容**,
|
|
37250
|
+
* 且能对一下"拿到的是不是你说的那份"
|
|
37251
|
+
*
|
|
37252
|
+
* 客户端那侧对应地**读不到就不 ack**(见 `scripts/cc-inbox.mjs`)——
|
|
37253
|
+
* ack 的语义是"我拿到了",拿到一半不能算。
|
|
37254
|
+
*/
|
|
37255
|
+
async sendFile(_target, message, attachment) {
|
|
37256
|
+
let st;
|
|
37257
|
+
try {
|
|
37258
|
+
st = fs40.statSync(attachment.path);
|
|
37259
|
+
} catch (e) {
|
|
37260
|
+
throw new Error(`\u9644\u4EF6\u4E0D\u5B58\u5728\uFF0C\u672A\u53D1\u9001\uFF1A${attachment.path}\uFF08${e?.code || e?.message}\uFF09`);
|
|
37261
|
+
}
|
|
37262
|
+
const file2 = {
|
|
37263
|
+
mode: "path-ref",
|
|
37264
|
+
path: attachment.path,
|
|
37265
|
+
filename: attachment.filename || path40.basename(attachment.path),
|
|
37266
|
+
mimeType: attachment.mimeType || "application/octet-stream",
|
|
37267
|
+
size: st.size,
|
|
37268
|
+
mtimeMs: Math.round(st.mtimeMs)
|
|
37269
|
+
};
|
|
37270
|
+
const item = {
|
|
37271
|
+
id: `f${Date.now()}-${++this.seq}`,
|
|
37272
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
37273
|
+
text: message || "",
|
|
37274
|
+
agent: this.config.agentName || "agent",
|
|
37275
|
+
file: file2
|
|
37276
|
+
};
|
|
37277
|
+
this.enqueue(item);
|
|
37278
|
+
if (this.broadcast({ type: "message", message: item }) === 0) {
|
|
37279
|
+
console.log(`[ccBridge] CC \u4E0D\u5728\u7EBF\uFF0C\u9644\u4EF6\u5DF2\u5165\u961F\uFF1A${file2.filename}`);
|
|
37280
|
+
}
|
|
37281
|
+
}
|
|
37282
|
+
};
|
|
37283
|
+
|
|
36831
37284
|
// src/channels/manager.ts
|
|
36832
|
-
var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy"];
|
|
37285
|
+
var MANAGED_CHANNEL_NAMES = ["discord", "feishu", "wechat", "deskBuddy", "ccBridge"];
|
|
36833
37286
|
var defaultFactories = {
|
|
36834
37287
|
discord: (block) => {
|
|
36835
37288
|
const accounts = block.accounts || {};
|
|
@@ -36891,6 +37344,9 @@ var defaultFactories = {
|
|
|
36891
37344
|
},
|
|
36892
37345
|
deskBuddy: (block) => {
|
|
36893
37346
|
return new DeskBuddyAdapter(block);
|
|
37347
|
+
},
|
|
37348
|
+
ccBridge: (block) => {
|
|
37349
|
+
return new CcBridgeAdapter(block);
|
|
36894
37350
|
}
|
|
36895
37351
|
};
|
|
36896
37352
|
var ChannelManager = class {
|
|
@@ -38842,7 +39298,7 @@ Examples:
|
|
|
38842
39298
|
properties: {
|
|
38843
39299
|
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" },
|
|
38844
39300
|
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" },
|
|
38845
|
-
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"] },
|
|
39301
|
+
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"] },
|
|
38846
39302
|
content: { type: "string", description: "\u6D88\u606F\u6B63\u6587" }
|
|
38847
39303
|
},
|
|
38848
39304
|
required: ["content", "to"]
|
|
@@ -42610,22 +43066,22 @@ ${lines.join("\n")}`);
|
|
|
42610
43066
|
await ctx.reply(wasOverride ? `Reset to default: **${defaultRef}** (auto-routing restored, fallback cooldowns cleared)` : `Already using default: **${defaultRef}**`);
|
|
42611
43067
|
return;
|
|
42612
43068
|
}
|
|
42613
|
-
const
|
|
42614
|
-
if (
|
|
43069
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43070
|
+
if (candidates2.length === 0) {
|
|
42615
43071
|
await ctx.reply(`No model matching "${input}". Use \`/model\` to see list.`);
|
|
42616
43072
|
return;
|
|
42617
43073
|
}
|
|
42618
|
-
if (
|
|
42619
|
-
const lines =
|
|
42620
|
-
const more =
|
|
42621
|
-
... and ${
|
|
43074
|
+
if (candidates2.length > 1) {
|
|
43075
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
43076
|
+
const more = candidates2.length > 10 ? `
|
|
43077
|
+
... and ${candidates2.length - 10} more` : "";
|
|
42622
43078
|
await ctx.reply(`Multiple matches for "${input}":
|
|
42623
43079
|
${lines.join("\n")}${more}
|
|
42624
43080
|
|
|
42625
|
-
Use full ref like \`/model ${
|
|
43081
|
+
Use full ref like \`/model ${candidates2[0].ref}\``);
|
|
42626
43082
|
return;
|
|
42627
43083
|
}
|
|
42628
|
-
const target =
|
|
43084
|
+
const target = candidates2[0].ref;
|
|
42629
43085
|
let targetDeps = deps.modelDepsCache.get(target);
|
|
42630
43086
|
if (!targetDeps) {
|
|
42631
43087
|
targetDeps = deps.createModelDeps(target);
|
|
@@ -42663,8 +43119,8 @@ Use full ref like \`/model ${candidates[0].ref}\``);
|
|
|
42663
43119
|
const writer = deps.sessions.getWriter(sid);
|
|
42664
43120
|
writer.writeModelChange(pid, mid);
|
|
42665
43121
|
}
|
|
42666
|
-
console.log(`[model] Switched to ${target} (${
|
|
42667
|
-
await ctx.reply(`Switched to **${target}** (${
|
|
43122
|
+
console.log(`[model] Switched to ${target} (${candidates2[0].name})`);
|
|
43123
|
+
await ctx.reply(`Switched to **${target}** (${candidates2[0].name})
|
|
42668
43124
|
Auto-routing disabled \u2014 all messages use this model.
|
|
42669
43125
|
\`/model auto\` to restore default.`);
|
|
42670
43126
|
}
|
|
@@ -42687,20 +43143,20 @@ Usage: \`/primary provider/model\` to persist (restart required)`);
|
|
|
42687
43143
|
return;
|
|
42688
43144
|
}
|
|
42689
43145
|
const { aliases, available } = collectModels(deps.config);
|
|
42690
|
-
const
|
|
42691
|
-
if (
|
|
43146
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43147
|
+
if (candidates2.length === 0) {
|
|
42692
43148
|
await ctx.reply(`No model matching "${input}".`);
|
|
42693
43149
|
return;
|
|
42694
43150
|
}
|
|
42695
|
-
if (
|
|
42696
|
-
const lines =
|
|
43151
|
+
if (candidates2.length > 1) {
|
|
43152
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42697
43153
|
await ctx.reply(`Multiple matches:
|
|
42698
43154
|
${lines.join("\n")}
|
|
42699
43155
|
|
|
42700
|
-
Use full ref like \`/primary ${
|
|
43156
|
+
Use full ref like \`/primary ${candidates2[0].ref}\``);
|
|
42701
43157
|
return;
|
|
42702
43158
|
}
|
|
42703
|
-
const target =
|
|
43159
|
+
const target = candidates2[0].ref;
|
|
42704
43160
|
if (!configPath2 || !writePath) {
|
|
42705
43161
|
await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
|
|
42706
43162
|
return;
|
|
@@ -42715,7 +43171,7 @@ Use full ref like \`/primary ${candidates[0].ref}\``);
|
|
|
42715
43171
|
cfg.agents.defaults.model.primary = target;
|
|
42716
43172
|
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42717
43173
|
console.log(`[primary] Persisted primary=${target} to ${writePath}`);
|
|
42718
|
-
await ctx.reply(`\u2705 Primary model set to **${target}** (${
|
|
43174
|
+
await ctx.reply(`\u2705 Primary model set to **${target}** (${candidates2[0].name})
|
|
42719
43175
|
Written to config, hot-reloading provider chain now.`);
|
|
42720
43176
|
} catch (err) {
|
|
42721
43177
|
await ctx.reply(`\u26A0\uFE0F Failed to write config: ${err.message}`);
|
|
@@ -42741,20 +43197,20 @@ Usage: \`/vision-primary provider/model\` to persist (hot-reload)
|
|
|
42741
43197
|
return;
|
|
42742
43198
|
}
|
|
42743
43199
|
const { aliases, available } = collectModels(deps.config);
|
|
42744
|
-
const
|
|
42745
|
-
if (
|
|
43200
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43201
|
+
if (candidates2.length === 0) {
|
|
42746
43202
|
await ctx.reply(`No model matching "${input}".`);
|
|
42747
43203
|
return;
|
|
42748
43204
|
}
|
|
42749
|
-
if (
|
|
42750
|
-
const lines =
|
|
43205
|
+
if (candidates2.length > 1) {
|
|
43206
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42751
43207
|
await ctx.reply(`Multiple matches:
|
|
42752
43208
|
${lines.join("\n")}
|
|
42753
43209
|
|
|
42754
|
-
Use full ref like \`/vision-primary ${
|
|
43210
|
+
Use full ref like \`/vision-primary ${candidates2[0].ref}\``);
|
|
42755
43211
|
return;
|
|
42756
43212
|
}
|
|
42757
|
-
const target =
|
|
43213
|
+
const target = candidates2[0].ref;
|
|
42758
43214
|
if (!configPath2 || !writePath) {
|
|
42759
43215
|
await ctx.reply(`\u26A0\uFE0F Config file path not available, cannot persist. Set manually in config.`);
|
|
42760
43216
|
return;
|
|
@@ -42775,7 +43231,7 @@ Use full ref like \`/vision-primary ${candidates[0].ref}\``);
|
|
|
42775
43231
|
cfg.agents.defaults.model.vision = target;
|
|
42776
43232
|
await fs56.promises.writeFile(writePath, JSON.stringify(cfg, null, 2), "utf-8");
|
|
42777
43233
|
console.log(`[vision-primary] Persisted vision=${target} to ${writePath}`);
|
|
42778
|
-
await ctx.reply(`\u2705 Vision primary set to **${target}** (${
|
|
43234
|
+
await ctx.reply(`\u2705 Vision primary set to **${target}** (${candidates2[0].name})
|
|
42779
43235
|
Written to config. Hot-reload will apply.`);
|
|
42780
43236
|
}
|
|
42781
43237
|
} catch (err) {
|
|
@@ -42807,20 +43263,20 @@ Available models: use \`/model\` to see full list
|
|
|
42807
43263
|
return;
|
|
42808
43264
|
}
|
|
42809
43265
|
const { aliases, available } = collectModels(deps.config);
|
|
42810
|
-
const
|
|
42811
|
-
if (
|
|
43266
|
+
const candidates2 = matchModels(input, aliases, available);
|
|
43267
|
+
if (candidates2.length === 0) {
|
|
42812
43268
|
await ctx.reply(`No model matching "${input}".`);
|
|
42813
43269
|
return;
|
|
42814
43270
|
}
|
|
42815
|
-
if (
|
|
42816
|
-
const lines =
|
|
43271
|
+
if (candidates2.length > 1) {
|
|
43272
|
+
const lines = candidates2.slice(0, 10).map((a) => `- \`${a.ref}\` \u2014 ${a.name}`);
|
|
42817
43273
|
await ctx.reply(`Multiple matches:
|
|
42818
43274
|
${lines.join("\n")}
|
|
42819
43275
|
|
|
42820
|
-
Use full ref like \`/vision-model ${
|
|
43276
|
+
Use full ref like \`/vision-model ${candidates2[0].ref}\``);
|
|
42821
43277
|
return;
|
|
42822
43278
|
}
|
|
42823
|
-
const target =
|
|
43279
|
+
const target = candidates2[0].ref;
|
|
42824
43280
|
let targetDeps = deps.modelDepsCache.get(`vision:${target}`);
|
|
42825
43281
|
if (!targetDeps) {
|
|
42826
43282
|
targetDeps = deps.createModelDeps(target);
|
|
@@ -42833,7 +43289,7 @@ Use full ref like \`/vision-model ${candidates[0].ref}\``);
|
|
|
42833
43289
|
deps.setVisionOverride(target);
|
|
42834
43290
|
deps.setVisionDeps(targetDeps);
|
|
42835
43291
|
console.log(`[vision-model] Override: ${target}`);
|
|
42836
|
-
await ctx.reply(`Vision switched to **${target}** (${
|
|
43292
|
+
await ctx.reply(`Vision switched to **${target}** (${candidates2[0].name}) (immediate)
|
|
42837
43293
|
\`/vision-model auto\` to restore default.`);
|
|
42838
43294
|
}
|
|
42839
43295
|
});
|
|
@@ -43652,7 +44108,7 @@ async function startEngine(config2, opts) {
|
|
|
43652
44108
|
};
|
|
43653
44109
|
const startReportingNow = () => {
|
|
43654
44110
|
stopReporting();
|
|
43655
|
-
reportingHandle = startReporting({ stateDir: config2.stateDir });
|
|
44111
|
+
reportingHandle = startReporting({ stateDir: config2.stateDir, log: (m) => console.log(m) });
|
|
43656
44112
|
};
|
|
43657
44113
|
const reportingEnabled = () => liveConfig.get("reporting.enabled") !== false;
|
|
43658
44114
|
reloadOrchestrator.register({
|
|
@@ -43710,11 +44166,7 @@ async function startEngine(config2, opts) {
|
|
|
43710
44166
|
}
|
|
43711
44167
|
|
|
43712
44168
|
// src/main.ts
|
|
43713
|
-
|
|
43714
|
-
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
43715
|
-
import { dirname as dirname16, join as join66 } from "node:path";
|
|
43716
|
-
var __dirname2 = dirname16(fileURLToPath9(import.meta.url));
|
|
43717
|
-
var pkg = JSON.parse(readFileSync48(join66(__dirname2, "..", "package.json"), "utf-8"));
|
|
44169
|
+
init_version();
|
|
43718
44170
|
var epipeSeen = false;
|
|
43719
44171
|
process.on("uncaughtException", (err) => {
|
|
43720
44172
|
const code = err?.code ?? "";
|
|
@@ -43757,10 +44209,12 @@ if (args[0] === "features") {
|
|
|
43757
44209
|
const status = getLicenseStatus2(stateDir);
|
|
43758
44210
|
const allFeatures = listFeatures2();
|
|
43759
44211
|
console.log("");
|
|
43760
|
-
if (status.
|
|
44212
|
+
if (status.licenseId) {
|
|
43761
44213
|
console.log(`\u{1F4C4} License: ${status.licenseId} | ${status.customer} | \u5230\u671F: ${status.expiresAt}`);
|
|
44214
|
+
} else if (status.openMode) {
|
|
44215
|
+
console.log("\u{1F4C4} License: \u65E0 \u2014\u2014 **\u5F00\u653E\u6A21\u5F0F**\uFF0C\u5168\u90E8 feature \u6FC0\u6D3B");
|
|
43762
44216
|
} else {
|
|
43763
|
-
console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F)");
|
|
44217
|
+
console.log("\u{1F4C4} License: \u65E0 (\u57FA\u7840\u5F15\u64CE\u6A21\u5F0F \u2014 \u914D\u4E86 devMode=false)");
|
|
43764
44218
|
}
|
|
43765
44219
|
console.log("");
|
|
43766
44220
|
for (const f of allFeatures) {
|
|
@@ -43773,15 +44227,10 @@ if (args[0] === "features") {
|
|
|
43773
44227
|
} else if (info && !info.active) {
|
|
43774
44228
|
console.log(`\u23F0 ${f.name} \u2014 \u5DF2\u8FC7\u671F${info.expiresAt ? ` (${info.expiresAt})` : ""}`);
|
|
43775
44229
|
} else {
|
|
43776
|
-
console.log(`\u274C ${f.name} \u2014 \u672A\
|
|
44230
|
+
console.log(`\u274C ${f.name} \u2014 \u672A\u542F\u7528 (devMode=false \u4E14\u65E0 license)`);
|
|
43777
44231
|
}
|
|
43778
44232
|
} else {
|
|
43779
|
-
|
|
43780
|
-
if (enabled) {
|
|
43781
|
-
console.log(`\u2705 ${f.name} (always)`);
|
|
43782
|
-
} else {
|
|
43783
|
-
console.log(`\u2B1C ${f.name} \u2014 \u672A\u542F\u7528`);
|
|
43784
|
-
}
|
|
44233
|
+
console.log(`\u26AA ${f.name} \u2014 \u4E0E license \u65E0\u5173\uFF08\u5F00\u4E0D\u5F00\u770B config\uFF09`);
|
|
43785
44234
|
}
|
|
43786
44235
|
}
|
|
43787
44236
|
console.log("");
|
|
@@ -43790,7 +44239,7 @@ if (args[0] === "features") {
|
|
|
43790
44239
|
var secretsPath = configPath ? loadSecretsEnvForConfig(configPath) : null;
|
|
43791
44240
|
console.log(secretsPath ? `[secrets] Loaded API keys from ${secretsPath}` : "[secrets] \u672A\u627E\u5230 .env \u2014\u2014 \u82E5 config \u7528\u7684\u662F env:XXX_API_KEY \u5360\u4F4D\u7B26\uFF0C\u5C06\u89E3\u6790\u5931\u8D25\uFF08401\uFF09");
|
|
43792
44241
|
var config = loadConfig(configPath);
|
|
43793
|
-
console.log(`=== Engine 7 v${
|
|
44242
|
+
console.log(`=== Engine 7 v${readEngineVersion()} ===`);
|
|
43794
44243
|
console.log(`PID: ${process.pid}`);
|
|
43795
44244
|
console.log(configSummary(config));
|
|
43796
44245
|
startEngine(config, { cliMode: true }).catch((err) => {
|