codex-to-im 1.0.19 → 1.0.21
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/README.md +44 -4
- package/README_EN.md +44 -10
- package/config.env.example +22 -9
- package/dist/cli.mjs +272 -3
- package/dist/daemon.mjs +761 -347
- package/dist/ui-server.mjs +1485 -725
- package/docs/install-windows.md +41 -17
- package/package.json +1 -1
- package/scripts/doctor.sh +12 -5
package/dist/daemon.mjs
CHANGED
|
@@ -4231,6 +4231,8 @@ function getBridgeContext() {
|
|
|
4231
4231
|
|
|
4232
4232
|
// src/lib/bridge/channel-adapter.ts
|
|
4233
4233
|
var BaseChannelAdapter = class {
|
|
4234
|
+
/** Human-readable instance alias, when applicable. */
|
|
4235
|
+
alias;
|
|
4234
4236
|
/**
|
|
4235
4237
|
* Answer a callback query (e.g. Telegram inline button press).
|
|
4236
4238
|
* Not all platforms support this — default implementation is a no-op.
|
|
@@ -4239,12 +4241,12 @@ var BaseChannelAdapter = class {
|
|
|
4239
4241
|
}
|
|
4240
4242
|
};
|
|
4241
4243
|
var adapterFactories = /* @__PURE__ */ new Map();
|
|
4242
|
-
function registerAdapterFactory(
|
|
4243
|
-
adapterFactories.set(
|
|
4244
|
+
function registerAdapterFactory(provider, factory) {
|
|
4245
|
+
adapterFactories.set(provider, factory);
|
|
4244
4246
|
}
|
|
4245
|
-
function createAdapter(
|
|
4246
|
-
const factory = adapterFactories.get(
|
|
4247
|
-
return factory ? factory() : null;
|
|
4247
|
+
function createAdapter(instance) {
|
|
4248
|
+
const factory = adapterFactories.get(instance.provider);
|
|
4249
|
+
return factory ? factory(instance) : null;
|
|
4248
4250
|
}
|
|
4249
4251
|
function getRegisteredTypes() {
|
|
4250
4252
|
return Array.from(adapterFactories.keys());
|
|
@@ -4464,6 +4466,7 @@ function tokenShortHash(botToken) {
|
|
|
4464
4466
|
var MEDIA_GROUP_DEBOUNCE_MS = 500;
|
|
4465
4467
|
var TelegramAdapter = class extends BaseChannelAdapter {
|
|
4466
4468
|
channelType = "telegram";
|
|
4469
|
+
provider = "telegram";
|
|
4467
4470
|
running = false;
|
|
4468
4471
|
abortController = null;
|
|
4469
4472
|
queue = [];
|
|
@@ -5079,9 +5082,346 @@ registerAdapterFactory("telegram", () => new TelegramAdapter());
|
|
|
5079
5082
|
|
|
5080
5083
|
// src/lib/bridge/adapters/feishu-adapter.ts
|
|
5081
5084
|
import crypto2 from "crypto";
|
|
5085
|
+
import fs2 from "node:fs";
|
|
5086
|
+
import path2 from "node:path";
|
|
5087
|
+
import * as lark from "@larksuiteoapi/node-sdk";
|
|
5088
|
+
|
|
5089
|
+
// src/config.ts
|
|
5082
5090
|
import fs from "node:fs";
|
|
5091
|
+
import os from "node:os";
|
|
5083
5092
|
import path from "node:path";
|
|
5084
|
-
|
|
5093
|
+
function toFeishuConfig(channel) {
|
|
5094
|
+
return channel?.provider === "feishu" ? channel.config : void 0;
|
|
5095
|
+
}
|
|
5096
|
+
function toWeixinConfig(channel) {
|
|
5097
|
+
return channel?.provider === "weixin" ? channel.config : void 0;
|
|
5098
|
+
}
|
|
5099
|
+
var LEGACY_CTI_HOME = path.join(os.homedir(), ".claude-to-im");
|
|
5100
|
+
var DEFAULT_CTI_HOME = path.join(os.homedir(), ".codex-to-im");
|
|
5101
|
+
var DEFAULT_WORKSPACE_ROOT = path.join(os.homedir(), "cx2im");
|
|
5102
|
+
function resolveDefaultCtiHome() {
|
|
5103
|
+
if (fs.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
|
|
5104
|
+
if (fs.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
|
|
5105
|
+
return DEFAULT_CTI_HOME;
|
|
5106
|
+
}
|
|
5107
|
+
var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
|
|
5108
|
+
var CONFIG_PATH = path.join(CTI_HOME, "config.env");
|
|
5109
|
+
var CONFIG_V2_PATH = path.join(CTI_HOME, "config.v2.json");
|
|
5110
|
+
function expandHomePath(value) {
|
|
5111
|
+
if (!value) return value;
|
|
5112
|
+
if (value === "~") return os.homedir();
|
|
5113
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
5114
|
+
return path.join(os.homedir(), value.slice(2));
|
|
5115
|
+
}
|
|
5116
|
+
return value;
|
|
5117
|
+
}
|
|
5118
|
+
function parseEnvFile(content) {
|
|
5119
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5120
|
+
for (const line of content.split("\n")) {
|
|
5121
|
+
const trimmed = line.trim();
|
|
5122
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
5123
|
+
const eqIdx = trimmed.indexOf("=");
|
|
5124
|
+
if (eqIdx === -1) continue;
|
|
5125
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
5126
|
+
let value = trimmed.slice(eqIdx + 1).trim();
|
|
5127
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
5128
|
+
value = value.slice(1, -1);
|
|
5129
|
+
}
|
|
5130
|
+
entries.set(key, value);
|
|
5131
|
+
}
|
|
5132
|
+
return entries;
|
|
5133
|
+
}
|
|
5134
|
+
function loadRawConfigEnv() {
|
|
5135
|
+
try {
|
|
5136
|
+
return parseEnvFile(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
5137
|
+
} catch {
|
|
5138
|
+
return /* @__PURE__ */ new Map();
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
function splitCsv(value) {
|
|
5142
|
+
if (!value) return void 0;
|
|
5143
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5144
|
+
}
|
|
5145
|
+
function parsePositiveInt(value) {
|
|
5146
|
+
if (!value) return void 0;
|
|
5147
|
+
const parsed = Number(value);
|
|
5148
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
|
|
5149
|
+
return Math.floor(parsed);
|
|
5150
|
+
}
|
|
5151
|
+
function parseSandboxMode(value) {
|
|
5152
|
+
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
5153
|
+
return value;
|
|
5154
|
+
}
|
|
5155
|
+
return void 0;
|
|
5156
|
+
}
|
|
5157
|
+
function parseReasoningEffort(value) {
|
|
5158
|
+
if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
|
|
5159
|
+
return value;
|
|
5160
|
+
}
|
|
5161
|
+
return void 0;
|
|
5162
|
+
}
|
|
5163
|
+
function normalizeFeishuSite(value) {
|
|
5164
|
+
const normalized = (value || "").trim().replace(/\/+$/, "").toLowerCase();
|
|
5165
|
+
if (!normalized) return "feishu";
|
|
5166
|
+
if (normalized === "lark") return "lark";
|
|
5167
|
+
if (normalized === "feishu") return "feishu";
|
|
5168
|
+
if (normalized.includes("open.larksuite.com")) return "lark";
|
|
5169
|
+
return "feishu";
|
|
5170
|
+
}
|
|
5171
|
+
function feishuSiteToApiBaseUrl(site) {
|
|
5172
|
+
return normalizeFeishuSite(site) === "lark" ? "https://open.larksuite.com" : "https://open.feishu.cn";
|
|
5173
|
+
}
|
|
5174
|
+
function nowIso() {
|
|
5175
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
5176
|
+
}
|
|
5177
|
+
function ensureConfigDir() {
|
|
5178
|
+
fs.mkdirSync(CTI_HOME, { recursive: true });
|
|
5179
|
+
}
|
|
5180
|
+
function readConfigV2File() {
|
|
5181
|
+
try {
|
|
5182
|
+
const parsed = JSON.parse(fs.readFileSync(CONFIG_V2_PATH, "utf-8"));
|
|
5183
|
+
if (parsed && parsed.schemaVersion === 2 && parsed.runtime && Array.isArray(parsed.channels)) {
|
|
5184
|
+
return parsed;
|
|
5185
|
+
}
|
|
5186
|
+
return null;
|
|
5187
|
+
} catch {
|
|
5188
|
+
return null;
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
function writeConfigV2File(config2) {
|
|
5192
|
+
ensureConfigDir();
|
|
5193
|
+
const tmpPath = CONFIG_V2_PATH + ".tmp";
|
|
5194
|
+
fs.writeFileSync(tmpPath, JSON.stringify(config2, null, 2), { mode: 384 });
|
|
5195
|
+
fs.renameSync(tmpPath, CONFIG_V2_PATH);
|
|
5196
|
+
}
|
|
5197
|
+
function defaultAliasForProvider(provider) {
|
|
5198
|
+
return provider === "feishu" ? "\u98DE\u4E66" : "\u5FAE\u4FE1";
|
|
5199
|
+
}
|
|
5200
|
+
function buildDefaultChannelId(provider) {
|
|
5201
|
+
return `${provider}-default`;
|
|
5202
|
+
}
|
|
5203
|
+
function migrateLegacyEnvToV2(env) {
|
|
5204
|
+
const rawRuntime = env.get("CTI_RUNTIME") || "codex";
|
|
5205
|
+
const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
|
|
5206
|
+
const enabledChannels = splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"];
|
|
5207
|
+
const timestamp = nowIso();
|
|
5208
|
+
const channels = [];
|
|
5209
|
+
const hasFeishuConfig = Boolean(
|
|
5210
|
+
env.get("CTI_FEISHU_APP_ID") || env.get("CTI_FEISHU_APP_SECRET") || env.get("CTI_FEISHU_ALLOWED_USERS") || enabledChannels.includes("feishu")
|
|
5211
|
+
);
|
|
5212
|
+
if (hasFeishuConfig) {
|
|
5213
|
+
channels.push({
|
|
5214
|
+
id: buildDefaultChannelId("feishu"),
|
|
5215
|
+
alias: defaultAliasForProvider("feishu"),
|
|
5216
|
+
provider: "feishu",
|
|
5217
|
+
enabled: enabledChannels.includes("feishu"),
|
|
5218
|
+
createdAt: timestamp,
|
|
5219
|
+
updatedAt: timestamp,
|
|
5220
|
+
config: {
|
|
5221
|
+
appId: env.get("CTI_FEISHU_APP_ID") || void 0,
|
|
5222
|
+
appSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
|
|
5223
|
+
site: normalizeFeishuSite(env.get("CTI_FEISHU_SITE") || env.get("CTI_FEISHU_DOMAIN")),
|
|
5224
|
+
allowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
|
|
5225
|
+
streamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
|
|
5226
|
+
feedbackMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true
|
|
5227
|
+
}
|
|
5228
|
+
});
|
|
5229
|
+
}
|
|
5230
|
+
const hasWeixinConfig = Boolean(
|
|
5231
|
+
env.get("CTI_WEIXIN_BASE_URL") || env.get("CTI_WEIXIN_CDN_BASE_URL") || env.get("CTI_WEIXIN_MEDIA_ENABLED") || enabledChannels.includes("weixin")
|
|
5232
|
+
);
|
|
5233
|
+
if (hasWeixinConfig) {
|
|
5234
|
+
channels.push({
|
|
5235
|
+
id: buildDefaultChannelId("weixin"),
|
|
5236
|
+
alias: defaultAliasForProvider("weixin"),
|
|
5237
|
+
provider: "weixin",
|
|
5238
|
+
enabled: enabledChannels.includes("weixin"),
|
|
5239
|
+
createdAt: timestamp,
|
|
5240
|
+
updatedAt: timestamp,
|
|
5241
|
+
config: {
|
|
5242
|
+
baseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
|
|
5243
|
+
cdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
|
|
5244
|
+
mediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
|
|
5245
|
+
feedbackMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false
|
|
5246
|
+
}
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
return {
|
|
5250
|
+
schemaVersion: 2,
|
|
5251
|
+
runtime: {
|
|
5252
|
+
provider: runtime,
|
|
5253
|
+
defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
|
|
5254
|
+
defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
|
|
5255
|
+
defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
|
|
5256
|
+
historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
|
|
5257
|
+
codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
|
|
5258
|
+
codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
|
|
5259
|
+
codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
|
|
5260
|
+
uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
|
|
5261
|
+
uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
|
|
5262
|
+
autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
|
|
5263
|
+
},
|
|
5264
|
+
channels
|
|
5265
|
+
};
|
|
5266
|
+
}
|
|
5267
|
+
function getChannelByProvider(config2, provider) {
|
|
5268
|
+
const preferredId = buildDefaultChannelId(provider);
|
|
5269
|
+
return config2.channels.find((channel) => channel.id === preferredId) || config2.channels.find((channel) => channel.provider === provider);
|
|
5270
|
+
}
|
|
5271
|
+
function expandConfig(v2) {
|
|
5272
|
+
return {
|
|
5273
|
+
schemaVersion: 2,
|
|
5274
|
+
channels: v2.channels,
|
|
5275
|
+
runtime: v2.runtime.provider,
|
|
5276
|
+
enabledChannels: Array.from(new Set(
|
|
5277
|
+
v2.channels.filter((channel) => channel.enabled).map((channel) => channel.provider)
|
|
5278
|
+
)),
|
|
5279
|
+
defaultWorkspaceRoot: v2.runtime.defaultWorkspaceRoot,
|
|
5280
|
+
defaultModel: v2.runtime.defaultModel,
|
|
5281
|
+
defaultMode: v2.runtime.defaultMode || "code",
|
|
5282
|
+
historyMessageLimit: v2.runtime.historyMessageLimit ?? 8,
|
|
5283
|
+
codexSkipGitRepoCheck: v2.runtime.codexSkipGitRepoCheck ?? true,
|
|
5284
|
+
codexSandboxMode: v2.runtime.codexSandboxMode ?? "workspace-write",
|
|
5285
|
+
codexReasoningEffort: v2.runtime.codexReasoningEffort ?? "medium",
|
|
5286
|
+
uiAllowLan: v2.runtime.uiAllowLan === true,
|
|
5287
|
+
uiAccessToken: v2.runtime.uiAccessToken || void 0,
|
|
5288
|
+
autoApprove: v2.runtime.autoApprove === true
|
|
5289
|
+
};
|
|
5290
|
+
}
|
|
5291
|
+
function loadConfig() {
|
|
5292
|
+
const current = readConfigV2File();
|
|
5293
|
+
if (current) return expandConfig(current);
|
|
5294
|
+
const legacyEnv = loadRawConfigEnv();
|
|
5295
|
+
if (legacyEnv.size > 0) {
|
|
5296
|
+
const migrated = migrateLegacyEnvToV2(legacyEnv);
|
|
5297
|
+
writeConfigV2File(migrated);
|
|
5298
|
+
return expandConfig(migrated);
|
|
5299
|
+
}
|
|
5300
|
+
const empty = {
|
|
5301
|
+
schemaVersion: 2,
|
|
5302
|
+
runtime: {
|
|
5303
|
+
provider: "codex",
|
|
5304
|
+
defaultWorkspaceRoot: DEFAULT_WORKSPACE_ROOT,
|
|
5305
|
+
defaultMode: "code",
|
|
5306
|
+
historyMessageLimit: 8,
|
|
5307
|
+
codexSkipGitRepoCheck: true,
|
|
5308
|
+
codexSandboxMode: "workspace-write",
|
|
5309
|
+
codexReasoningEffort: "medium",
|
|
5310
|
+
uiAllowLan: false,
|
|
5311
|
+
autoApprove: false
|
|
5312
|
+
},
|
|
5313
|
+
channels: []
|
|
5314
|
+
};
|
|
5315
|
+
return expandConfig(empty);
|
|
5316
|
+
}
|
|
5317
|
+
function listChannelInstances(config2) {
|
|
5318
|
+
return [...config2?.channels || loadConfig().channels || []];
|
|
5319
|
+
}
|
|
5320
|
+
function findChannelInstance(channelId, config2) {
|
|
5321
|
+
return listChannelInstances(config2).find((channel) => channel.id === channelId);
|
|
5322
|
+
}
|
|
5323
|
+
function configToSettings(config2) {
|
|
5324
|
+
const m = /* @__PURE__ */ new Map();
|
|
5325
|
+
const current = {
|
|
5326
|
+
schemaVersion: 2,
|
|
5327
|
+
runtime: {
|
|
5328
|
+
provider: config2.runtime,
|
|
5329
|
+
defaultMode: config2.defaultMode
|
|
5330
|
+
},
|
|
5331
|
+
channels: config2.channels || []
|
|
5332
|
+
};
|
|
5333
|
+
const feishu = getChannelByProvider(current, "feishu");
|
|
5334
|
+
const weixin = getChannelByProvider(current, "weixin");
|
|
5335
|
+
const feishuConfig = toFeishuConfig(feishu);
|
|
5336
|
+
const weixinConfig = toWeixinConfig(weixin);
|
|
5337
|
+
m.set("remote_bridge_enabled", "true");
|
|
5338
|
+
if (config2.defaultWorkspaceRoot) {
|
|
5339
|
+
m.set("bridge_default_workspace_root", config2.defaultWorkspaceRoot);
|
|
5340
|
+
}
|
|
5341
|
+
if (config2.defaultModel) {
|
|
5342
|
+
m.set("bridge_default_model", config2.defaultModel);
|
|
5343
|
+
m.set("default_model", config2.defaultModel);
|
|
5344
|
+
}
|
|
5345
|
+
m.set("bridge_default_mode", config2.defaultMode);
|
|
5346
|
+
m.set(
|
|
5347
|
+
"bridge_history_message_limit",
|
|
5348
|
+
String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
|
|
5349
|
+
);
|
|
5350
|
+
m.set(
|
|
5351
|
+
"bridge_codex_skip_git_repo_check",
|
|
5352
|
+
config2.codexSkipGitRepoCheck === true ? "true" : "false"
|
|
5353
|
+
);
|
|
5354
|
+
m.set(
|
|
5355
|
+
"bridge_codex_sandbox_mode",
|
|
5356
|
+
config2.codexSandboxMode || "workspace-write"
|
|
5357
|
+
);
|
|
5358
|
+
m.set(
|
|
5359
|
+
"bridge_codex_reasoning_effort",
|
|
5360
|
+
config2.codexReasoningEffort || "medium"
|
|
5361
|
+
);
|
|
5362
|
+
m.set(
|
|
5363
|
+
"bridge_channel_instances_json",
|
|
5364
|
+
JSON.stringify(config2.channels || [])
|
|
5365
|
+
);
|
|
5366
|
+
m.set(
|
|
5367
|
+
"bridge_telegram_enabled",
|
|
5368
|
+
config2.enabledChannels.includes("telegram") ? "true" : "false"
|
|
5369
|
+
);
|
|
5370
|
+
if (config2.tgBotToken) m.set("telegram_bot_token", config2.tgBotToken);
|
|
5371
|
+
if (config2.tgAllowedUsers) m.set("telegram_bridge_allowed_users", config2.tgAllowedUsers.join(","));
|
|
5372
|
+
if (config2.tgChatId) m.set("telegram_chat_id", config2.tgChatId);
|
|
5373
|
+
m.set(
|
|
5374
|
+
"bridge_discord_enabled",
|
|
5375
|
+
config2.enabledChannels.includes("discord") ? "true" : "false"
|
|
5376
|
+
);
|
|
5377
|
+
if (config2.discordBotToken) m.set("bridge_discord_bot_token", config2.discordBotToken);
|
|
5378
|
+
if (config2.discordAllowedUsers) m.set("bridge_discord_allowed_users", config2.discordAllowedUsers.join(","));
|
|
5379
|
+
if (config2.discordAllowedChannels) m.set("bridge_discord_allowed_channels", config2.discordAllowedChannels.join(","));
|
|
5380
|
+
if (config2.discordAllowedGuilds) m.set("bridge_discord_allowed_guilds", config2.discordAllowedGuilds.join(","));
|
|
5381
|
+
m.set(
|
|
5382
|
+
"bridge_feishu_enabled",
|
|
5383
|
+
feishu?.enabled === true ? "true" : "false"
|
|
5384
|
+
);
|
|
5385
|
+
if (feishuConfig?.appId) m.set("bridge_feishu_app_id", feishuConfig.appId);
|
|
5386
|
+
if (feishuConfig?.appSecret) m.set("bridge_feishu_app_secret", feishuConfig.appSecret);
|
|
5387
|
+
if (feishuConfig?.site) m.set("bridge_feishu_site", feishuConfig.site);
|
|
5388
|
+
if (feishuConfig?.allowedUsers) m.set("bridge_feishu_allowed_users", feishuConfig.allowedUsers.join(","));
|
|
5389
|
+
m.set(
|
|
5390
|
+
"bridge_feishu_streaming_enabled",
|
|
5391
|
+
feishuConfig?.streamingEnabled === false ? "false" : "true"
|
|
5392
|
+
);
|
|
5393
|
+
m.set(
|
|
5394
|
+
"bridge_feishu_command_markdown_enabled",
|
|
5395
|
+
feishuConfig?.feedbackMarkdownEnabled === false ? "false" : "true"
|
|
5396
|
+
);
|
|
5397
|
+
m.set(
|
|
5398
|
+
"bridge_qq_enabled",
|
|
5399
|
+
config2.enabledChannels.includes("qq") ? "true" : "false"
|
|
5400
|
+
);
|
|
5401
|
+
if (config2.qqAppId) m.set("bridge_qq_app_id", config2.qqAppId);
|
|
5402
|
+
if (config2.qqAppSecret) m.set("bridge_qq_app_secret", config2.qqAppSecret);
|
|
5403
|
+
if (config2.qqAllowedUsers) m.set("bridge_qq_allowed_users", config2.qqAllowedUsers.join(","));
|
|
5404
|
+
if (config2.qqImageEnabled !== void 0) {
|
|
5405
|
+
m.set("bridge_qq_image_enabled", String(config2.qqImageEnabled));
|
|
5406
|
+
}
|
|
5407
|
+
if (config2.qqMaxImageSize !== void 0) {
|
|
5408
|
+
m.set("bridge_qq_max_image_size", String(config2.qqMaxImageSize));
|
|
5409
|
+
}
|
|
5410
|
+
m.set(
|
|
5411
|
+
"bridge_weixin_enabled",
|
|
5412
|
+
weixin?.enabled === true ? "true" : "false"
|
|
5413
|
+
);
|
|
5414
|
+
if (weixinConfig?.mediaEnabled !== void 0) {
|
|
5415
|
+
m.set("bridge_weixin_media_enabled", String(weixinConfig.mediaEnabled));
|
|
5416
|
+
}
|
|
5417
|
+
m.set(
|
|
5418
|
+
"bridge_weixin_command_markdown_enabled",
|
|
5419
|
+
weixinConfig?.feedbackMarkdownEnabled === true ? "true" : "false"
|
|
5420
|
+
);
|
|
5421
|
+
if (weixinConfig?.baseUrl) m.set("bridge_weixin_base_url", weixinConfig.baseUrl);
|
|
5422
|
+
if (weixinConfig?.cdnBaseUrl) m.set("bridge_weixin_cdn_base_url", weixinConfig.cdnBaseUrl);
|
|
5423
|
+
return m;
|
|
5424
|
+
}
|
|
5085
5425
|
|
|
5086
5426
|
// src/lib/bridge/markdown/feishu.ts
|
|
5087
5427
|
function hasComplexMarkdown(text2) {
|
|
@@ -5259,7 +5599,10 @@ var MIME_BY_TYPE = {
|
|
|
5259
5599
|
media: "application/octet-stream"
|
|
5260
5600
|
};
|
|
5261
5601
|
var FeishuAdapter = class extends BaseChannelAdapter {
|
|
5262
|
-
channelType
|
|
5602
|
+
channelType;
|
|
5603
|
+
provider = "feishu";
|
|
5604
|
+
alias;
|
|
5605
|
+
channelConfig;
|
|
5263
5606
|
running = false;
|
|
5264
5607
|
queue = [];
|
|
5265
5608
|
waiters = [];
|
|
@@ -5279,8 +5622,23 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5279
5622
|
cardCreatePromises = /* @__PURE__ */ new Map();
|
|
5280
5623
|
/** Cached tenant token for upload APIs. */
|
|
5281
5624
|
tenantTokenCache = null;
|
|
5625
|
+
constructor(instance) {
|
|
5626
|
+
super();
|
|
5627
|
+
this.channelType = instance?.id || "feishu";
|
|
5628
|
+
this.alias = instance?.alias;
|
|
5629
|
+
this.channelConfig = instance?.config || {};
|
|
5630
|
+
}
|
|
5631
|
+
get appId() {
|
|
5632
|
+
return this.channelConfig.appId?.trim() || "";
|
|
5633
|
+
}
|
|
5634
|
+
get appSecret() {
|
|
5635
|
+
return this.channelConfig.appSecret?.trim() || "";
|
|
5636
|
+
}
|
|
5637
|
+
get site() {
|
|
5638
|
+
return normalizeFeishuSite(this.channelConfig.site);
|
|
5639
|
+
}
|
|
5282
5640
|
isStreamingEnabled() {
|
|
5283
|
-
return
|
|
5641
|
+
return this.channelConfig.streamingEnabled !== false;
|
|
5284
5642
|
}
|
|
5285
5643
|
resolveStreamKey(chatId, streamKey) {
|
|
5286
5644
|
return streamKey?.trim() || chatId;
|
|
@@ -5293,10 +5651,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5293
5651
|
console.warn("[feishu-adapter] Cannot start:", configError);
|
|
5294
5652
|
return;
|
|
5295
5653
|
}
|
|
5296
|
-
const appId =
|
|
5297
|
-
const appSecret =
|
|
5298
|
-
const
|
|
5299
|
-
const domain =
|
|
5654
|
+
const appId = this.appId;
|
|
5655
|
+
const appSecret = this.appSecret;
|
|
5656
|
+
const site = this.site;
|
|
5657
|
+
const domain = site === "lark" ? lark.Domain.Lark : lark.Domain.Feishu;
|
|
5300
5658
|
this.restClient = new lark.Client({
|
|
5301
5659
|
appId,
|
|
5302
5660
|
appSecret,
|
|
@@ -5446,7 +5804,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5446
5804
|
const callbackMsg = {
|
|
5447
5805
|
messageId: messageId || `card_action_${Date.now()}`,
|
|
5448
5806
|
address: {
|
|
5449
|
-
channelType:
|
|
5807
|
+
channelType: this.channelType,
|
|
5808
|
+
channelProvider: this.provider,
|
|
5809
|
+
channelAlias: this.alias,
|
|
5450
5810
|
chatId,
|
|
5451
5811
|
userId
|
|
5452
5812
|
},
|
|
@@ -5649,7 +6009,16 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5649
6009
|
status: statusLabels[status] || status,
|
|
5650
6010
|
elapsed: formatElapsed(elapsedMs)
|
|
5651
6011
|
};
|
|
5652
|
-
const
|
|
6012
|
+
const existingText = state.pendingText || "";
|
|
6013
|
+
const trimmedExisting = existingText.trim();
|
|
6014
|
+
const trimmedResponse = responseText.trim();
|
|
6015
|
+
let finalText = trimmedResponse || trimmedExisting;
|
|
6016
|
+
if (status === "interrupted" && trimmedExisting && trimmedResponse && trimmedResponse !== trimmedExisting && !trimmedExisting.includes(trimmedResponse)) {
|
|
6017
|
+
finalText = `${trimmedExisting}
|
|
6018
|
+
|
|
6019
|
+
${trimmedResponse}`;
|
|
6020
|
+
}
|
|
6021
|
+
const finalCardJson = buildFinalCardJson(finalText, state.toolCalls, footer);
|
|
5653
6022
|
state.sequence++;
|
|
5654
6023
|
await cardkit.card.update({
|
|
5655
6024
|
path: { card_id: state.cardId },
|
|
@@ -5746,18 +6115,11 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5746
6115
|
return this.sendAsPost(message.address.chatId, text2);
|
|
5747
6116
|
}
|
|
5748
6117
|
getOpenApiBaseUrl() {
|
|
5749
|
-
|
|
5750
|
-
if (configured.startsWith("http://") || configured.startsWith("https://")) {
|
|
5751
|
-
return configured;
|
|
5752
|
-
}
|
|
5753
|
-
if (configured.toLowerCase() === "lark") {
|
|
5754
|
-
return "https://open.larksuite.com";
|
|
5755
|
-
}
|
|
5756
|
-
return "https://open.feishu.cn";
|
|
6118
|
+
return feishuSiteToApiBaseUrl(this.site);
|
|
5757
6119
|
}
|
|
5758
6120
|
async getTenantAccessToken() {
|
|
5759
|
-
const appId =
|
|
5760
|
-
const appSecret =
|
|
6121
|
+
const appId = this.appId;
|
|
6122
|
+
const appSecret = this.appSecret;
|
|
5761
6123
|
const domain = this.getOpenApiBaseUrl();
|
|
5762
6124
|
if (!appId || !appSecret) {
|
|
5763
6125
|
throw new Error("Feishu App ID / App Secret not configured");
|
|
@@ -5797,7 +6159,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5797
6159
|
return { ok: true, messageId: lastMessageId };
|
|
5798
6160
|
}
|
|
5799
6161
|
async sendAttachment(chatId, attachment, replyToMessageId) {
|
|
5800
|
-
if (!
|
|
6162
|
+
if (!fs2.existsSync(attachment.path)) {
|
|
5801
6163
|
return { ok: false, error: `Attachment not found: ${attachment.path}` };
|
|
5802
6164
|
}
|
|
5803
6165
|
try {
|
|
@@ -5823,10 +6185,10 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5823
6185
|
}
|
|
5824
6186
|
async uploadImage(attachment) {
|
|
5825
6187
|
const token = await this.getTenantAccessToken();
|
|
5826
|
-
const fileName = attachment.name ||
|
|
6188
|
+
const fileName = attachment.name || path2.basename(attachment.path) || "image.png";
|
|
5827
6189
|
const form = new FormData();
|
|
5828
6190
|
form.set("image_type", "message");
|
|
5829
|
-
form.set("image", new Blob([
|
|
6191
|
+
form.set("image", new Blob([fs2.readFileSync(attachment.path)]), fileName);
|
|
5830
6192
|
const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/images`, {
|
|
5831
6193
|
method: "POST",
|
|
5832
6194
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -5840,11 +6202,11 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
5840
6202
|
}
|
|
5841
6203
|
async uploadFile(attachment) {
|
|
5842
6204
|
const token = await this.getTenantAccessToken();
|
|
5843
|
-
const fileName = attachment.name ||
|
|
6205
|
+
const fileName = attachment.name || path2.basename(attachment.path) || "attachment.bin";
|
|
5844
6206
|
const form = new FormData();
|
|
5845
6207
|
form.set("file_type", "stream");
|
|
5846
6208
|
form.set("file_name", fileName);
|
|
5847
|
-
form.set("file", new Blob([
|
|
6209
|
+
form.set("file", new Blob([fs2.readFileSync(attachment.path)]), fileName);
|
|
5848
6210
|
const response = await fetch(`${this.getOpenApiBaseUrl()}/open-apis/im/v1/files`, {
|
|
5849
6211
|
method: "POST",
|
|
5850
6212
|
headers: { Authorization: `Bearer ${token}` },
|
|
@@ -6052,16 +6414,14 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6052
6414
|
}
|
|
6053
6415
|
// ── Config & Auth ───────────────────────────────────────────
|
|
6054
6416
|
validateConfig() {
|
|
6055
|
-
const
|
|
6056
|
-
if (
|
|
6057
|
-
const
|
|
6058
|
-
if (!
|
|
6059
|
-
const appSecret = getBridgeContext().store.getSetting("bridge_feishu_app_secret");
|
|
6060
|
-
if (!appSecret) return "bridge_feishu_app_secret not configured";
|
|
6417
|
+
const appId = this.appId;
|
|
6418
|
+
if (!appId) return "Feishu App ID \u672A\u914D\u7F6E";
|
|
6419
|
+
const appSecret = this.appSecret;
|
|
6420
|
+
if (!appSecret) return "Feishu App Secret \u672A\u914D\u7F6E";
|
|
6061
6421
|
return null;
|
|
6062
6422
|
}
|
|
6063
6423
|
isAuthorized(userId, chatId) {
|
|
6064
|
-
const allowedUsers =
|
|
6424
|
+
const allowedUsers = (this.channelConfig.allowedUsers || []).join(",");
|
|
6065
6425
|
if (!allowedUsers) {
|
|
6066
6426
|
return true;
|
|
6067
6427
|
}
|
|
@@ -6111,7 +6471,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6111
6471
|
console.log("[feishu-adapter] Group message ignored (bot not @mentioned), chatId:", chatId, "msgId:", msg.message_id);
|
|
6112
6472
|
try {
|
|
6113
6473
|
getBridgeContext().store.insertAuditLog({
|
|
6114
|
-
channelType:
|
|
6474
|
+
channelType: this.channelType,
|
|
6475
|
+
channelProvider: this.provider,
|
|
6476
|
+
channelAlias: this.alias,
|
|
6115
6477
|
chatId,
|
|
6116
6478
|
direction: "inbound",
|
|
6117
6479
|
messageId: msg.message_id,
|
|
@@ -6140,7 +6502,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6140
6502
|
text2 = "[image download failed]";
|
|
6141
6503
|
try {
|
|
6142
6504
|
getBridgeContext().store.insertAuditLog({
|
|
6143
|
-
channelType:
|
|
6505
|
+
channelType: this.channelType,
|
|
6506
|
+
channelProvider: this.provider,
|
|
6507
|
+
channelAlias: this.alias,
|
|
6144
6508
|
chatId,
|
|
6145
6509
|
direction: "inbound",
|
|
6146
6510
|
messageId: msg.message_id,
|
|
@@ -6161,7 +6525,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6161
6525
|
text2 = `[${messageType} download failed]`;
|
|
6162
6526
|
try {
|
|
6163
6527
|
getBridgeContext().store.insertAuditLog({
|
|
6164
|
-
channelType:
|
|
6528
|
+
channelType: this.channelType,
|
|
6529
|
+
channelProvider: this.provider,
|
|
6530
|
+
channelAlias: this.alias,
|
|
6165
6531
|
chatId,
|
|
6166
6532
|
direction: "inbound",
|
|
6167
6533
|
messageId: msg.message_id,
|
|
@@ -6188,7 +6554,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6188
6554
|
if (!text2.trim() && attachments.length === 0) return;
|
|
6189
6555
|
const timestamp = parseInt(msg.create_time, 10) || Date.now();
|
|
6190
6556
|
const address = {
|
|
6191
|
-
channelType:
|
|
6557
|
+
channelType: this.channelType,
|
|
6558
|
+
channelProvider: this.provider,
|
|
6559
|
+
channelAlias: this.alias,
|
|
6192
6560
|
chatId,
|
|
6193
6561
|
userId
|
|
6194
6562
|
};
|
|
@@ -6220,7 +6588,9 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6220
6588
|
try {
|
|
6221
6589
|
const summary = attachments.length > 0 ? `[${attachments.length} attachment(s)] ${text2.slice(0, 150)}` : text2.slice(0, 200);
|
|
6222
6590
|
getBridgeContext().store.insertAuditLog({
|
|
6223
|
-
channelType:
|
|
6591
|
+
channelType: this.channelType,
|
|
6592
|
+
channelProvider: this.provider,
|
|
6593
|
+
channelAlias: this.alias,
|
|
6224
6594
|
chatId,
|
|
6225
6595
|
direction: "inbound",
|
|
6226
6596
|
messageId: msg.message_id,
|
|
@@ -6435,7 +6805,7 @@ var FeishuAdapter = class extends BaseChannelAdapter {
|
|
|
6435
6805
|
}
|
|
6436
6806
|
}
|
|
6437
6807
|
};
|
|
6438
|
-
registerAdapterFactory("feishu", () => new FeishuAdapter());
|
|
6808
|
+
registerAdapterFactory("feishu", (instance) => new FeishuAdapter(instance));
|
|
6439
6809
|
|
|
6440
6810
|
// src/lib/bridge/adapters/discord-adapter.ts
|
|
6441
6811
|
import crypto3 from "crypto";
|
|
@@ -6453,6 +6823,7 @@ async function loadDiscordJs() {
|
|
|
6453
6823
|
}
|
|
6454
6824
|
var DiscordAdapter = class extends BaseChannelAdapter {
|
|
6455
6825
|
channelType = "discord";
|
|
6826
|
+
provider = "discord";
|
|
6456
6827
|
running = false;
|
|
6457
6828
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6458
6829
|
client = null;
|
|
@@ -7039,6 +7410,7 @@ async function sendPrivateMessage(accessToken, params) {
|
|
|
7039
7410
|
// src/lib/bridge/adapters/qq-adapter.ts
|
|
7040
7411
|
var QQAdapter = class extends BaseChannelAdapter {
|
|
7041
7412
|
channelType = "qq";
|
|
7413
|
+
provider = "qq";
|
|
7042
7414
|
_running = false;
|
|
7043
7415
|
queue = [];
|
|
7044
7416
|
waiters = [];
|
|
@@ -7429,218 +7801,6 @@ registerAdapterFactory("qq", () => new QQAdapter());
|
|
|
7429
7801
|
// src/weixin-store.ts
|
|
7430
7802
|
import fs3 from "node:fs";
|
|
7431
7803
|
import path3 from "node:path";
|
|
7432
|
-
|
|
7433
|
-
// src/config.ts
|
|
7434
|
-
import fs2 from "node:fs";
|
|
7435
|
-
import os from "node:os";
|
|
7436
|
-
import path2 from "node:path";
|
|
7437
|
-
var LEGACY_CTI_HOME = path2.join(os.homedir(), ".claude-to-im");
|
|
7438
|
-
var DEFAULT_CTI_HOME = path2.join(os.homedir(), ".codex-to-im");
|
|
7439
|
-
var DEFAULT_WORKSPACE_ROOT = path2.join(os.homedir(), "cx2im");
|
|
7440
|
-
function resolveDefaultCtiHome() {
|
|
7441
|
-
if (fs2.existsSync(DEFAULT_CTI_HOME)) return DEFAULT_CTI_HOME;
|
|
7442
|
-
if (fs2.existsSync(LEGACY_CTI_HOME)) return LEGACY_CTI_HOME;
|
|
7443
|
-
return DEFAULT_CTI_HOME;
|
|
7444
|
-
}
|
|
7445
|
-
var CTI_HOME = process.env.CTI_HOME || resolveDefaultCtiHome();
|
|
7446
|
-
var CONFIG_PATH = path2.join(CTI_HOME, "config.env");
|
|
7447
|
-
function expandHomePath(value) {
|
|
7448
|
-
if (!value) return value;
|
|
7449
|
-
if (value === "~") return os.homedir();
|
|
7450
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
7451
|
-
return path2.join(os.homedir(), value.slice(2));
|
|
7452
|
-
}
|
|
7453
|
-
return value;
|
|
7454
|
-
}
|
|
7455
|
-
function parseEnvFile(content) {
|
|
7456
|
-
const entries = /* @__PURE__ */ new Map();
|
|
7457
|
-
for (const line of content.split("\n")) {
|
|
7458
|
-
const trimmed = line.trim();
|
|
7459
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
7460
|
-
const eqIdx = trimmed.indexOf("=");
|
|
7461
|
-
if (eqIdx === -1) continue;
|
|
7462
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
7463
|
-
let value = trimmed.slice(eqIdx + 1).trim();
|
|
7464
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
7465
|
-
value = value.slice(1, -1);
|
|
7466
|
-
}
|
|
7467
|
-
entries.set(key, value);
|
|
7468
|
-
}
|
|
7469
|
-
return entries;
|
|
7470
|
-
}
|
|
7471
|
-
function loadRawConfigEnv() {
|
|
7472
|
-
try {
|
|
7473
|
-
return parseEnvFile(fs2.readFileSync(CONFIG_PATH, "utf-8"));
|
|
7474
|
-
} catch {
|
|
7475
|
-
return /* @__PURE__ */ new Map();
|
|
7476
|
-
}
|
|
7477
|
-
}
|
|
7478
|
-
function splitCsv(value) {
|
|
7479
|
-
if (!value) return void 0;
|
|
7480
|
-
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7481
|
-
}
|
|
7482
|
-
function parsePositiveInt(value) {
|
|
7483
|
-
if (!value) return void 0;
|
|
7484
|
-
const parsed = Number(value);
|
|
7485
|
-
if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
|
|
7486
|
-
return Math.floor(parsed);
|
|
7487
|
-
}
|
|
7488
|
-
function parseSandboxMode(value) {
|
|
7489
|
-
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
7490
|
-
return value;
|
|
7491
|
-
}
|
|
7492
|
-
return void 0;
|
|
7493
|
-
}
|
|
7494
|
-
function parseReasoningEffort(value) {
|
|
7495
|
-
if (value === "minimal" || value === "low" || value === "medium" || value === "high" || value === "xhigh") {
|
|
7496
|
-
return value;
|
|
7497
|
-
}
|
|
7498
|
-
return void 0;
|
|
7499
|
-
}
|
|
7500
|
-
function loadConfig() {
|
|
7501
|
-
const env = loadRawConfigEnv();
|
|
7502
|
-
const rawRuntime = env.get("CTI_RUNTIME") || "codex";
|
|
7503
|
-
const runtime = ["claude", "codex", "auto"].includes(rawRuntime) ? rawRuntime : "codex";
|
|
7504
|
-
return {
|
|
7505
|
-
runtime,
|
|
7506
|
-
enabledChannels: splitCsv(env.get("CTI_ENABLED_CHANNELS")) ?? ["feishu"],
|
|
7507
|
-
defaultWorkspaceRoot: expandHomePath(env.get("CTI_DEFAULT_WORKSPACE_ROOT")) || void 0,
|
|
7508
|
-
defaultModel: env.get("CTI_DEFAULT_MODEL") || void 0,
|
|
7509
|
-
defaultMode: env.get("CTI_DEFAULT_MODE") || "code",
|
|
7510
|
-
historyMessageLimit: parsePositiveInt(env.get("CTI_HISTORY_MESSAGE_LIMIT")) ?? 8,
|
|
7511
|
-
codexSkipGitRepoCheck: env.has("CTI_CODEX_SKIP_GIT_REPO_CHECK") ? env.get("CTI_CODEX_SKIP_GIT_REPO_CHECK") === "true" : true,
|
|
7512
|
-
codexSandboxMode: parseSandboxMode(env.get("CTI_CODEX_SANDBOX_MODE")) ?? "workspace-write",
|
|
7513
|
-
codexReasoningEffort: parseReasoningEffort(env.get("CTI_CODEX_REASONING_EFFORT")) ?? "medium",
|
|
7514
|
-
uiAllowLan: env.get("CTI_UI_ALLOW_LAN") === "true",
|
|
7515
|
-
uiAccessToken: env.get("CTI_UI_ACCESS_TOKEN") || void 0,
|
|
7516
|
-
tgBotToken: env.get("CTI_TG_BOT_TOKEN") || void 0,
|
|
7517
|
-
tgChatId: env.get("CTI_TG_CHAT_ID") || void 0,
|
|
7518
|
-
tgAllowedUsers: splitCsv(env.get("CTI_TG_ALLOWED_USERS")),
|
|
7519
|
-
feishuAppId: env.get("CTI_FEISHU_APP_ID") || void 0,
|
|
7520
|
-
feishuAppSecret: env.get("CTI_FEISHU_APP_SECRET") || void 0,
|
|
7521
|
-
feishuDomain: env.get("CTI_FEISHU_DOMAIN") || void 0,
|
|
7522
|
-
feishuAllowedUsers: splitCsv(env.get("CTI_FEISHU_ALLOWED_USERS")),
|
|
7523
|
-
feishuStreamingEnabled: env.has("CTI_FEISHU_STREAMING_ENABLED") ? env.get("CTI_FEISHU_STREAMING_ENABLED") === "true" : true,
|
|
7524
|
-
feishuCommandMarkdownEnabled: env.has("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_FEISHU_COMMAND_MARKDOWN_ENABLED") === "true" : true,
|
|
7525
|
-
discordBotToken: env.get("CTI_DISCORD_BOT_TOKEN") || void 0,
|
|
7526
|
-
discordAllowedUsers: splitCsv(env.get("CTI_DISCORD_ALLOWED_USERS")),
|
|
7527
|
-
discordAllowedChannels: splitCsv(
|
|
7528
|
-
env.get("CTI_DISCORD_ALLOWED_CHANNELS")
|
|
7529
|
-
),
|
|
7530
|
-
discordAllowedGuilds: splitCsv(env.get("CTI_DISCORD_ALLOWED_GUILDS")),
|
|
7531
|
-
qqAppId: env.get("CTI_QQ_APP_ID") || void 0,
|
|
7532
|
-
qqAppSecret: env.get("CTI_QQ_APP_SECRET") || void 0,
|
|
7533
|
-
qqAllowedUsers: splitCsv(env.get("CTI_QQ_ALLOWED_USERS")),
|
|
7534
|
-
qqImageEnabled: env.has("CTI_QQ_IMAGE_ENABLED") ? env.get("CTI_QQ_IMAGE_ENABLED") === "true" : void 0,
|
|
7535
|
-
qqMaxImageSize: env.get("CTI_QQ_MAX_IMAGE_SIZE") ? Number(env.get("CTI_QQ_MAX_IMAGE_SIZE")) : void 0,
|
|
7536
|
-
weixinBaseUrl: env.get("CTI_WEIXIN_BASE_URL") || void 0,
|
|
7537
|
-
weixinCdnBaseUrl: env.get("CTI_WEIXIN_CDN_BASE_URL") || void 0,
|
|
7538
|
-
weixinMediaEnabled: env.has("CTI_WEIXIN_MEDIA_ENABLED") ? env.get("CTI_WEIXIN_MEDIA_ENABLED") === "true" : void 0,
|
|
7539
|
-
weixinCommandMarkdownEnabled: env.has("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") ? env.get("CTI_WEIXIN_COMMAND_MARKDOWN_ENABLED") === "true" : false,
|
|
7540
|
-
autoApprove: env.get("CTI_AUTO_APPROVE") === "true"
|
|
7541
|
-
};
|
|
7542
|
-
}
|
|
7543
|
-
function configToSettings(config2) {
|
|
7544
|
-
const m = /* @__PURE__ */ new Map();
|
|
7545
|
-
m.set("remote_bridge_enabled", "true");
|
|
7546
|
-
m.set(
|
|
7547
|
-
"bridge_telegram_enabled",
|
|
7548
|
-
config2.enabledChannels.includes("telegram") ? "true" : "false"
|
|
7549
|
-
);
|
|
7550
|
-
if (config2.tgBotToken) m.set("telegram_bot_token", config2.tgBotToken);
|
|
7551
|
-
if (config2.tgAllowedUsers)
|
|
7552
|
-
m.set("telegram_bridge_allowed_users", config2.tgAllowedUsers.join(","));
|
|
7553
|
-
if (config2.tgChatId) m.set("telegram_chat_id", config2.tgChatId);
|
|
7554
|
-
m.set(
|
|
7555
|
-
"bridge_discord_enabled",
|
|
7556
|
-
config2.enabledChannels.includes("discord") ? "true" : "false"
|
|
7557
|
-
);
|
|
7558
|
-
if (config2.discordBotToken)
|
|
7559
|
-
m.set("bridge_discord_bot_token", config2.discordBotToken);
|
|
7560
|
-
if (config2.discordAllowedUsers)
|
|
7561
|
-
m.set("bridge_discord_allowed_users", config2.discordAllowedUsers.join(","));
|
|
7562
|
-
if (config2.discordAllowedChannels)
|
|
7563
|
-
m.set(
|
|
7564
|
-
"bridge_discord_allowed_channels",
|
|
7565
|
-
config2.discordAllowedChannels.join(",")
|
|
7566
|
-
);
|
|
7567
|
-
if (config2.discordAllowedGuilds)
|
|
7568
|
-
m.set(
|
|
7569
|
-
"bridge_discord_allowed_guilds",
|
|
7570
|
-
config2.discordAllowedGuilds.join(",")
|
|
7571
|
-
);
|
|
7572
|
-
m.set(
|
|
7573
|
-
"bridge_feishu_enabled",
|
|
7574
|
-
config2.enabledChannels.includes("feishu") ? "true" : "false"
|
|
7575
|
-
);
|
|
7576
|
-
if (config2.feishuAppId) m.set("bridge_feishu_app_id", config2.feishuAppId);
|
|
7577
|
-
if (config2.feishuAppSecret)
|
|
7578
|
-
m.set("bridge_feishu_app_secret", config2.feishuAppSecret);
|
|
7579
|
-
if (config2.feishuDomain) m.set("bridge_feishu_domain", config2.feishuDomain);
|
|
7580
|
-
if (config2.feishuAllowedUsers)
|
|
7581
|
-
m.set("bridge_feishu_allowed_users", config2.feishuAllowedUsers.join(","));
|
|
7582
|
-
m.set(
|
|
7583
|
-
"bridge_feishu_streaming_enabled",
|
|
7584
|
-
config2.feishuStreamingEnabled === false ? "false" : "true"
|
|
7585
|
-
);
|
|
7586
|
-
m.set(
|
|
7587
|
-
"bridge_feishu_command_markdown_enabled",
|
|
7588
|
-
config2.feishuCommandMarkdownEnabled === false ? "false" : "true"
|
|
7589
|
-
);
|
|
7590
|
-
m.set(
|
|
7591
|
-
"bridge_qq_enabled",
|
|
7592
|
-
config2.enabledChannels.includes("qq") ? "true" : "false"
|
|
7593
|
-
);
|
|
7594
|
-
if (config2.qqAppId) m.set("bridge_qq_app_id", config2.qqAppId);
|
|
7595
|
-
if (config2.qqAppSecret) m.set("bridge_qq_app_secret", config2.qqAppSecret);
|
|
7596
|
-
if (config2.qqAllowedUsers)
|
|
7597
|
-
m.set("bridge_qq_allowed_users", config2.qqAllowedUsers.join(","));
|
|
7598
|
-
if (config2.qqImageEnabled !== void 0)
|
|
7599
|
-
m.set("bridge_qq_image_enabled", String(config2.qqImageEnabled));
|
|
7600
|
-
if (config2.qqMaxImageSize !== void 0)
|
|
7601
|
-
m.set("bridge_qq_max_image_size", String(config2.qqMaxImageSize));
|
|
7602
|
-
m.set(
|
|
7603
|
-
"bridge_weixin_enabled",
|
|
7604
|
-
config2.enabledChannels.includes("weixin") ? "true" : "false"
|
|
7605
|
-
);
|
|
7606
|
-
if (config2.weixinMediaEnabled !== void 0)
|
|
7607
|
-
m.set("bridge_weixin_media_enabled", String(config2.weixinMediaEnabled));
|
|
7608
|
-
m.set(
|
|
7609
|
-
"bridge_weixin_command_markdown_enabled",
|
|
7610
|
-
config2.weixinCommandMarkdownEnabled === true ? "true" : "false"
|
|
7611
|
-
);
|
|
7612
|
-
if (config2.weixinBaseUrl)
|
|
7613
|
-
m.set("bridge_weixin_base_url", config2.weixinBaseUrl);
|
|
7614
|
-
if (config2.weixinCdnBaseUrl)
|
|
7615
|
-
m.set("bridge_weixin_cdn_base_url", config2.weixinCdnBaseUrl);
|
|
7616
|
-
if (config2.defaultWorkspaceRoot) {
|
|
7617
|
-
m.set("bridge_default_workspace_root", config2.defaultWorkspaceRoot);
|
|
7618
|
-
}
|
|
7619
|
-
if (config2.defaultModel) {
|
|
7620
|
-
m.set("bridge_default_model", config2.defaultModel);
|
|
7621
|
-
m.set("default_model", config2.defaultModel);
|
|
7622
|
-
}
|
|
7623
|
-
m.set("bridge_default_mode", config2.defaultMode);
|
|
7624
|
-
m.set(
|
|
7625
|
-
"bridge_history_message_limit",
|
|
7626
|
-
String(config2.historyMessageLimit && config2.historyMessageLimit > 0 ? config2.historyMessageLimit : 8)
|
|
7627
|
-
);
|
|
7628
|
-
m.set(
|
|
7629
|
-
"bridge_codex_skip_git_repo_check",
|
|
7630
|
-
config2.codexSkipGitRepoCheck === true ? "true" : "false"
|
|
7631
|
-
);
|
|
7632
|
-
m.set(
|
|
7633
|
-
"bridge_codex_sandbox_mode",
|
|
7634
|
-
config2.codexSandboxMode || "workspace-write"
|
|
7635
|
-
);
|
|
7636
|
-
m.set(
|
|
7637
|
-
"bridge_codex_reasoning_effort",
|
|
7638
|
-
config2.codexReasoningEffort || "medium"
|
|
7639
|
-
);
|
|
7640
|
-
return m;
|
|
7641
|
-
}
|
|
7642
|
-
|
|
7643
|
-
// src/weixin-store.ts
|
|
7644
7804
|
var DATA_DIR = path3.join(CTI_HOME, "data");
|
|
7645
7805
|
var ACCOUNTS_PATH = path3.join(DATA_DIR, "weixin-accounts.json");
|
|
7646
7806
|
var CONTEXT_TOKENS_PATH = path3.join(DATA_DIR, "weixin-context-tokens.json");
|
|
@@ -13975,7 +14135,10 @@ var DEDUP_MAX3 = 500;
|
|
|
13975
14135
|
var BACKOFF_BASE_MS = 2e3;
|
|
13976
14136
|
var BACKOFF_MAX_MS = 3e4;
|
|
13977
14137
|
var WeixinAdapter = class extends BaseChannelAdapter {
|
|
13978
|
-
channelType
|
|
14138
|
+
channelType;
|
|
14139
|
+
provider = "weixin";
|
|
14140
|
+
alias;
|
|
14141
|
+
channelConfig;
|
|
13979
14142
|
_running = false;
|
|
13980
14143
|
idleLogged = false;
|
|
13981
14144
|
reconcileTimer = null;
|
|
@@ -13988,6 +14151,19 @@ var WeixinAdapter = class extends BaseChannelAdapter {
|
|
|
13988
14151
|
typingTickets = /* @__PURE__ */ new Map();
|
|
13989
14152
|
pendingCursors = /* @__PURE__ */ new Map();
|
|
13990
14153
|
nextBatchId = 1;
|
|
14154
|
+
constructor(instance) {
|
|
14155
|
+
super();
|
|
14156
|
+
this.channelType = instance?.id || "weixin";
|
|
14157
|
+
this.alias = instance?.alias;
|
|
14158
|
+
this.channelConfig = instance?.config || {};
|
|
14159
|
+
}
|
|
14160
|
+
get mediaEnabled() {
|
|
14161
|
+
return this.channelConfig.mediaEnabled === true;
|
|
14162
|
+
}
|
|
14163
|
+
get configuredAccountId() {
|
|
14164
|
+
const value = this.channelConfig.accountId?.trim();
|
|
14165
|
+
return value || void 0;
|
|
14166
|
+
}
|
|
13991
14167
|
async start() {
|
|
13992
14168
|
if (this._running) return;
|
|
13993
14169
|
this._running = true;
|
|
@@ -14063,7 +14239,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
|
|
|
14063
14239
|
}
|
|
14064
14240
|
}
|
|
14065
14241
|
validateConfig() {
|
|
14066
|
-
const linkedAccounts =
|
|
14242
|
+
const linkedAccounts = this.filterConfiguredAccounts(
|
|
14243
|
+
listWeixinAccounts().filter((account) => account.enabled && account.token)
|
|
14244
|
+
);
|
|
14067
14245
|
if (linkedAccounts.length === 0) {
|
|
14068
14246
|
return "No linked WeChat account. Run the WeChat QR login helper first.";
|
|
14069
14247
|
}
|
|
@@ -14095,7 +14273,9 @@ var WeixinAdapter = class extends BaseChannelAdapter {
|
|
|
14095
14273
|
this.queue.push(message);
|
|
14096
14274
|
}
|
|
14097
14275
|
async reconcileAccounts() {
|
|
14098
|
-
const linkedAccounts =
|
|
14276
|
+
const linkedAccounts = this.filterConfiguredAccounts(
|
|
14277
|
+
listWeixinAccounts().filter((account) => account.enabled && account.token)
|
|
14278
|
+
);
|
|
14099
14279
|
if (linkedAccounts.length === 0 && this.pollAborts.size === 0) {
|
|
14100
14280
|
if (!this.idleLogged) {
|
|
14101
14281
|
console.log("[weixin-adapter] No linked WeChat account is enabled, adapter started but idle");
|
|
@@ -14233,7 +14413,7 @@ var WeixinAdapter = class extends BaseChannelAdapter {
|
|
|
14233
14413
|
const attachments = [];
|
|
14234
14414
|
let failedCount = 0;
|
|
14235
14415
|
let missingVoiceTranscriptCount = 0;
|
|
14236
|
-
const mediaEnabled =
|
|
14416
|
+
const mediaEnabled = this.mediaEnabled;
|
|
14237
14417
|
const account = mediaEnabled ? getWeixinAccount(accountId) : void 0;
|
|
14238
14418
|
const creds = account ? this.accountToCreds(account) : void 0;
|
|
14239
14419
|
for (const item of message.item_list || []) {
|
|
@@ -14285,7 +14465,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
|
|
|
14285
14465
|
const inbound = {
|
|
14286
14466
|
messageId: message.message_id || `weixin_${accountId}_${message.seq || Date.now()}`,
|
|
14287
14467
|
address: {
|
|
14288
|
-
channelType:
|
|
14468
|
+
channelType: this.channelType,
|
|
14469
|
+
channelProvider: this.provider,
|
|
14470
|
+
channelAlias: this.alias,
|
|
14289
14471
|
chatId,
|
|
14290
14472
|
userId: message.from_user_id,
|
|
14291
14473
|
displayName: message.from_user_id.slice(0, 12)
|
|
@@ -14316,7 +14498,9 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
|
|
|
14316
14498
|
this.enqueue(inbound);
|
|
14317
14499
|
const summary = attachments.length > 0 ? `[${attachments.length} attachment(s)] ${inbound.text.slice(0, 150)}` : missingVoiceTranscriptCount > 0 && !inbound.text ? "[voice transcription unavailable]" : failedCount > 0 && !inbound.text ? `[${failedCount} attachment(s) failed]` : inbound.text.slice(0, 200);
|
|
14318
14500
|
getBridgeContext().store.insertAuditLog({
|
|
14319
|
-
channelType:
|
|
14501
|
+
channelType: this.channelType,
|
|
14502
|
+
channelProvider: this.provider,
|
|
14503
|
+
channelAlias: this.alias,
|
|
14320
14504
|
chatId,
|
|
14321
14505
|
direction: "inbound",
|
|
14322
14506
|
messageId: inbound.messageId,
|
|
@@ -14379,6 +14563,10 @@ ${failureNote}` : attachments.length > 0 ? failureNote : text2;
|
|
|
14379
14563
|
getBridgeContext().store.setChannelOffset(batch.offsetKey, batch.cursor);
|
|
14380
14564
|
this.pendingCursors.delete(updateId);
|
|
14381
14565
|
}
|
|
14566
|
+
filterConfiguredAccounts(accounts) {
|
|
14567
|
+
if (!this.configuredAccountId) return accounts;
|
|
14568
|
+
return accounts.filter((account) => account.accountId === this.configuredAccountId);
|
|
14569
|
+
}
|
|
14382
14570
|
};
|
|
14383
14571
|
function stripFormatting(text2, parseMode) {
|
|
14384
14572
|
if (parseMode === "HTML") {
|
|
@@ -14389,7 +14577,7 @@ function stripFormatting(text2, parseMode) {
|
|
|
14389
14577
|
}
|
|
14390
14578
|
return text2;
|
|
14391
14579
|
}
|
|
14392
|
-
registerAdapterFactory("weixin", () => new WeixinAdapter());
|
|
14580
|
+
registerAdapterFactory("weixin", (instance) => new WeixinAdapter(instance));
|
|
14393
14581
|
|
|
14394
14582
|
// src/session-bindings.ts
|
|
14395
14583
|
import path5 from "node:path";
|
|
@@ -15073,8 +15261,24 @@ function readDesktopSessionEventStream(threadId) {
|
|
|
15073
15261
|
}
|
|
15074
15262
|
|
|
15075
15263
|
// src/session-bindings.ts
|
|
15076
|
-
function
|
|
15077
|
-
return
|
|
15264
|
+
function asChannelProvider(value) {
|
|
15265
|
+
return value === "feishu" || value === "weixin" ? value : void 0;
|
|
15266
|
+
}
|
|
15267
|
+
function resolveChannelMeta(channelType, provider) {
|
|
15268
|
+
const instance = findChannelInstance(channelType, loadConfig());
|
|
15269
|
+
if (instance) {
|
|
15270
|
+
return {
|
|
15271
|
+
provider: instance.provider,
|
|
15272
|
+
alias: instance.alias
|
|
15273
|
+
};
|
|
15274
|
+
}
|
|
15275
|
+
return {
|
|
15276
|
+
provider,
|
|
15277
|
+
alias: channelType
|
|
15278
|
+
};
|
|
15279
|
+
}
|
|
15280
|
+
function formatChannelLabel(binding) {
|
|
15281
|
+
return binding.channelAlias?.trim() || resolveChannelMeta(binding.channelType, asChannelProvider(binding.channelProvider)).alias || binding.channelType;
|
|
15078
15282
|
}
|
|
15079
15283
|
function formatBindingChatTarget(binding) {
|
|
15080
15284
|
return binding.chatDisplayName?.trim() || binding.chatId;
|
|
@@ -15095,7 +15299,7 @@ function assertBindingTargetAvailable(store, current, opts) {
|
|
|
15095
15299
|
);
|
|
15096
15300
|
if (!conflict) return;
|
|
15097
15301
|
throw new Error(
|
|
15098
|
-
`\u8BE5\u4F1A\u8BDD\u5DF2\u7ED1\u5B9A\u5230 ${formatChannelLabel(conflict
|
|
15302
|
+
`\u8BE5\u4F1A\u8BDD\u5DF2\u7ED1\u5B9A\u5230 ${formatChannelLabel(conflict)} \u804A\u5929 ${formatBindingChatTarget(conflict)}\u3002\u4E00\u4E2A\u4F1A\u8BDD\u53EA\u80FD\u7ED1\u5B9A\u4E00\u4E2A\u804A\u5929\u3002`
|
|
15099
15303
|
);
|
|
15100
15304
|
}
|
|
15101
15305
|
function getSessionMode(store, session) {
|
|
@@ -15109,8 +15313,11 @@ function bindStoreToSession(store, channelType, chatId, sessionId) {
|
|
|
15109
15313
|
{ channelType, chatId },
|
|
15110
15314
|
{ sessionId: session.id, sdkSessionId: session.sdk_session_id || void 0 }
|
|
15111
15315
|
);
|
|
15316
|
+
const meta = resolveChannelMeta(channelType);
|
|
15112
15317
|
return store.upsertChannelBinding({
|
|
15113
15318
|
channelType,
|
|
15319
|
+
channelProvider: meta.provider,
|
|
15320
|
+
channelAlias: meta.alias,
|
|
15114
15321
|
chatId,
|
|
15115
15322
|
codepilotSessionId: session.id,
|
|
15116
15323
|
sdkSessionId: session.sdk_session_id || "",
|
|
@@ -15125,10 +15332,13 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
15125
15332
|
{ channelType, chatId },
|
|
15126
15333
|
{ sdkSessionId }
|
|
15127
15334
|
);
|
|
15335
|
+
const meta = resolveChannelMeta(channelType);
|
|
15128
15336
|
const existing = store.findSessionBySdkSessionId(sdkSessionId);
|
|
15129
15337
|
if (existing) {
|
|
15130
15338
|
return store.upsertChannelBinding({
|
|
15131
15339
|
channelType,
|
|
15340
|
+
channelProvider: meta.provider,
|
|
15341
|
+
channelAlias: meta.alias,
|
|
15132
15342
|
chatId,
|
|
15133
15343
|
codepilotSessionId: existing.id,
|
|
15134
15344
|
sdkSessionId,
|
|
@@ -15150,6 +15360,8 @@ function bindStoreToSdkSession(store, channelType, chatId, sdkSessionId, opts) {
|
|
|
15150
15360
|
store.updateSdkSessionId(session.id, sdkSessionId);
|
|
15151
15361
|
return store.upsertChannelBinding({
|
|
15152
15362
|
channelType,
|
|
15363
|
+
channelProvider: meta.provider,
|
|
15364
|
+
channelAlias: meta.alias,
|
|
15153
15365
|
chatId,
|
|
15154
15366
|
codepilotSessionId: session.id,
|
|
15155
15367
|
sdkSessionId,
|
|
@@ -15388,8 +15600,8 @@ function stripOutboundArtifactBlocksForStreaming(text2) {
|
|
|
15388
15600
|
}
|
|
15389
15601
|
return stripped.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
15390
15602
|
}
|
|
15391
|
-
function supportsOutboundArtifacts(
|
|
15392
|
-
return
|
|
15603
|
+
function supportsOutboundArtifacts(provider) {
|
|
15604
|
+
return provider === "feishu";
|
|
15393
15605
|
}
|
|
15394
15606
|
|
|
15395
15607
|
// src/lib/bridge/conversation-engine.ts
|
|
@@ -15931,9 +16143,9 @@ async function deliver(adapter, message, opts) {
|
|
|
15931
16143
|
} catch {
|
|
15932
16144
|
}
|
|
15933
16145
|
}
|
|
15934
|
-
const limit = PLATFORM_LIMITS[adapter.channelType] || 4096;
|
|
16146
|
+
const limit = PLATFORM_LIMITS[adapter.provider] || PLATFORM_LIMITS[adapter.channelType] || 4096;
|
|
15935
16147
|
let chunks = chunkText2(message.text, limit);
|
|
15936
|
-
if (adapter.
|
|
16148
|
+
if (adapter.provider === "qq" && chunks.length > 3) {
|
|
15937
16149
|
const first2 = chunks.slice(0, 2);
|
|
15938
16150
|
let merged = chunks.slice(2).join("\n");
|
|
15939
16151
|
if (merged.length > limit) {
|
|
@@ -16114,8 +16326,8 @@ async function forwardPermissionRequest(adapter, address, permissionRequestId, t
|
|
|
16114
16326
|
const inputStr = JSON.stringify(toolInput, null, 2);
|
|
16115
16327
|
const truncatedInput = inputStr.length > 300 ? inputStr.slice(0, 300) + "..." : inputStr;
|
|
16116
16328
|
let result;
|
|
16117
|
-
if (adapter.
|
|
16118
|
-
const channelLabel = adapter.
|
|
16329
|
+
if (adapter.provider === "qq" || adapter.provider === "weixin") {
|
|
16330
|
+
const channelLabel = adapter.provider === "weixin" ? "WeChat" : "QQ";
|
|
16119
16331
|
const plainText = [
|
|
16120
16332
|
`Permission Required`,
|
|
16121
16333
|
``,
|
|
@@ -16798,7 +17010,8 @@ var MIRROR_WATCH_DEBOUNCE_MS = 350;
|
|
|
16798
17010
|
var MIRROR_EVENT_BATCH_LIMIT = 8;
|
|
16799
17011
|
var MIRROR_SUPPRESSION_WINDOW_MS = 4e3;
|
|
16800
17012
|
var MIRROR_PROMPT_MATCH_GRACE_MS = 12e4;
|
|
16801
|
-
var
|
|
17013
|
+
var INTERACTIVE_IDLE_REMINDER_MS = 6e5;
|
|
17014
|
+
var MIRROR_IDLE_TIMEOUT_MS = 6e5;
|
|
16802
17015
|
var AVAILABLE_CODEX_MODELS = listSelectableCodexModels();
|
|
16803
17016
|
var AVAILABLE_CODEX_MODEL_MAP = new Map(AVAILABLE_CODEX_MODELS.map((model) => [model.slug, model]));
|
|
16804
17017
|
function generateDraftId() {
|
|
@@ -16817,6 +17030,27 @@ function getStreamConfig(channelType = "telegram") {
|
|
|
16817
17030
|
const maxChars = parseInt(store.getSetting(`${prefix}max_chars`) || "", 10) || defaults.maxChars;
|
|
16818
17031
|
return { intervalMs, minDeltaChars, maxChars };
|
|
16819
17032
|
}
|
|
17033
|
+
function listConfiguredChannelInstances() {
|
|
17034
|
+
const { store } = getBridgeContext();
|
|
17035
|
+
const raw = store.getSetting("bridge_channel_instances_json");
|
|
17036
|
+
if (!raw) return [];
|
|
17037
|
+
try {
|
|
17038
|
+
const parsed = JSON.parse(raw);
|
|
17039
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
17040
|
+
} catch {
|
|
17041
|
+
return [];
|
|
17042
|
+
}
|
|
17043
|
+
}
|
|
17044
|
+
function getConfiguredChannelInstance(channelType) {
|
|
17045
|
+
return listConfiguredChannelInstances().find((channel) => channel.id === channelType) || null;
|
|
17046
|
+
}
|
|
17047
|
+
function inferChannelProvider(channelType) {
|
|
17048
|
+
const instance = getConfiguredChannelInstance(channelType);
|
|
17049
|
+
return instance?.provider;
|
|
17050
|
+
}
|
|
17051
|
+
function getChannelProviderKey(channelType) {
|
|
17052
|
+
return inferChannelProvider(channelType) || channelType;
|
|
17053
|
+
}
|
|
16820
17054
|
function parseListIndex(raw) {
|
|
16821
17055
|
const trimmed = raw.trim();
|
|
16822
17056
|
if (!/^\d+$/.test(trimmed)) return null;
|
|
@@ -16899,7 +17133,7 @@ function getSessionDisplayName(session, fallbackDirectory) {
|
|
|
16899
17133
|
if (session?.id) return session.id.slice(0, 8);
|
|
16900
17134
|
return "\u672A\u547D\u540D\u4F1A\u8BDD";
|
|
16901
17135
|
}
|
|
16902
|
-
function
|
|
17136
|
+
function nowIso2() {
|
|
16903
17137
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
16904
17138
|
}
|
|
16905
17139
|
function buildInteractiveStreamKey(sessionId, messageId) {
|
|
@@ -17016,12 +17250,12 @@ function buildDesktopThreadsCommandResponse(desktopSessions, markdown, showAll,
|
|
|
17016
17250
|
);
|
|
17017
17251
|
}
|
|
17018
17252
|
function isFeedbackMarkdownEnabled(channelType) {
|
|
17019
|
-
const
|
|
17020
|
-
if (
|
|
17021
|
-
return
|
|
17253
|
+
const instance = getConfiguredChannelInstance(channelType);
|
|
17254
|
+
if (instance?.provider === "feishu") {
|
|
17255
|
+
return instance.config.feedbackMarkdownEnabled !== false;
|
|
17022
17256
|
}
|
|
17023
|
-
if (
|
|
17024
|
-
return
|
|
17257
|
+
if (instance?.provider === "weixin") {
|
|
17258
|
+
return instance.config.feedbackMarkdownEnabled === true;
|
|
17025
17259
|
}
|
|
17026
17260
|
return false;
|
|
17027
17261
|
}
|
|
@@ -17042,7 +17276,8 @@ function toUserVisibleBindingError(error, fallback) {
|
|
|
17042
17276
|
return fallback;
|
|
17043
17277
|
}
|
|
17044
17278
|
function formatBindingChatLabel(binding) {
|
|
17045
|
-
const
|
|
17279
|
+
const instance = getConfiguredChannelInstance(binding.channelType);
|
|
17280
|
+
const channelLabel = binding.channelAlias || instance?.alias || (binding.channelProvider === "feishu" ? "\u98DE\u4E66" : binding.channelProvider === "weixin" ? "\u5FAE\u4FE1" : binding.channelType);
|
|
17046
17281
|
const chatLabel = binding.chatDisplayName?.trim() || binding.chatId;
|
|
17047
17282
|
return `${channelLabel} \u804A\u5929 ${chatLabel}`;
|
|
17048
17283
|
}
|
|
@@ -17381,7 +17616,7 @@ async function deliverTextResponse(adapter, address, responseText, sessionId, re
|
|
|
17381
17616
|
}
|
|
17382
17617
|
return { ok: true };
|
|
17383
17618
|
}
|
|
17384
|
-
if (parseMode === "Markdown" && adapter.
|
|
17619
|
+
if (parseMode === "Markdown" && adapter.provider === "feishu") {
|
|
17385
17620
|
return deliver(adapter, {
|
|
17386
17621
|
address,
|
|
17387
17622
|
text: responseText,
|
|
@@ -17408,7 +17643,7 @@ async function deliverResponse(adapter, address, responseText, sessionId, replyT
|
|
|
17408
17643
|
if (!captionResult.ok) return captionResult;
|
|
17409
17644
|
lastResult = captionResult;
|
|
17410
17645
|
}
|
|
17411
|
-
if (!supportsOutboundArtifacts(adapter.
|
|
17646
|
+
if (!supportsOutboundArtifacts(adapter.provider)) {
|
|
17412
17647
|
lastResult = await deliverTextResponse(
|
|
17413
17648
|
adapter,
|
|
17414
17649
|
address,
|
|
@@ -17486,6 +17721,53 @@ function getQueuedCount(sessionId) {
|
|
|
17486
17721
|
const state = getState();
|
|
17487
17722
|
return state.queuedCounts.get(sessionId) || 0;
|
|
17488
17723
|
}
|
|
17724
|
+
function buildInteractiveIdleReminderNotice() {
|
|
17725
|
+
return [
|
|
17726
|
+
"\u63D0\u9192\uFF1A\u8FD9\u8F6E\u4EFB\u52A1\u4ECD\u5728\u8FD0\u884C\uFF0C\u4F46\u5DF2\u7ECF\u8D85\u8FC7 10 \u5206\u949F\u6CA1\u6709\u65B0\u7684\u6267\u884C\u8F93\u51FA\u3002",
|
|
17727
|
+
"\u7CFB\u7EDF\u4E0D\u4F1A\u81EA\u52A8\u7EC8\u6B62\u5B83\uFF1B\u5982\u679C\u4F60\u4ECD\u5728\u5BF9\u5E94\u7EBF\u7A0B\uFF0C\u53EF\u53D1\u9001 `/stop` \u4E3B\u52A8\u505C\u6B62\uFF1B\u5982\u679C\u5DF2\u7ECF\u5207\u5230\u522B\u7684\u7EBF\u7A0B\uFF0C\u9700\u8981\u5148\u5207\u56DE\u5BF9\u5E94\u7EBF\u7A0B\u3002"
|
|
17728
|
+
].join("\n");
|
|
17729
|
+
}
|
|
17730
|
+
function isCurrentInteractiveTask(sessionId, taskId) {
|
|
17731
|
+
return getState().activeTasks.get(sessionId)?.id === taskId;
|
|
17732
|
+
}
|
|
17733
|
+
function touchInteractiveTask(sessionId, taskId) {
|
|
17734
|
+
const task = getState().activeTasks.get(sessionId);
|
|
17735
|
+
if (task?.id !== taskId) return;
|
|
17736
|
+
task.lastActivityAt = Date.now();
|
|
17737
|
+
task.idleReminderSent = false;
|
|
17738
|
+
}
|
|
17739
|
+
function releaseInteractiveTask(sessionId, taskId) {
|
|
17740
|
+
const state = getState();
|
|
17741
|
+
const current = state.activeTasks.get(sessionId);
|
|
17742
|
+
if (current?.id !== taskId) return;
|
|
17743
|
+
state.activeTasks.delete(sessionId);
|
|
17744
|
+
syncSessionRuntimeState(sessionId);
|
|
17745
|
+
}
|
|
17746
|
+
async function remindIdleInteractiveTask(task) {
|
|
17747
|
+
if (!isCurrentInteractiveTask(task.sessionId, task.id) || task.idleReminderSent) return;
|
|
17748
|
+
task.idleReminderSent = true;
|
|
17749
|
+
try {
|
|
17750
|
+
await deliver(task.adapter, {
|
|
17751
|
+
address: task.address,
|
|
17752
|
+
text: renderFeedbackTextForChannel(
|
|
17753
|
+
task.adapter.channelType,
|
|
17754
|
+
buildInteractiveIdleReminderNotice()
|
|
17755
|
+
),
|
|
17756
|
+
parseMode: getFeedbackParseMode(task.adapter.channelType),
|
|
17757
|
+
replyToMessageId: task.requestMessageId
|
|
17758
|
+
});
|
|
17759
|
+
} catch {
|
|
17760
|
+
}
|
|
17761
|
+
}
|
|
17762
|
+
async function reconcileIdleInteractiveTasks() {
|
|
17763
|
+
const now2 = Date.now();
|
|
17764
|
+
const tasks = Array.from(getState().activeTasks.values());
|
|
17765
|
+
for (const task of tasks) {
|
|
17766
|
+
if (task.idleReminderSent) continue;
|
|
17767
|
+
if (now2 - task.lastActivityAt < INTERACTIVE_IDLE_REMINDER_MS) continue;
|
|
17768
|
+
await remindIdleInteractiveTask(task);
|
|
17769
|
+
}
|
|
17770
|
+
}
|
|
17489
17771
|
function syncSessionRuntimeState(sessionId) {
|
|
17490
17772
|
const { store } = getBridgeContext();
|
|
17491
17773
|
const session = store.getSession(sessionId);
|
|
@@ -17499,7 +17781,7 @@ function syncSessionRuntimeState(sessionId) {
|
|
|
17499
17781
|
store.updateSession(sessionId, {
|
|
17500
17782
|
queued_count: queuedCount,
|
|
17501
17783
|
runtime_status: runtimeStatus,
|
|
17502
|
-
last_runtime_update_at:
|
|
17784
|
+
last_runtime_update_at: nowIso2()
|
|
17503
17785
|
});
|
|
17504
17786
|
}
|
|
17505
17787
|
function incrementQueuedCount(sessionId) {
|
|
@@ -17861,7 +18143,7 @@ function getMirrorStreamingAdapter(subscription) {
|
|
|
17861
18143
|
const state = getState();
|
|
17862
18144
|
const adapter = state.adapters.get(subscription.channelType);
|
|
17863
18145
|
if (!adapter || !adapter.isRunning()) return null;
|
|
17864
|
-
if (subscription.channelType !== "feishu") return null;
|
|
18146
|
+
if (getChannelProviderKey(subscription.channelType) !== "feishu") return null;
|
|
17865
18147
|
if (typeof adapter.onStreamText !== "function" || typeof adapter.onStreamEnd !== "function") {
|
|
17866
18148
|
return null;
|
|
17867
18149
|
}
|
|
@@ -17941,7 +18223,7 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17941
18223
|
renderedStreamText || buildMirrorTitle(title, markdown),
|
|
17942
18224
|
responseParseMode
|
|
17943
18225
|
);
|
|
17944
|
-
if (subscription.channelType === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
18226
|
+
if (getChannelProviderKey(subscription.channelType) === "feishu" && typeof adapter.onStreamEnd === "function") {
|
|
17945
18227
|
try {
|
|
17946
18228
|
const finalized = await adapter.onStreamEnd(
|
|
17947
18229
|
subscription.chatId,
|
|
@@ -17950,7 +18232,7 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17950
18232
|
turn.streamKey
|
|
17951
18233
|
);
|
|
17952
18234
|
if (finalized) {
|
|
17953
|
-
subscription.lastDeliveredAt = turn.timestamp ||
|
|
18235
|
+
subscription.lastDeliveredAt = turn.timestamp || nowIso2();
|
|
17954
18236
|
return;
|
|
17955
18237
|
}
|
|
17956
18238
|
} catch (error) {
|
|
@@ -17972,7 +18254,7 @@ async function deliverMirrorTurn(subscription, turn) {
|
|
|
17972
18254
|
if (!response.ok) {
|
|
17973
18255
|
throw new Error(response.error || "mirror delivery failed");
|
|
17974
18256
|
}
|
|
17975
|
-
subscription.lastDeliveredAt = turn.timestamp ||
|
|
18257
|
+
subscription.lastDeliveredAt = turn.timestamp || nowIso2();
|
|
17976
18258
|
}
|
|
17977
18259
|
async function deliverMirrorTurns(subscription, turns) {
|
|
17978
18260
|
for (const turn of turns.slice(-MIRROR_EVENT_BATCH_LIMIT)) {
|
|
@@ -17980,7 +18262,7 @@ async function deliverMirrorTurns(subscription, turns) {
|
|
|
17980
18262
|
}
|
|
17981
18263
|
}
|
|
17982
18264
|
function createMirrorTurnState(sessionId, timestamp, turnId) {
|
|
17983
|
-
const safeTimestamp = timestamp ||
|
|
18265
|
+
const safeTimestamp = timestamp || nowIso2();
|
|
17984
18266
|
return {
|
|
17985
18267
|
turnId: turnId || null,
|
|
17986
18268
|
streamKey: buildMirrorStreamKey(sessionId, turnId || null, safeTimestamp),
|
|
@@ -18044,7 +18326,7 @@ function finalizeMirrorTurn(subscription, signature, timestamp, status, preferre
|
|
|
18044
18326
|
userText,
|
|
18045
18327
|
text: text2,
|
|
18046
18328
|
signature,
|
|
18047
|
-
timestamp: timestamp || pendingTurn.lastActivityAt ||
|
|
18329
|
+
timestamp: timestamp || pendingTurn.lastActivityAt || nowIso2(),
|
|
18048
18330
|
status,
|
|
18049
18331
|
...signature.startsWith("timeout:") ? { timedOut: true } : {}
|
|
18050
18332
|
};
|
|
@@ -18279,7 +18561,7 @@ async function reconcileMirrorSubscription(subscription) {
|
|
|
18279
18561
|
resetMirrorReadState(subscription);
|
|
18280
18562
|
}
|
|
18281
18563
|
watchMirrorFile(subscription, subscription.filePath);
|
|
18282
|
-
subscription.lastReconciledAt =
|
|
18564
|
+
subscription.lastReconciledAt = nowIso2();
|
|
18283
18565
|
if (!subscription.filePath) {
|
|
18284
18566
|
syncMirrorSessionState(subscription.sessionId);
|
|
18285
18567
|
return;
|
|
@@ -18427,6 +18709,51 @@ function notifyAdapterSetChanged() {
|
|
|
18427
18709
|
const { lifecycle } = getBridgeContext();
|
|
18428
18710
|
lifecycle.onBridgeAdaptersChanged?.(getActiveChannelTypes());
|
|
18429
18711
|
}
|
|
18712
|
+
function stableFingerprintValue(value) {
|
|
18713
|
+
if (Array.isArray(value)) {
|
|
18714
|
+
return value.map(stableFingerprintValue);
|
|
18715
|
+
}
|
|
18716
|
+
if (value && typeof value === "object") {
|
|
18717
|
+
return Object.fromEntries(
|
|
18718
|
+
Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, entryValue]) => [key, stableFingerprintValue(entryValue)])
|
|
18719
|
+
);
|
|
18720
|
+
}
|
|
18721
|
+
return value;
|
|
18722
|
+
}
|
|
18723
|
+
function buildAdapterConfigFingerprint(instance) {
|
|
18724
|
+
const normalizedConfig = stableFingerprintValue(instance.config);
|
|
18725
|
+
return JSON.stringify({
|
|
18726
|
+
provider: instance.provider,
|
|
18727
|
+
alias: instance.alias,
|
|
18728
|
+
enabled: instance.enabled,
|
|
18729
|
+
config: normalizedConfig
|
|
18730
|
+
});
|
|
18731
|
+
}
|
|
18732
|
+
function listEnabledAdapterInstances() {
|
|
18733
|
+
const { store } = getBridgeContext();
|
|
18734
|
+
const configured = listConfiguredChannelInstances().filter((channel) => channel.enabled).map((channel) => ({
|
|
18735
|
+
id: channel.id,
|
|
18736
|
+
provider: channel.provider,
|
|
18737
|
+
alias: channel.alias,
|
|
18738
|
+
enabled: channel.enabled,
|
|
18739
|
+
config: channel.config
|
|
18740
|
+
}));
|
|
18741
|
+
const configuredProviders = new Set(configured.map((channel) => channel.provider));
|
|
18742
|
+
for (const provider of getRegisteredTypes()) {
|
|
18743
|
+
if (provider === "feishu" || provider === "weixin") continue;
|
|
18744
|
+
if (configuredProviders.has(provider)) continue;
|
|
18745
|
+
const enabled = store.getSetting(`bridge_${provider}_enabled`) === "true";
|
|
18746
|
+
if (!enabled) continue;
|
|
18747
|
+
configured.push({
|
|
18748
|
+
id: provider,
|
|
18749
|
+
provider,
|
|
18750
|
+
alias: provider,
|
|
18751
|
+
enabled: true,
|
|
18752
|
+
config: {}
|
|
18753
|
+
});
|
|
18754
|
+
}
|
|
18755
|
+
return configured;
|
|
18756
|
+
}
|
|
18430
18757
|
async function stopAdapterInstance(channelType) {
|
|
18431
18758
|
const state = getState();
|
|
18432
18759
|
const adapter = state.adapters.get(channelType);
|
|
@@ -18444,44 +18771,50 @@ async function stopAdapterInstance(channelType) {
|
|
|
18444
18771
|
}
|
|
18445
18772
|
async function syncConfiguredAdapters(options) {
|
|
18446
18773
|
const state = getState();
|
|
18447
|
-
const { store } = getBridgeContext();
|
|
18448
18774
|
let changed = false;
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
|
|
18454
|
-
|
|
18455
|
-
|
|
18456
|
-
|
|
18457
|
-
|
|
18458
|
-
|
|
18775
|
+
const desiredInstances = listEnabledAdapterInstances();
|
|
18776
|
+
const desiredKeys = new Set(desiredInstances.map((channel) => channel.id));
|
|
18777
|
+
const desiredFingerprints = new Map(
|
|
18778
|
+
desiredInstances.map((instance) => [instance.id, buildAdapterConfigFingerprint(instance)])
|
|
18779
|
+
);
|
|
18780
|
+
for (const existingKey of Array.from(state.adapters.keys())) {
|
|
18781
|
+
if (desiredKeys.has(existingKey)) continue;
|
|
18782
|
+
await stopAdapterInstance(existingKey);
|
|
18783
|
+
changed = true;
|
|
18784
|
+
}
|
|
18785
|
+
for (const instance of desiredInstances) {
|
|
18786
|
+
const existing = state.adapters.get(instance.id);
|
|
18787
|
+
const desiredFingerprint = desiredFingerprints.get(instance.id) || "";
|
|
18788
|
+
const existingMeta = state.adapterMeta.get(instance.id);
|
|
18789
|
+
if (existing && existingMeta?.configFingerprint === desiredFingerprint) continue;
|
|
18459
18790
|
if (existing) {
|
|
18460
|
-
|
|
18791
|
+
await stopAdapterInstance(instance.id);
|
|
18792
|
+
changed = true;
|
|
18461
18793
|
}
|
|
18462
|
-
const adapter = createAdapter(
|
|
18794
|
+
const adapter = createAdapter(instance);
|
|
18463
18795
|
if (!adapter) continue;
|
|
18464
18796
|
const configError = adapter.validateConfig();
|
|
18465
18797
|
if (configError) {
|
|
18466
|
-
console.warn(`[bridge-manager] ${
|
|
18798
|
+
console.warn(`[bridge-manager] ${instance.id} adapter not valid:`, configError);
|
|
18467
18799
|
continue;
|
|
18468
18800
|
}
|
|
18469
18801
|
try {
|
|
18470
|
-
state.adapters.set(
|
|
18471
|
-
state.adapterMeta.set(
|
|
18802
|
+
state.adapters.set(instance.id, adapter);
|
|
18803
|
+
state.adapterMeta.set(instance.id, {
|
|
18472
18804
|
lastMessageAt: null,
|
|
18473
|
-
lastError: null
|
|
18805
|
+
lastError: null,
|
|
18806
|
+
configFingerprint: desiredFingerprint
|
|
18474
18807
|
});
|
|
18475
18808
|
await adapter.start();
|
|
18476
|
-
console.log(`[bridge-manager] Started adapter: ${
|
|
18809
|
+
console.log(`[bridge-manager] Started adapter: ${instance.id}`);
|
|
18477
18810
|
if (options.startLoops && state.running && adapter.isRunning()) {
|
|
18478
18811
|
runAdapterLoop(adapter);
|
|
18479
18812
|
}
|
|
18480
18813
|
changed = true;
|
|
18481
18814
|
} catch (err) {
|
|
18482
|
-
state.adapters.delete(
|
|
18483
|
-
state.adapterMeta.delete(
|
|
18484
|
-
console.error(`[bridge-manager] Failed to start adapter ${
|
|
18815
|
+
state.adapters.delete(instance.id);
|
|
18816
|
+
state.adapterMeta.delete(instance.id);
|
|
18817
|
+
console.error(`[bridge-manager] Failed to start adapter ${instance.id}:`, err);
|
|
18485
18818
|
}
|
|
18486
18819
|
}
|
|
18487
18820
|
if (changed) {
|
|
@@ -18517,6 +18850,9 @@ async function start() {
|
|
|
18517
18850
|
void syncConfiguredAdapters({ startLoops: true }).catch((err) => {
|
|
18518
18851
|
console.error("[bridge-manager] Adapter reconcile failed:", err);
|
|
18519
18852
|
});
|
|
18853
|
+
void reconcileIdleInteractiveTasks().catch((err) => {
|
|
18854
|
+
console.error("[bridge-manager] Interactive idle reminder reconcile failed:", err);
|
|
18855
|
+
});
|
|
18520
18856
|
}, 5e3);
|
|
18521
18857
|
state.mirrorPollTimer = setInterval(() => {
|
|
18522
18858
|
void reconcileMirrorSubscriptions().catch((err) => {
|
|
@@ -18550,8 +18886,8 @@ async function stop() {
|
|
|
18550
18886
|
}
|
|
18551
18887
|
state.loopAborts.clear();
|
|
18552
18888
|
const activeSessionIds = Array.from(state.activeTasks.keys());
|
|
18553
|
-
for (const
|
|
18554
|
-
|
|
18889
|
+
for (const task of state.activeTasks.values()) {
|
|
18890
|
+
task.abortController.abort();
|
|
18555
18891
|
}
|
|
18556
18892
|
state.activeTasks.clear();
|
|
18557
18893
|
state.mirrorSuppressUntil.clear();
|
|
@@ -18577,6 +18913,8 @@ function getStatus() {
|
|
|
18577
18913
|
const meta = state.adapterMeta.get(type);
|
|
18578
18914
|
return {
|
|
18579
18915
|
channelType: adapter.channelType,
|
|
18916
|
+
channelProvider: adapter.provider,
|
|
18917
|
+
channelAlias: adapter.alias,
|
|
18580
18918
|
running: adapter.isRunning(),
|
|
18581
18919
|
connectedAt: state.startedAt,
|
|
18582
18920
|
lastMessageAt: meta?.lastMessageAt ?? null,
|
|
@@ -18594,7 +18932,7 @@ function runAdapterLoop(adapter) {
|
|
|
18594
18932
|
try {
|
|
18595
18933
|
const msg = await adapter.consumeOne();
|
|
18596
18934
|
if (!msg) continue;
|
|
18597
|
-
if (msg.callbackData || msg.text.trim().startsWith("/") || isNumericPermissionShortcut(adapter.
|
|
18935
|
+
if (msg.callbackData || msg.text.trim().startsWith("/") || isNumericPermissionShortcut(adapter.provider, msg.text.trim(), msg.address.chatId)) {
|
|
18598
18936
|
await handleMessage(adapter, msg);
|
|
18599
18937
|
} else {
|
|
18600
18938
|
const binding = resolve(msg.address);
|
|
@@ -18609,7 +18947,7 @@ function runAdapterLoop(adapter) {
|
|
|
18609
18947
|
if (abort.signal.aborted) break;
|
|
18610
18948
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
18611
18949
|
console.error(`[bridge-manager] Error in ${adapter.channelType} loop:`, err);
|
|
18612
|
-
const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
|
|
18950
|
+
const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
|
|
18613
18951
|
meta.lastError = errMsg;
|
|
18614
18952
|
state.adapterMeta.set(adapter.channelType, meta);
|
|
18615
18953
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
@@ -18619,7 +18957,7 @@ function runAdapterLoop(adapter) {
|
|
|
18619
18957
|
if (!abort.signal.aborted) {
|
|
18620
18958
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
18621
18959
|
console.error(`[bridge-manager] ${adapter.channelType} loop crashed:`, err);
|
|
18622
|
-
const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
|
|
18960
|
+
const meta = state.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
|
|
18623
18961
|
meta.lastError = errMsg;
|
|
18624
18962
|
state.adapterMeta.set(adapter.channelType, meta);
|
|
18625
18963
|
}
|
|
@@ -18628,7 +18966,7 @@ function runAdapterLoop(adapter) {
|
|
|
18628
18966
|
async function handleMessage(adapter, msg) {
|
|
18629
18967
|
const { store } = getBridgeContext();
|
|
18630
18968
|
const adapterState = getState();
|
|
18631
|
-
const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null };
|
|
18969
|
+
const meta = adapterState.adapterMeta.get(adapter.channelType) || { lastMessageAt: null, lastError: null, configFingerprint: "" };
|
|
18632
18970
|
meta.lastMessageAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
18633
18971
|
adapterState.adapterMeta.set(adapter.channelType, meta);
|
|
18634
18972
|
const ack = () => {
|
|
@@ -18672,7 +19010,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18672
19010
|
ack();
|
|
18673
19011
|
return;
|
|
18674
19012
|
}
|
|
18675
|
-
if (adapter.
|
|
19013
|
+
if (adapter.provider === "feishu" || adapter.provider === "qq" || adapter.provider === "weixin") {
|
|
18676
19014
|
const normalized = rawText.normalize("NFKC").replace(/[\u200B-\u200D\uFEFF]/g, "").trim();
|
|
18677
19015
|
if (/^[123]$/.test(normalized)) {
|
|
18678
19016
|
const currentBinding = store.getChannelBinding(msg.address.channelType, msg.address.chatId);
|
|
@@ -18759,10 +19097,25 @@ async function handleMessage(adapter, msg) {
|
|
|
18759
19097
|
const streamKey = buildInteractiveStreamKey(binding.codepilotSessionId, msg.messageId);
|
|
18760
19098
|
adapter.onMessageStart?.(msg.address.chatId, streamKey);
|
|
18761
19099
|
const taskAbort = new AbortController();
|
|
19100
|
+
const taskId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
18762
19101
|
const state = getState();
|
|
18763
19102
|
resetMirrorSessionForInteractiveRun(binding.codepilotSessionId);
|
|
18764
|
-
|
|
18765
|
-
|
|
19103
|
+
const taskState = {
|
|
19104
|
+
id: taskId,
|
|
19105
|
+
abortController: taskAbort,
|
|
19106
|
+
adapter,
|
|
19107
|
+
address: msg.address,
|
|
19108
|
+
requestMessageId: msg.messageId,
|
|
19109
|
+
streamKey,
|
|
19110
|
+
sessionId: binding.codepilotSessionId,
|
|
19111
|
+
hasStreamingCards: false,
|
|
19112
|
+
lastActivityAt: Date.now(),
|
|
19113
|
+
idleReminderSent: false,
|
|
19114
|
+
streamFinalized: false,
|
|
19115
|
+
uiEnded: false,
|
|
19116
|
+
mirrorSuppressionId: null
|
|
19117
|
+
};
|
|
19118
|
+
state.activeTasks.set(binding.codepilotSessionId, taskState);
|
|
18766
19119
|
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18767
19120
|
let previewState = null;
|
|
18768
19121
|
const caps = adapter.getPreviewCapabilities?.(msg.address.chatId) ?? null;
|
|
@@ -18777,7 +19130,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18777
19130
|
pendingText: ""
|
|
18778
19131
|
};
|
|
18779
19132
|
}
|
|
18780
|
-
const streamCfg = previewState ? getStreamConfig(adapter.
|
|
19133
|
+
const streamCfg = previewState ? getStreamConfig(adapter.provider) : null;
|
|
18781
19134
|
const previewOnPartialText = previewState && streamCfg ? (fullText) => {
|
|
18782
19135
|
const ps = previewState;
|
|
18783
19136
|
const cfg = streamCfg;
|
|
@@ -18811,8 +19164,10 @@ async function handleMessage(adapter, msg) {
|
|
|
18811
19164
|
flushPreview(adapter, ps, cfg);
|
|
18812
19165
|
} : void 0;
|
|
18813
19166
|
const hasStreamingCards = typeof adapter.onStreamText === "function";
|
|
19167
|
+
taskState.hasStreamingCards = hasStreamingCards;
|
|
18814
19168
|
const toolCallTracker = /* @__PURE__ */ new Map();
|
|
18815
19169
|
const onStreamCardText = hasStreamingCards ? (fullText) => {
|
|
19170
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
18816
19171
|
const rendered = renderFeedbackTextForChannel(
|
|
18817
19172
|
adapter.channelType,
|
|
18818
19173
|
stripOutboundArtifactBlocksForStreaming(fullText)
|
|
@@ -18823,6 +19178,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18823
19178
|
}
|
|
18824
19179
|
} : void 0;
|
|
18825
19180
|
const onToolEvent = hasStreamingCards ? (toolId, toolName, status) => {
|
|
19181
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19182
|
+
touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
18826
19183
|
if (toolName) {
|
|
18827
19184
|
toolCallTracker.set(toolId, { id: toolId, name: toolName, status });
|
|
18828
19185
|
} else {
|
|
@@ -18835,6 +19192,8 @@ async function handleMessage(adapter, msg) {
|
|
|
18835
19192
|
}
|
|
18836
19193
|
} : void 0;
|
|
18837
19194
|
const onPartialText = previewOnPartialText || onStreamCardText ? (fullText) => {
|
|
19195
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) return;
|
|
19196
|
+
touchInteractiveTask(binding.codepilotSessionId, taskId);
|
|
18838
19197
|
if (previewOnPartialText) previewOnPartialText(fullText);
|
|
18839
19198
|
if (onStreamCardText) onStreamCardText(fullText);
|
|
18840
19199
|
} : void 0;
|
|
@@ -18852,10 +19211,13 @@ async function handleMessage(adapter, msg) {
|
|
|
18852
19211
|
msg.messageId
|
|
18853
19212
|
);
|
|
18854
19213
|
}, taskAbort.signal, hasAttachments ? msg.attachments : void 0, onPartialText, onToolEvent, (preparedPrompt) => {
|
|
18855
|
-
if (!mirrorSuppressionId) {
|
|
18856
|
-
mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
19214
|
+
if (!taskState.mirrorSuppressionId) {
|
|
19215
|
+
taskState.mirrorSuppressionId = beginMirrorSuppression(binding.codepilotSessionId, preparedPrompt);
|
|
18857
19216
|
}
|
|
18858
19217
|
});
|
|
19218
|
+
if (!isCurrentInteractiveTask(binding.codepilotSessionId, taskId)) {
|
|
19219
|
+
return;
|
|
19220
|
+
}
|
|
18859
19221
|
let cardFinalized = false;
|
|
18860
19222
|
if (hasStreamingCards && adapter.onStreamEnd) {
|
|
18861
19223
|
try {
|
|
@@ -18866,6 +19228,7 @@ async function handleMessage(adapter, msg) {
|
|
|
18866
19228
|
renderFeedbackTextForChannel(adapter.channelType, result.responseText),
|
|
18867
19229
|
streamKey
|
|
18868
19230
|
);
|
|
19231
|
+
taskState.streamFinalized = cardFinalized;
|
|
18869
19232
|
} catch (err) {
|
|
18870
19233
|
console.warn("[bridge-manager] Card finalize failed:", err instanceof Error ? err.message : err);
|
|
18871
19234
|
}
|
|
@@ -18890,14 +19253,9 @@ async function handleMessage(adapter, msg) {
|
|
|
18890
19253
|
[]
|
|
18891
19254
|
);
|
|
18892
19255
|
}
|
|
18893
|
-
|
|
18894
|
-
|
|
18895
|
-
|
|
18896
|
-
if (update !== null) {
|
|
18897
|
-
store.updateChannelBinding(binding.id, { sdkSessionId: update });
|
|
18898
|
-
}
|
|
18899
|
-
} catch {
|
|
18900
|
-
}
|
|
19256
|
+
try {
|
|
19257
|
+
persistSdkSessionUpdate(binding.codepilotSessionId, result.sdkSessionId, result.hasError);
|
|
19258
|
+
} catch {
|
|
18901
19259
|
}
|
|
18902
19260
|
} finally {
|
|
18903
19261
|
if (previewState) {
|
|
@@ -18907,18 +19265,22 @@ async function handleMessage(adapter, msg) {
|
|
|
18907
19265
|
}
|
|
18908
19266
|
adapter.endPreview?.(msg.address.chatId, previewState.draftId);
|
|
18909
19267
|
}
|
|
18910
|
-
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted) {
|
|
19268
|
+
if (hasStreamingCards && adapter.onStreamEnd && taskAbort.signal.aborted && !taskState.streamFinalized) {
|
|
18911
19269
|
try {
|
|
18912
19270
|
await adapter.onStreamEnd(msg.address.chatId, "interrupted", "", streamKey);
|
|
19271
|
+
taskState.streamFinalized = true;
|
|
18913
19272
|
} catch {
|
|
18914
19273
|
}
|
|
18915
19274
|
}
|
|
18916
|
-
if (mirrorSuppressionId) {
|
|
18917
|
-
settleMirrorSuppression(binding.codepilotSessionId, mirrorSuppressionId);
|
|
19275
|
+
if (taskState.mirrorSuppressionId) {
|
|
19276
|
+
settleMirrorSuppression(binding.codepilotSessionId, taskState.mirrorSuppressionId);
|
|
19277
|
+
taskState.mirrorSuppressionId = null;
|
|
19278
|
+
}
|
|
19279
|
+
releaseInteractiveTask(binding.codepilotSessionId, taskId);
|
|
19280
|
+
if (!taskState.uiEnded) {
|
|
19281
|
+
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
19282
|
+
taskState.uiEnded = true;
|
|
18918
19283
|
}
|
|
18919
|
-
state.activeTasks.delete(binding.codepilotSessionId);
|
|
18920
|
-
syncSessionRuntimeState(binding.codepilotSessionId);
|
|
18921
|
-
adapter.onMessageEnd?.(msg.address.chatId, streamKey);
|
|
18922
19284
|
ack();
|
|
18923
19285
|
}
|
|
18924
19286
|
}
|
|
@@ -18971,13 +19333,6 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18971
19333
|
response = resolved.message;
|
|
18972
19334
|
break;
|
|
18973
19335
|
}
|
|
18974
|
-
if (currentBinding) {
|
|
18975
|
-
const st = getState();
|
|
18976
|
-
const oldTask = st.activeTasks.get(currentBinding.codepilotSessionId);
|
|
18977
|
-
if (oldTask) {
|
|
18978
|
-
oldTask.abort();
|
|
18979
|
-
}
|
|
18980
|
-
}
|
|
18981
19336
|
const workDir = resolved.workDir;
|
|
18982
19337
|
ensureWorkingDirectoryExists(workDir);
|
|
18983
19338
|
const binding = createBinding(msg.address, workDir);
|
|
@@ -18991,6 +19346,7 @@ async function handleCommand(adapter, msg, text2) {
|
|
|
18991
19346
|
],
|
|
18992
19347
|
[
|
|
18993
19348
|
args.trim() ? "\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002" : "\u5DF2\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u65B0\u5EFA\u4E00\u4E2A\u7EBF\u7A0B\u3002\u63A5\u4E0B\u6765\u76F4\u63A5\u53D1\u9001\u6587\u672C\u5373\u53EF\u7EE7\u7EED\u3002",
|
|
19349
|
+
"\u5982\u679C\u5F53\u524D\u804A\u5929\u91CC\u5DF2\u6709\u65E7\u4EFB\u52A1\u5728\u8FD0\u884C\uFF0C\u5B83\u4E0D\u4F1A\u88AB\u7EC8\u6B62\uFF0C\u4ECD\u4F1A\u5728\u540E\u53F0\u7EE7\u7EED\u6267\u884C\u5E76\u53EF\u80FD\u7A0D\u540E\u56DE\u6D88\u606F\u3002",
|
|
18994
19350
|
"\u8FD9\u662F IM \u4FA7\u7EBF\u7A0B\uFF0C\u5F53\u524D\u53EA\u4FDD\u8BC1\u5728 IM \u4E2D\u53EF\u7EE7\u7EED\uFF1B\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 Codex Desktop \u4F1A\u8BDD\u5217\u8868\u4E2D\u3002"
|
|
18995
19351
|
],
|
|
18996
19352
|
responseParseMode === "Markdown"
|
|
@@ -19402,6 +19758,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19402
19758
|
return {
|
|
19403
19759
|
heading: `${getSessionDisplayName(session, session.working_directory)}${session.id === currentBinding?.codepilotSessionId ? " [\u5F53\u524D]" : ""}`,
|
|
19404
19760
|
details: [
|
|
19761
|
+
`\u72B6\u6001\uFF1A${formatRuntimeStatus(session)}`,
|
|
19405
19762
|
`\u76EE\u5F55\uFF1A${formatCommandPath(session.working_directory)}`
|
|
19406
19763
|
]
|
|
19407
19764
|
};
|
|
@@ -19420,7 +19777,7 @@ ${truncateHistoryContent(formatStoredMessageContent(message.content))}`;
|
|
|
19420
19777
|
const st = getState();
|
|
19421
19778
|
const taskAbort = st.activeTasks.get(binding.codepilotSessionId);
|
|
19422
19779
|
if (taskAbort) {
|
|
19423
|
-
taskAbort.abort();
|
|
19780
|
+
taskAbort.abortController.abort();
|
|
19424
19781
|
response = "\u6B63\u5728\u505C\u6B62\u5F53\u524D\u4EFB\u52A1...";
|
|
19425
19782
|
} else {
|
|
19426
19783
|
response = "\u5F53\u524D\u6CA1\u6709\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u3002";
|
|
@@ -19525,6 +19882,13 @@ function computeSdkSessionUpdate(sdkSessionId, hasError) {
|
|
|
19525
19882
|
}
|
|
19526
19883
|
return null;
|
|
19527
19884
|
}
|
|
19885
|
+
function persistSdkSessionUpdate(sessionId, sdkSessionId, hasError) {
|
|
19886
|
+
const update = computeSdkSessionUpdate(sdkSessionId, hasError);
|
|
19887
|
+
if (update === null) {
|
|
19888
|
+
return;
|
|
19889
|
+
}
|
|
19890
|
+
getBridgeContext().store.updateSdkSessionId(sessionId, update);
|
|
19891
|
+
}
|
|
19528
19892
|
|
|
19529
19893
|
// src/store.ts
|
|
19530
19894
|
import fs9 from "node:fs";
|
|
@@ -19557,6 +19921,35 @@ function uuid() {
|
|
|
19557
19921
|
function now() {
|
|
19558
19922
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
19559
19923
|
}
|
|
19924
|
+
function defaultAliasForProvider2(provider) {
|
|
19925
|
+
if (provider === "feishu") return "\u98DE\u4E66";
|
|
19926
|
+
if (provider === "weixin") return "\u5FAE\u4FE1";
|
|
19927
|
+
return void 0;
|
|
19928
|
+
}
|
|
19929
|
+
function normalizeLegacyBinding(binding) {
|
|
19930
|
+
const config2 = loadConfig();
|
|
19931
|
+
const legacyProvider = binding.channelType === "feishu" || binding.channelType === "weixin" ? binding.channelType : void 0;
|
|
19932
|
+
const resolvedInstance = legacyProvider ? (config2.channels || []).find((channel) => channel.provider === legacyProvider) : findChannelInstance(binding.channelType, config2);
|
|
19933
|
+
if (!resolvedInstance && !legacyProvider) {
|
|
19934
|
+
return {
|
|
19935
|
+
...binding,
|
|
19936
|
+
active: binding.active !== false
|
|
19937
|
+
};
|
|
19938
|
+
}
|
|
19939
|
+
const channelType = resolvedInstance?.id || binding.channelType;
|
|
19940
|
+
const channelProvider = resolvedInstance?.provider || legacyProvider || binding.channelProvider;
|
|
19941
|
+
const channelAlias = resolvedInstance?.alias || binding.channelAlias || defaultAliasForProvider2(channelProvider);
|
|
19942
|
+
return {
|
|
19943
|
+
...binding,
|
|
19944
|
+
channelType,
|
|
19945
|
+
channelProvider,
|
|
19946
|
+
channelAlias,
|
|
19947
|
+
active: binding.active !== false
|
|
19948
|
+
};
|
|
19949
|
+
}
|
|
19950
|
+
function didBindingChange(before, after) {
|
|
19951
|
+
return before.channelType !== after.channelType || before.channelProvider !== after.channelProvider || before.channelAlias !== after.channelAlias || before.active !== false !== after.active;
|
|
19952
|
+
}
|
|
19560
19953
|
var JsonFileStore = class {
|
|
19561
19954
|
settings;
|
|
19562
19955
|
dynamicSettings;
|
|
@@ -19614,7 +20007,19 @@ var JsonFileStore = class {
|
|
|
19614
20007
|
path12.join(DATA_DIR2, "bindings.json"),
|
|
19615
20008
|
{}
|
|
19616
20009
|
);
|
|
19617
|
-
|
|
20010
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
20011
|
+
let changed = false;
|
|
20012
|
+
for (const binding of Object.values(bindings)) {
|
|
20013
|
+
const normalizedBinding = normalizeLegacyBinding(binding);
|
|
20014
|
+
if (didBindingChange(binding, normalizedBinding)) {
|
|
20015
|
+
changed = true;
|
|
20016
|
+
}
|
|
20017
|
+
normalized.set(`${normalizedBinding.channelType}:${normalizedBinding.chatId}`, normalizedBinding);
|
|
20018
|
+
}
|
|
20019
|
+
this.bindings = normalized;
|
|
20020
|
+
if (changed) {
|
|
20021
|
+
this.persistBindings();
|
|
20022
|
+
}
|
|
19618
20023
|
}
|
|
19619
20024
|
persistSessions() {
|
|
19620
20025
|
writeJson(
|
|
@@ -19694,6 +20099,8 @@ var JsonFileStore = class {
|
|
|
19694
20099
|
...existing,
|
|
19695
20100
|
codepilotSessionId: data.codepilotSessionId,
|
|
19696
20101
|
sdkSessionId: data.sdkSessionId ?? existing.sdkSessionId,
|
|
20102
|
+
channelProvider: data.channelProvider ?? existing.channelProvider,
|
|
20103
|
+
channelAlias: data.channelAlias ?? existing.channelAlias,
|
|
19697
20104
|
chatUserId: data.chatUserId ?? existing.chatUserId,
|
|
19698
20105
|
chatDisplayName: data.chatDisplayName ?? existing.chatDisplayName,
|
|
19699
20106
|
workingDirectory: data.workingDirectory,
|
|
@@ -19708,6 +20115,8 @@ var JsonFileStore = class {
|
|
|
19708
20115
|
const binding = {
|
|
19709
20116
|
id: uuid(),
|
|
19710
20117
|
channelType: data.channelType,
|
|
20118
|
+
channelProvider: data.channelProvider,
|
|
20119
|
+
channelAlias: data.channelAlias,
|
|
19711
20120
|
chatId: data.chatId,
|
|
19712
20121
|
chatUserId: data.chatUserId,
|
|
19713
20122
|
chatDisplayName: data.chatDisplayName,
|
|
@@ -20645,6 +21054,9 @@ function writeStatus(info) {
|
|
|
20645
21054
|
function getRunningChannels() {
|
|
20646
21055
|
return getStatus().adapters.map((adapter) => adapter.channelType).sort();
|
|
20647
21056
|
}
|
|
21057
|
+
function getAdapterStatuses() {
|
|
21058
|
+
return getStatus().adapters;
|
|
21059
|
+
}
|
|
20648
21060
|
async function main() {
|
|
20649
21061
|
const config2 = loadConfig();
|
|
20650
21062
|
setupLogger();
|
|
@@ -20672,7 +21084,8 @@ async function main() {
|
|
|
20672
21084
|
pid: process.pid,
|
|
20673
21085
|
runId,
|
|
20674
21086
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
20675
|
-
channels
|
|
21087
|
+
channels,
|
|
21088
|
+
adapters: getAdapterStatuses()
|
|
20676
21089
|
});
|
|
20677
21090
|
console.log(`[codex-to-im] Bridge started (PID: ${process.pid}, channels: ${channels.join(", ")})`);
|
|
20678
21091
|
},
|
|
@@ -20681,12 +21094,13 @@ async function main() {
|
|
|
20681
21094
|
running: true,
|
|
20682
21095
|
pid: process.pid,
|
|
20683
21096
|
runId,
|
|
20684
|
-
channels
|
|
21097
|
+
channels,
|
|
21098
|
+
adapters: getAdapterStatuses()
|
|
20685
21099
|
});
|
|
20686
21100
|
console.log(`[codex-to-im] Active channels updated: ${channels.join(", ") || "none"}`);
|
|
20687
21101
|
},
|
|
20688
21102
|
onBridgeStop: () => {
|
|
20689
|
-
writeStatus({ running: false, channels: [] });
|
|
21103
|
+
writeStatus({ running: false, channels: [], adapters: [] });
|
|
20690
21104
|
console.log("[codex-to-im] Bridge stopped");
|
|
20691
21105
|
}
|
|
20692
21106
|
}
|