dsh-lark-bot 0.19.11 → 0.19.13
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 +3 -1
- package/README_EN.md +3 -1
- package/dist/cli.js +801 -242
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +120 -1
- package/dist/plugin.js +853 -232
- package/dist/plugin.js.map +1 -1
- package/dist/skill.js +2 -1
- package/dist/skill.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -296,6 +296,16 @@ var init_profile_package = __esm({
|
|
|
296
296
|
}
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
// src/adapters/model-choice.ts
|
|
300
|
+
function resolveModelChoice(preferenceModel, envModel) {
|
|
301
|
+
return preferenceModel?.trim() || envModel;
|
|
302
|
+
}
|
|
303
|
+
var init_model_choice = __esm({
|
|
304
|
+
"src/adapters/model-choice.ts"() {
|
|
305
|
+
"use strict";
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
299
309
|
// src/adapters/dsh/acp-runtime.ts
|
|
300
310
|
import { spawn as spawn4 } from "cross-spawn";
|
|
301
311
|
import { existsSync as existsSync4, realpathSync as realpathSync3 } from "fs";
|
|
@@ -666,7 +676,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
666
676
|
home: homedir2(),
|
|
667
677
|
env: process.env,
|
|
668
678
|
provider: env.provider,
|
|
669
|
-
model: preferences.model
|
|
679
|
+
model: resolveModelChoice(preferences.model, env.model),
|
|
670
680
|
...env.dshExplicit ? { command: env.dshCommand, args: env.dshArgs } : {}
|
|
671
681
|
};
|
|
672
682
|
const ensure = await ensureAcpProfile(runtimeOptions);
|
|
@@ -679,7 +689,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
679
689
|
return new AcpDshAdapter({
|
|
680
690
|
launch,
|
|
681
691
|
provider: env.provider,
|
|
682
|
-
model: preferences.model
|
|
692
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
683
693
|
});
|
|
684
694
|
}
|
|
685
695
|
var AcpDshAdapter;
|
|
@@ -688,6 +698,7 @@ var init_acp_adapter = __esm({
|
|
|
688
698
|
"use strict";
|
|
689
699
|
init_acp_runtime();
|
|
690
700
|
init_event_channel();
|
|
701
|
+
init_model_choice();
|
|
691
702
|
AcpDshAdapter = class {
|
|
692
703
|
id = "dsh-acp";
|
|
693
704
|
displayName = "DeepSeek Harness (ACP)";
|
|
@@ -814,14 +825,14 @@ var init_acp_adapter = __esm({
|
|
|
814
825
|
return;
|
|
815
826
|
}
|
|
816
827
|
if (response.usage) {
|
|
817
|
-
const
|
|
828
|
+
const usage4 = {
|
|
818
829
|
type: "usage",
|
|
819
830
|
...typeof response.usage.inputTokens === "number" ? { inputTokens: response.usage.inputTokens } : {},
|
|
820
831
|
...typeof response.usage.outputTokens === "number" ? { outputTokens: response.usage.outputTokens } : {},
|
|
821
832
|
...typeof response.usage.cachedReadTokens === "number" ? { cacheReadTokens: response.usage.cachedReadTokens } : {},
|
|
822
833
|
...typeof response.usage.cachedWriteTokens === "number" ? { cacheWriteTokens: response.usage.cachedWriteTokens } : {}
|
|
823
834
|
};
|
|
824
|
-
channel.push(
|
|
835
|
+
channel.push(usage4);
|
|
825
836
|
}
|
|
826
837
|
})().catch((error) => {
|
|
827
838
|
if (!stopRequested.value) {
|
|
@@ -972,12 +983,18 @@ async function listProcesses(platform = process.platform, run = captureOutput) {
|
|
|
972
983
|
return listProcessesPosix(run);
|
|
973
984
|
}
|
|
974
985
|
async function listProcessesPosix(run) {
|
|
975
|
-
const { stdout } = await run("ps", ["-axo", "pid=,args="], 1e4);
|
|
986
|
+
const { stdout } = await run("ps", ["-axo", "pid=,ppid=,args="], 1e4);
|
|
976
987
|
const result = [];
|
|
977
988
|
for (const line of stdout.split("\n")) {
|
|
978
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
979
|
-
if (match)
|
|
980
|
-
|
|
989
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
990
|
+
if (!match) continue;
|
|
991
|
+
const pid = Number(match[1]);
|
|
992
|
+
const ppid = Number(match[2]);
|
|
993
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
994
|
+
if (Number.isInteger(ppid) && ppid > 0) {
|
|
995
|
+
result.push({ pid, ppid, cmdline: match[3] ?? "" });
|
|
996
|
+
} else {
|
|
997
|
+
result.push({ pid, cmdline: match[3] ?? "" });
|
|
981
998
|
}
|
|
982
999
|
}
|
|
983
1000
|
return result;
|
|
@@ -989,7 +1006,7 @@ async function listProcessesWindows(run) {
|
|
|
989
1006
|
"-NoProfile",
|
|
990
1007
|
"-NonInteractive",
|
|
991
1008
|
"-Command",
|
|
992
|
-
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
1009
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
993
1010
|
],
|
|
994
1011
|
1e4
|
|
995
1012
|
);
|
|
@@ -1000,8 +1017,11 @@ async function listProcessesWindows(run) {
|
|
|
1000
1017
|
if (typeof row !== "object" || row === null) return [];
|
|
1001
1018
|
const record = row;
|
|
1002
1019
|
const pid = Number(record.ProcessId);
|
|
1020
|
+
const ppid = Number(record.ParentProcessId);
|
|
1003
1021
|
const cmdline = record.CommandLine;
|
|
1004
|
-
|
|
1022
|
+
if (!Number.isInteger(pid) || pid <= 0 || typeof cmdline !== "string") return [];
|
|
1023
|
+
if (Number.isInteger(ppid) && ppid > 0) return [{ pid, ppid, cmdline }];
|
|
1024
|
+
return [{ pid, cmdline }];
|
|
1005
1025
|
});
|
|
1006
1026
|
} catch {
|
|
1007
1027
|
return [];
|
|
@@ -1703,7 +1723,7 @@ var DshProviderManager = class {
|
|
|
1703
1723
|
try {
|
|
1704
1724
|
catalogProviders = await this.catalog.listProviders();
|
|
1705
1725
|
} catch (error) {
|
|
1706
|
-
log.
|
|
1726
|
+
log.info("model-catalog", "refresh-failed", {
|
|
1707
1727
|
error: error instanceof Error ? error.message : String(error)
|
|
1708
1728
|
});
|
|
1709
1729
|
}
|
|
@@ -2482,11 +2502,11 @@ function translateToolResult(data) {
|
|
|
2482
2502
|
}
|
|
2483
2503
|
function translateAssistantMessage(data) {
|
|
2484
2504
|
if (!isRecord3(data) || !isRecord3(data.usage)) return [];
|
|
2485
|
-
const
|
|
2486
|
-
const inputTokens = typeof
|
|
2487
|
-
const outputTokens = typeof
|
|
2488
|
-
const cacheReadTokens = typeof
|
|
2489
|
-
const cacheWriteTokens = typeof
|
|
2505
|
+
const usage4 = data.usage;
|
|
2506
|
+
const inputTokens = typeof usage4.inputTokens === "number" ? usage4.inputTokens : void 0;
|
|
2507
|
+
const outputTokens = typeof usage4.outputTokens === "number" ? usage4.outputTokens : void 0;
|
|
2508
|
+
const cacheReadTokens = typeof usage4.cacheReadTokens === "number" ? usage4.cacheReadTokens : void 0;
|
|
2509
|
+
const cacheWriteTokens = typeof usage4.cacheWriteTokens === "number" ? usage4.cacheWriteTokens : void 0;
|
|
2490
2510
|
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadTokens === void 0 && cacheWriteTokens === void 0) return [];
|
|
2491
2511
|
return [
|
|
2492
2512
|
{
|
|
@@ -3442,6 +3462,7 @@ var WebDshAdapter = class {
|
|
|
3442
3462
|
};
|
|
3443
3463
|
|
|
3444
3464
|
// src/adapters/index.ts
|
|
3465
|
+
init_model_choice();
|
|
3445
3466
|
async function resolveAdapterRoute(input, source) {
|
|
3446
3467
|
const provider = input.provider?.trim() || void 0;
|
|
3447
3468
|
const model = input.model?.trim() || void 0;
|
|
@@ -3461,7 +3482,7 @@ async function resolveAdapterRoute(input, source) {
|
|
|
3461
3482
|
return void 0;
|
|
3462
3483
|
}
|
|
3463
3484
|
async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model: void 0 }) {
|
|
3464
|
-
const configuredModel = preferences.model
|
|
3485
|
+
const configuredModel = resolveModelChoice(preferences.model, env.model);
|
|
3465
3486
|
const managedRoute = env.adapterMode === "sdk" || env.adapterMode === "acp" ? await resolveAdapterRoute(
|
|
3466
3487
|
{ provider: env.provider, model: configuredModel },
|
|
3467
3488
|
new DshProviderManager({ env: process.env })
|
|
@@ -3493,7 +3514,7 @@ async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model
|
|
|
3493
3514
|
return new WebDshAdapter({
|
|
3494
3515
|
baseUrl: env.webBaseUrl,
|
|
3495
3516
|
provider: env.provider,
|
|
3496
|
-
model: preferences.model
|
|
3517
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
3497
3518
|
});
|
|
3498
3519
|
}
|
|
3499
3520
|
case "sdk":
|
|
@@ -3661,6 +3682,7 @@ function resolveAppPaths(root = defaultHome()) {
|
|
|
3661
3682
|
jobsFile: (profile) => profilePath(profile, "jobs.json"),
|
|
3662
3683
|
permissionPoliciesFile: (profile) => profilePath(profile, "permission-policies.json"),
|
|
3663
3684
|
notificationPreferencesFile: (profile) => profilePath(profile, "notification-preferences.json"),
|
|
3685
|
+
notificationChannelsFile: (profile) => profilePath(profile, "notification-channels.json"),
|
|
3664
3686
|
replyPoliciesFile: (profile) => profilePath(profile, "reply-policies.json"),
|
|
3665
3687
|
executionModesFile: (profile) => profilePath(profile, "execution-modes.json"),
|
|
3666
3688
|
languagePoliciesFile: (profile) => profilePath(profile, "language-policy.json"),
|
|
@@ -4189,17 +4211,36 @@ var ConfigStore = class {
|
|
|
4189
4211
|
};
|
|
4190
4212
|
|
|
4191
4213
|
// src/guardian/install.ts
|
|
4192
|
-
init_own_package();
|
|
4193
|
-
init_dsh_runtime();
|
|
4194
|
-
init_process();
|
|
4195
4214
|
import { mkdir as mkdir8, readFile as readFile8, rm as rm3, writeFile as writeFile5 } from "fs/promises";
|
|
4196
4215
|
import { existsSync as existsSync5 } from "fs";
|
|
4197
4216
|
import { homedir as homedir6 } from "os";
|
|
4198
|
-
import {
|
|
4217
|
+
import { dirname as dirname10, join as join11 } from "path";
|
|
4218
|
+
|
|
4219
|
+
// src/platform/path.ts
|
|
4220
|
+
import { delimiter, dirname as dirname8 } from "path";
|
|
4221
|
+
function sanitizeServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
4222
|
+
const entries = [dirname8(nodeBin), ...inheritedPath?.split(delimiter) ?? []];
|
|
4223
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4224
|
+
return entries.filter((entry) => {
|
|
4225
|
+
if (!entry) return false;
|
|
4226
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
4227
|
+
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
4228
|
+
return false;
|
|
4229
|
+
}
|
|
4230
|
+
if (seen.has(entry)) return false;
|
|
4231
|
+
seen.add(entry);
|
|
4232
|
+
return true;
|
|
4233
|
+
}).join(delimiter);
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
// src/guardian/install.ts
|
|
4237
|
+
init_own_package();
|
|
4238
|
+
init_dsh_runtime();
|
|
4239
|
+
init_process();
|
|
4199
4240
|
|
|
4200
4241
|
// src/guardian/state.ts
|
|
4201
4242
|
import { readFile as readFile7 } from "fs/promises";
|
|
4202
|
-
import { dirname as
|
|
4243
|
+
import { dirname as dirname9 } from "path";
|
|
4203
4244
|
import { mkdir as mkdir7 } from "fs/promises";
|
|
4204
4245
|
var DEFAULT_DSH_PROFILE = "dsh-lark";
|
|
4205
4246
|
var DEFAULT_BRIDGE_PROFILE = "default";
|
|
@@ -4241,7 +4282,7 @@ async function loadGuardianState(file, fallback) {
|
|
|
4241
4282
|
}
|
|
4242
4283
|
async function saveGuardianState(file, state) {
|
|
4243
4284
|
const next = { ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4244
|
-
await mkdir7(
|
|
4285
|
+
await mkdir7(dirname9(file), { recursive: true });
|
|
4245
4286
|
await writeFileAtomic(file, `${JSON.stringify(next, null, 2)}
|
|
4246
4287
|
`, { mode: 384 });
|
|
4247
4288
|
}
|
|
@@ -4295,7 +4336,7 @@ function guardianServiceFilePath(platform, root) {
|
|
|
4295
4336
|
}
|
|
4296
4337
|
function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
4297
4338
|
const renderedEnv = {
|
|
4298
|
-
PATH: env.PATH ??
|
|
4339
|
+
PATH: env.PATH ?? dirname10(nodeBin),
|
|
4299
4340
|
...env
|
|
4300
4341
|
};
|
|
4301
4342
|
const envLines = Object.entries(renderedEnv).map(([key, value]) => `Environment=${key}=${value}`).join("\n");
|
|
@@ -4318,18 +4359,7 @@ function systemdUnit(nodeBin, cliEntry, env = {}) {
|
|
|
4318
4359
|
].join("\n");
|
|
4319
4360
|
}
|
|
4320
4361
|
function stableGuardianServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
4321
|
-
|
|
4322
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4323
|
-
return entries.filter((entry) => {
|
|
4324
|
-
if (!entry) return false;
|
|
4325
|
-
const normalized = entry.replaceAll("\\", "/");
|
|
4326
|
-
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
4327
|
-
return false;
|
|
4328
|
-
}
|
|
4329
|
-
if (seen.has(entry)) return false;
|
|
4330
|
-
seen.add(entry);
|
|
4331
|
-
return true;
|
|
4332
|
-
}).join(delimiter);
|
|
4362
|
+
return sanitizeServicePath(nodeBin, inheritedPath);
|
|
4333
4363
|
}
|
|
4334
4364
|
function launchdPlist(nodeBin, cliEntry, label2 = `io.dsh-lark.${GUARDIAN_LABEL}`, logPath = "/tmp/dsh-lark-guardian.log") {
|
|
4335
4365
|
return [
|
|
@@ -4666,7 +4696,7 @@ async function detectUpgradeState(options = {}) {
|
|
|
4666
4696
|
|
|
4667
4697
|
// src/upgrade/state.ts
|
|
4668
4698
|
import { mkdir as mkdir9, readFile as readFile11 } from "fs/promises";
|
|
4669
|
-
import { dirname as
|
|
4699
|
+
import { dirname as dirname11, join as join13 } from "path";
|
|
4670
4700
|
function upgradeStatePath(root) {
|
|
4671
4701
|
return join13(root, "upgrade-state.json");
|
|
4672
4702
|
}
|
|
@@ -4682,7 +4712,7 @@ async function loadUpgradeState(file) {
|
|
|
4682
4712
|
}
|
|
4683
4713
|
}
|
|
4684
4714
|
async function saveUpgradeState(file, state) {
|
|
4685
|
-
await mkdir9(
|
|
4715
|
+
await mkdir9(dirname11(file), { recursive: true });
|
|
4686
4716
|
await writeFileAtomic(file, JSON.stringify(state, null, 2));
|
|
4687
4717
|
}
|
|
4688
4718
|
|
|
@@ -4765,13 +4795,13 @@ function packageSpecFor(name, version) {
|
|
|
4765
4795
|
|
|
4766
4796
|
// src/service/manager.ts
|
|
4767
4797
|
import { mkdir as mkdir14, readFile as readFile14, rename as rename2, rm as rm7, stat as stat5 } from "fs/promises";
|
|
4768
|
-
import { dirname as
|
|
4798
|
+
import { dirname as dirname14, join as join19 } from "path";
|
|
4769
4799
|
import { homedir as homedir11 } from "os";
|
|
4770
4800
|
init_dsh_runtime();
|
|
4771
4801
|
|
|
4772
4802
|
// src/service/command.ts
|
|
4773
4803
|
import { execFile } from "child_process";
|
|
4774
|
-
import { basename as basename3, dirname as
|
|
4804
|
+
import { basename as basename3, dirname as dirname12, join as join14 } from "path";
|
|
4775
4805
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4776
4806
|
var runCommand = (command, args, options) => new Promise((resolve6) => {
|
|
4777
4807
|
execFile(
|
|
@@ -4797,7 +4827,7 @@ function resolveCliJsPath(metaUrl = import.meta.url) {
|
|
|
4797
4827
|
const current = fileURLToPath2(metaUrl);
|
|
4798
4828
|
const name = basename3(current);
|
|
4799
4829
|
if (name === "cli.js" || name === "cli.mjs" || name === "cli.cjs") return current;
|
|
4800
|
-
return join14(
|
|
4830
|
+
return join14(dirname12(current), "..", "dist", "cli.js");
|
|
4801
4831
|
}
|
|
4802
4832
|
function slugify(value) {
|
|
4803
4833
|
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -4840,6 +4870,9 @@ function snapshotServiceEnv(source = process.env, extraKeys = [], inherited = {}
|
|
|
4840
4870
|
}
|
|
4841
4871
|
}
|
|
4842
4872
|
}
|
|
4873
|
+
if (typeof env.PATH === "string" && env.PATH.length > 0) {
|
|
4874
|
+
env.PATH = sanitizeServicePath(process.execPath, env.PATH);
|
|
4875
|
+
}
|
|
4843
4876
|
return env;
|
|
4844
4877
|
}
|
|
4845
4878
|
function formatEnvFile(env) {
|
|
@@ -5425,7 +5458,7 @@ var PortableServiceController = class {
|
|
|
5425
5458
|
|
|
5426
5459
|
// src/service/windows-task.ts
|
|
5427
5460
|
import { mkdir as mkdir13, writeFile as writeFile7 } from "fs/promises";
|
|
5428
|
-
import { dirname as
|
|
5461
|
+
import { dirname as dirname13, join as join18 } from "path";
|
|
5429
5462
|
function psQuote(value) {
|
|
5430
5463
|
return `'${value.replace(/'/g, "''")}'`;
|
|
5431
5464
|
}
|
|
@@ -5436,7 +5469,7 @@ function buildScheduledTaskScript(action, spec) {
|
|
|
5436
5469
|
`$arguments = ${psQuote(spec.commandArgs.map((arg) => `"${arg.replace(/"/g, '\\"')}"`).join(" "))}`,
|
|
5437
5470
|
`$log = ${psQuote(spec.logFile)}`,
|
|
5438
5471
|
`$profile = ${psQuote(spec.profile)}`,
|
|
5439
|
-
`$workDir = ${psQuote(
|
|
5472
|
+
`$workDir = ${psQuote(dirname13(spec.commandPath))}`
|
|
5440
5473
|
].join("\n");
|
|
5441
5474
|
const install = [
|
|
5442
5475
|
`$inner = '"' + $command + '" ' + $arguments + ' >> "' + $log + '" 2>&1'`,
|
|
@@ -5655,7 +5688,7 @@ var ServiceManager = class {
|
|
|
5655
5688
|
}
|
|
5656
5689
|
async withLifecycleLock(operation) {
|
|
5657
5690
|
const lockDir = this.paths.serviceLockDir(this.profile);
|
|
5658
|
-
await mkdir14(
|
|
5691
|
+
await mkdir14(dirname14(lockDir), { recursive: true });
|
|
5659
5692
|
try {
|
|
5660
5693
|
await mkdir14(lockDir);
|
|
5661
5694
|
} catch (error) {
|
|
@@ -5695,11 +5728,39 @@ var ServiceManager = class {
|
|
|
5695
5728
|
if (serviceStatus.state === "running") return;
|
|
5696
5729
|
const process2 = await this.findProcess(this.profile);
|
|
5697
5730
|
if (process2) {
|
|
5731
|
+
if (await this.isGuardianSpawned(process2)) {
|
|
5732
|
+
await this.stopGuardianSpawnedProcess(process2.pid);
|
|
5733
|
+
return;
|
|
5734
|
+
}
|
|
5698
5735
|
throw new Error(
|
|
5699
5736
|
`\u68C0\u6D4B\u5230\u672A\u53D7\u7BA1\u7684 dsh --profile ${this.profile}\uFF08pid ${process2.pid}\uFF09\u3002\u8BF7\u5148\u5728\u539F\u7EC8\u7AEF\u505C\u6B62\u5B83\uFF0C\u518D\u91CD\u8BD5\u3002`
|
|
5700
5737
|
);
|
|
5701
5738
|
}
|
|
5702
5739
|
}
|
|
5740
|
+
/** True when the profile process was spawned by the resident guardian. */
|
|
5741
|
+
async isGuardianSpawned(process2) {
|
|
5742
|
+
const ppid = process2.ppid;
|
|
5743
|
+
if (!ppid) return false;
|
|
5744
|
+
const parent = (await listProcesses()).find((entry) => entry.pid === ppid);
|
|
5745
|
+
return parent !== void 0 && matchGuardianProcess(parent.cmdline);
|
|
5746
|
+
}
|
|
5747
|
+
/** Best-effort stop of a guardian-spawned profile process (grace, then kill). */
|
|
5748
|
+
async stopGuardianSpawnedProcess(pid) {
|
|
5749
|
+
try {
|
|
5750
|
+
process.kill(pid, "SIGTERM");
|
|
5751
|
+
} catch {
|
|
5752
|
+
return;
|
|
5753
|
+
}
|
|
5754
|
+
const deadline = Date.now() + 5e3;
|
|
5755
|
+
while (Date.now() < deadline && isPidAlive2(pid)) {
|
|
5756
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
5757
|
+
}
|
|
5758
|
+
if (!isPidAlive2(pid)) return;
|
|
5759
|
+
try {
|
|
5760
|
+
process.kill(pid, "SIGKILL");
|
|
5761
|
+
} catch {
|
|
5762
|
+
}
|
|
5763
|
+
}
|
|
5703
5764
|
async buildSpec(requireProfile = false) {
|
|
5704
5765
|
const previous = await this.readMetadata();
|
|
5705
5766
|
const dshBin = this.dshBinOverride ?? discoverDshBin(this.home, this.sourceEnv) ?? previous?.dshBin;
|
|
@@ -5796,8 +5857,8 @@ var ServiceManager = class {
|
|
|
5796
5857
|
}
|
|
5797
5858
|
}
|
|
5798
5859
|
async prepareFiles(spec, platform) {
|
|
5799
|
-
await mkdir14(
|
|
5800
|
-
await mkdir14(
|
|
5860
|
+
await mkdir14(dirname14(spec.logFile), { recursive: true });
|
|
5861
|
+
await mkdir14(dirname14(spec.envFile), { recursive: true });
|
|
5801
5862
|
await writeServiceEnv(spec.envFile, spec.env);
|
|
5802
5863
|
if (platform === "win32-task") {
|
|
5803
5864
|
try {
|
|
@@ -6850,7 +6911,7 @@ var PermissionPolicyStore = class {
|
|
|
6850
6911
|
|
|
6851
6912
|
// src/bot/notification-preference-store.ts
|
|
6852
6913
|
import { readFile as readFile19 } from "fs/promises";
|
|
6853
|
-
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval"]);
|
|
6914
|
+
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval", "urgent"]);
|
|
6854
6915
|
var NotificationPreferenceStore = class {
|
|
6855
6916
|
constructor(path) {
|
|
6856
6917
|
this.path = path;
|
|
@@ -6879,7 +6940,7 @@ var NotificationPreferenceStore = class {
|
|
|
6879
6940
|
get(scope) {
|
|
6880
6941
|
const value = this.data.scopes[scope];
|
|
6881
6942
|
if (value === void 0 || value === false) return void 0;
|
|
6882
|
-
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds] };
|
|
6943
|
+
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds], sinks: [...value.sinks] };
|
|
6883
6944
|
}
|
|
6884
6945
|
resolve(scope, fallback) {
|
|
6885
6946
|
const value = this.data.scopes[scope];
|
|
@@ -6924,6 +6985,7 @@ function normalizePreference(value) {
|
|
|
6924
6985
|
...typeof input.target === "string" && input.target.trim() ? { target: input.target.trim() } : {},
|
|
6925
6986
|
events,
|
|
6926
6987
|
mentionUserIds: [...new Set((input.mentionUserIds ?? []).filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()))],
|
|
6988
|
+
sinks: [...new Set((input.sinks ?? []).filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()))],
|
|
6927
6989
|
approvalReminderMs
|
|
6928
6990
|
};
|
|
6929
6991
|
}
|
|
@@ -7400,6 +7462,7 @@ function statusCardMarkdownFor(input, locale) {
|
|
|
7400
7462
|
zh ? `\u{1F39A}\uFE0F **\u6267\u884C\u6A21\u5F0F**\uFF1A\`${input.executionMode ?? "balanced"}\`` : `\u{1F39A}\uFE0F **Execution mode**: \`${input.executionMode ?? "balanced"}\``,
|
|
7401
7463
|
zh ? `\u{1F6E1}\uFE0F **\u5DE5\u5177\u6743\u9650**\uFF1A\`${input.permissionPolicy ?? "ask"}\`` : `\u{1F6E1}\uFE0F **Tool permission**: \`${input.permissionPolicy ?? "ask"}\``,
|
|
7402
7464
|
...input.notificationPreference ? [zh ? `\u{1F514} **\u4E3B\u52A8\u63D0\u9192**\uFF1A\`${input.notificationPreference.events.join(",")}\` \u2192 \`${input.notificationPreference.target ?? "current"}\`\uFF08\u5BA1\u6279 ${String(input.notificationPreference.approvalReminderMs / 6e4)} \u5206\u949F\uFF09` : `\u{1F514} **Notifications**: \`${input.notificationPreference.events.join(",")}\` \u2192 \`${input.notificationPreference.target ?? "current"}\` (approval ${String(input.notificationPreference.approvalReminderMs / 6e4)} min)`] : [zh ? "\u{1F515} **\u4E3B\u52A8\u63D0\u9192**\uFF1A\u5173\u95ED" : "\u{1F515} **Notifications**: off"],
|
|
7465
|
+
...input.notificationChannels && input.notificationChannels.length > 0 ? [zh ? `\u{1F4E4} **\u51FA\u7AD9\u6E20\u9053**\uFF1A\`${input.notificationChannels.join(",")}\`` : `\u{1F4E4} **Outbound channels**: \`${input.notificationChannels.join(",")}\``] : [],
|
|
7403
7466
|
...input.replyPolicy ? [zh ? `\u{1F4E8} **\u56DE\u590D\u7B56\u7565**\uFF1A${input.replyPolicyConfigured ? "\u81EA\u5B9A\u4E49" : "\u9ED8\u8BA4"} \xB7 \u5408\u5E76 ${String(input.replyPolicy.mergeWindowMs / 1e3)} \u79D2 \xB7 \u6BCF\u6279 ${String(input.replyPolicy.maxBatchSize)} \xB7 \u95F4\u9694 ${String(input.replyPolicy.minIntervalMs / 1e3)} \u79D2 \xB7 \u53BB\u91CD ${String(input.replyPolicy.dedupeWindowMs / 1e3)} \u79D2` : `\u{1F4E8} **Reply policy**: ${input.replyPolicyConfigured ? "custom" : "default"} \xB7 merge ${String(input.replyPolicy.mergeWindowMs / 1e3)}s \xB7 batch ${String(input.replyPolicy.maxBatchSize)} \xB7 interval ${String(input.replyPolicy.minIntervalMs / 1e3)}s \xB7 dedupe ${String(input.replyPolicy.dedupeWindowMs / 1e3)}s`] : [],
|
|
7404
7467
|
...input.role ? [`\u{1F3AD} **role**\uFF1A${input.role}`] : [],
|
|
7405
7468
|
`\u{1F516} **version**\uFF1A\`${input.version}\``,
|
|
@@ -7816,8 +7879,8 @@ function renderStandard(state, now, locale, maxTools) {
|
|
|
7816
7879
|
} else if (state.terminal === "done" && !hasAnswer(state)) {
|
|
7817
7880
|
elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
7818
7881
|
} else if (state.terminal === "done") {
|
|
7819
|
-
const
|
|
7820
|
-
if (
|
|
7882
|
+
const usage4 = usageLine(state);
|
|
7883
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
7821
7884
|
}
|
|
7822
7885
|
if (state.finalDeliveryError) {
|
|
7823
7886
|
elements.push(finalDeliveryFailureLine(locale));
|
|
@@ -7883,8 +7946,8 @@ function renderDetailed(state, now, locale, maxTools) {
|
|
|
7883
7946
|
} else if (state.terminal === "error") {
|
|
7884
7947
|
elements.push(runFailureLine(locale));
|
|
7885
7948
|
} else if (state.terminal === "done") {
|
|
7886
|
-
const
|
|
7887
|
-
if (
|
|
7949
|
+
const usage4 = usageLine(state);
|
|
7950
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
7888
7951
|
if (!hasAnswer(state)) elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
7889
7952
|
}
|
|
7890
7953
|
if (state.finalDeliveryError) {
|
|
@@ -10461,7 +10524,7 @@ async function handleConfigHubAction(action, ctx, value = {}) {
|
|
|
10461
10524
|
}
|
|
10462
10525
|
|
|
10463
10526
|
// src/commands/archive.ts
|
|
10464
|
-
import { dirname as
|
|
10527
|
+
import { dirname as dirname15 } from "path";
|
|
10465
10528
|
|
|
10466
10529
|
// src/media/outbound-files.ts
|
|
10467
10530
|
import { constants } from "fs";
|
|
@@ -10657,7 +10720,7 @@ async function sendArchiveFiles(ctx, record, destination = {
|
|
|
10657
10720
|
[record.jsonlPath, `${record.archiveId}.jsonl`]
|
|
10658
10721
|
]) {
|
|
10659
10722
|
try {
|
|
10660
|
-
const prepared = await prepareOutboundFile({ path, baseDir:
|
|
10723
|
+
const prepared = await prepareOutboundFile({ path, baseDir: dirname15(path), allowedRoots: [dirname15(path)], fileName });
|
|
10661
10724
|
await ctx.channel.sendFile(destination.chatId, prepared.fileName, prepared.content, {
|
|
10662
10725
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
10663
10726
|
...destination.threadId ? { threadId: destination.threadId } : {}
|
|
@@ -10907,7 +10970,7 @@ async function handleNotify(args, ctx) {
|
|
|
10907
10970
|
}
|
|
10908
10971
|
|
|
10909
10972
|
// src/commands/notifications.ts
|
|
10910
|
-
var ALL_EVENTS = ["completed", "failed", "approval"];
|
|
10973
|
+
var ALL_EVENTS = ["completed", "failed", "approval", "urgent"];
|
|
10911
10974
|
async function reply5(ctx, zh, en) {
|
|
10912
10975
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
10913
10976
|
}
|
|
@@ -10945,6 +11008,7 @@ async function handleNotifications(args, ctx) {
|
|
|
10945
11008
|
let events = [...ALL_EVENTS];
|
|
10946
11009
|
let mentionUserIds = ctx.senderId ? [ctx.senderId] : [];
|
|
10947
11010
|
let reminderMinutes = 10;
|
|
11011
|
+
let sinks = [];
|
|
10948
11012
|
for (const token of tokens.slice(1)) {
|
|
10949
11013
|
if (token.startsWith("events=")) {
|
|
10950
11014
|
const requested = token.slice(7).split(",").filter(Boolean);
|
|
@@ -10956,6 +11020,8 @@ async function handleNotifications(args, ctx) {
|
|
|
10956
11020
|
} else if (token.startsWith("remind=")) {
|
|
10957
11021
|
reminderMinutes = Number(token.slice(7));
|
|
10958
11022
|
if (!Number.isInteger(reminderMinutes) || reminderMinutes < 1 || reminderMinutes > 1440) return usage(ctx);
|
|
11023
|
+
} else if (token.startsWith("sinks=")) {
|
|
11024
|
+
sinks = token.slice(6).split(",").map((id) => id.trim()).filter(Boolean);
|
|
10959
11025
|
} else if (!target) {
|
|
10960
11026
|
target = token === "current" ? void 0 : token;
|
|
10961
11027
|
} else return usage(ctx);
|
|
@@ -10974,50 +11040,297 @@ async function handleNotifications(args, ctx) {
|
|
|
10974
11040
|
...target ? { target } : {},
|
|
10975
11041
|
events,
|
|
10976
11042
|
mentionUserIds: [...new Set(mentionUserIds)],
|
|
10977
|
-
approvalReminderMs: reminderMinutes * 6e4
|
|
11043
|
+
approvalReminderMs: reminderMinutes * 6e4,
|
|
11044
|
+
sinks: [...new Set(sinks)]
|
|
10978
11045
|
};
|
|
10979
11046
|
await store.set(ctx.scope, value);
|
|
10980
11047
|
await reply5(ctx, `\u5DF2\u5F00\u542F\u3002${describe(value, true)}`, `Enabled. ${describe(value, false)}`);
|
|
10981
11048
|
}
|
|
10982
11049
|
function describe(value, zh) {
|
|
10983
11050
|
const minutes = value.approvalReminderMs / 6e4;
|
|
10984
|
-
|
|
11051
|
+
const sinks = value.sinks.join(",") || "none";
|
|
11052
|
+
return zh ? `\u4E3B\u52A8\u63D0\u9192\uFF1A**\u5F00\u542F** \xB7 \u4E8B\u4EF6 \`${value.events.join(",")}\` \xB7 \u76EE\u6807 \`${value.target ?? "current"}\` \xB7 @ \`${value.mentionUserIds.join(",") || "none"}\` \xB7 \u6E20\u9053 \`${sinks}\` \xB7 \u5BA1\u6279\u63D0\u9192 ${String(minutes)} \u5206\u949F\u3002` : `Proactive notifications: **on** \xB7 events \`${value.events.join(",")}\` \xB7 target \`${value.target ?? "current"}\` \xB7 mentions \`${value.mentionUserIds.join(",") || "none"}\` \xB7 channels \`${sinks}\` \xB7 approval reminder ${String(minutes)} min.`;
|
|
10985
11053
|
}
|
|
10986
11054
|
async function usage(ctx) {
|
|
10987
11055
|
await reply5(
|
|
10988
11056
|
ctx,
|
|
10989
|
-
"\u7528\u6CD5\uFF1A`/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`",
|
|
10990
|
-
"Usage: `/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`"
|
|
11057
|
+
"\u7528\u6CD5\uFF1A`/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval,urgent] [mentions=self,ou_x|none] [sinks=channelId1,channelId2] [remind=10]]`",
|
|
11058
|
+
"Usage: `/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval,urgent] [mentions=self,ou_x|none] [sinks=channelId1,channelId2] [remind=10]]`"
|
|
11059
|
+
);
|
|
11060
|
+
}
|
|
11061
|
+
|
|
11062
|
+
// src/notify/sinks/channel-store.ts
|
|
11063
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
11064
|
+
var SINK_TYPES = ["telegram", "wecom"];
|
|
11065
|
+
var NotificationChannelStore = class {
|
|
11066
|
+
constructor(path) {
|
|
11067
|
+
this.path = path;
|
|
11068
|
+
}
|
|
11069
|
+
path;
|
|
11070
|
+
data = { schemaVersion: 1, channels: {} };
|
|
11071
|
+
saving = Promise.resolve();
|
|
11072
|
+
async load() {
|
|
11073
|
+
try {
|
|
11074
|
+
const parsed = JSON.parse(await readFile23(this.path, "utf8"));
|
|
11075
|
+
const channels = {};
|
|
11076
|
+
for (const [id, value] of Object.entries(parsed.channels ?? {})) {
|
|
11077
|
+
const normalized = normalizeChannel(id, value);
|
|
11078
|
+
if (normalized) channels[id] = normalized;
|
|
11079
|
+
}
|
|
11080
|
+
this.data = { schemaVersion: 1, channels };
|
|
11081
|
+
} catch (error) {
|
|
11082
|
+
if (error.code !== "ENOENT") throw error;
|
|
11083
|
+
this.data = { schemaVersion: 1, channels: {} };
|
|
11084
|
+
}
|
|
11085
|
+
}
|
|
11086
|
+
list() {
|
|
11087
|
+
return Object.values(this.data.channels).map((channel) => ({ ...channel }));
|
|
11088
|
+
}
|
|
11089
|
+
get(id) {
|
|
11090
|
+
const channel = this.data.channels[id];
|
|
11091
|
+
return channel ? { ...channel } : void 0;
|
|
11092
|
+
}
|
|
11093
|
+
async add(channel) {
|
|
11094
|
+
const normalized = normalizeChannel(channel.id, channel);
|
|
11095
|
+
if (!normalized) throw new Error("invalid notification channel");
|
|
11096
|
+
await this.mutate(() => {
|
|
11097
|
+
this.data.channels[normalized.id] = normalized;
|
|
11098
|
+
});
|
|
11099
|
+
}
|
|
11100
|
+
async update(id, patch) {
|
|
11101
|
+
const existing = this.data.channels[id];
|
|
11102
|
+
if (!existing) return false;
|
|
11103
|
+
const next = normalizeChannel(id, { ...existing, ...patch });
|
|
11104
|
+
if (!next) return false;
|
|
11105
|
+
await this.mutate(() => {
|
|
11106
|
+
this.data.channels[id] = next;
|
|
11107
|
+
});
|
|
11108
|
+
return true;
|
|
11109
|
+
}
|
|
11110
|
+
async setEnabled(id, enabled) {
|
|
11111
|
+
return this.update(id, { enabled });
|
|
11112
|
+
}
|
|
11113
|
+
async remove(id) {
|
|
11114
|
+
const existing = this.data.channels[id];
|
|
11115
|
+
if (!existing) return false;
|
|
11116
|
+
await this.mutate(() => {
|
|
11117
|
+
delete this.data.channels[id];
|
|
11118
|
+
});
|
|
11119
|
+
return true;
|
|
11120
|
+
}
|
|
11121
|
+
async flush() {
|
|
11122
|
+
await this.saving;
|
|
11123
|
+
}
|
|
11124
|
+
async mutate(fn) {
|
|
11125
|
+
const snapshot = JSON.stringify(this.data, null, 2) + "\n";
|
|
11126
|
+
const previous = JSON.stringify(this.data);
|
|
11127
|
+
fn();
|
|
11128
|
+
this.saving = this.saving.then(async () => {
|
|
11129
|
+
await writeFileAtomic(this.path, snapshot, { mode: 384 });
|
|
11130
|
+
}).catch((error) => {
|
|
11131
|
+
try {
|
|
11132
|
+
this.data = JSON.parse(previous);
|
|
11133
|
+
} catch {
|
|
11134
|
+
}
|
|
11135
|
+
log.fail("notification-channels", error, { step: "persist" });
|
|
11136
|
+
throw error;
|
|
11137
|
+
});
|
|
11138
|
+
await this.saving;
|
|
11139
|
+
}
|
|
11140
|
+
};
|
|
11141
|
+
function normalizeChannel(id, value) {
|
|
11142
|
+
if (!value || typeof value !== "object" || !id.trim()) return void 0;
|
|
11143
|
+
const input = value;
|
|
11144
|
+
const type = input.type;
|
|
11145
|
+
if (type !== "telegram" && type !== "wecom") return void 0;
|
|
11146
|
+
const label2 = typeof input.label === "string" ? input.label.trim() : "";
|
|
11147
|
+
const destination = typeof input.destination === "string" ? input.destination.trim() : "";
|
|
11148
|
+
const secret = typeof input.secret === "string" ? input.secret.trim() : "";
|
|
11149
|
+
if (!label2 || !destination || !secret) return void 0;
|
|
11150
|
+
const mentionMap = {};
|
|
11151
|
+
if (input.mentionMap && typeof input.mentionMap === "object") {
|
|
11152
|
+
for (const [from, to] of Object.entries(input.mentionMap)) {
|
|
11153
|
+
if (typeof to === "string" && to.trim()) mentionMap[from] = to.trim();
|
|
11154
|
+
}
|
|
11155
|
+
}
|
|
11156
|
+
return {
|
|
11157
|
+
id: id.trim(),
|
|
11158
|
+
type,
|
|
11159
|
+
label: label2,
|
|
11160
|
+
destination,
|
|
11161
|
+
secret,
|
|
11162
|
+
enabled: input.enabled !== false,
|
|
11163
|
+
...Object.keys(mentionMap).length > 0 ? { mentionMap } : {}
|
|
11164
|
+
};
|
|
11165
|
+
}
|
|
11166
|
+
|
|
11167
|
+
// src/notify/sinks/types.ts
|
|
11168
|
+
var SINK_HTTP_TIMEOUT_MS = 1e4;
|
|
11169
|
+
function maskChannel(channel, includeId = true) {
|
|
11170
|
+
const id = includeId ? `${channel.id} \xB7 ` : "";
|
|
11171
|
+
const dest = maskSecret(channel.destination);
|
|
11172
|
+
return `${id}${channel.type} \xB7 ${channel.label} \u2192 ${dest}`;
|
|
11173
|
+
}
|
|
11174
|
+
function maskSecret(value) {
|
|
11175
|
+
if (!value) return "(unset)";
|
|
11176
|
+
if (value.length <= 6) return `${value[0]}{redacted}`;
|
|
11177
|
+
return `${value.slice(0, 3)}\u2026${value.slice(-3)}`;
|
|
11178
|
+
}
|
|
11179
|
+
|
|
11180
|
+
// src/commands/channels.ts
|
|
11181
|
+
async function reply6(ctx, zh, en) {
|
|
11182
|
+
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
11183
|
+
}
|
|
11184
|
+
function requireAdmin3(ctx) {
|
|
11185
|
+
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
11186
|
+
void reply6(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u7BA1\u7406\u901A\u77E5\u6E20\u9053\u3002", "Only admins can manage notification channels.");
|
|
11187
|
+
return false;
|
|
11188
|
+
}
|
|
11189
|
+
async function handleChannels(args, ctx) {
|
|
11190
|
+
if (!requireAdmin3(ctx)) return;
|
|
11191
|
+
const store = ctx.notificationChannels;
|
|
11192
|
+
if (!store) {
|
|
11193
|
+
await reply6(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u901A\u77E5\u6E20\u9053\u7BA1\u7406\u3002", "Notification channel management is unavailable in this runtime.");
|
|
11194
|
+
return;
|
|
11195
|
+
}
|
|
11196
|
+
const [sub, ...rest] = args.trim().split(/\s+/);
|
|
11197
|
+
switch (sub) {
|
|
11198
|
+
case "list":
|
|
11199
|
+
return listChannels(ctx, store);
|
|
11200
|
+
case "show": {
|
|
11201
|
+
const id = rest[0];
|
|
11202
|
+
if (!id) return usage2(ctx);
|
|
11203
|
+
const channel = store.get(id);
|
|
11204
|
+
if (!channel) return notFound(ctx, id);
|
|
11205
|
+
return showChannel(ctx, channel);
|
|
11206
|
+
}
|
|
11207
|
+
case "add":
|
|
11208
|
+
return addChannel(ctx, store, rest.join(" "));
|
|
11209
|
+
case "remove": {
|
|
11210
|
+
const id = rest[0];
|
|
11211
|
+
if (!id) return usage2(ctx);
|
|
11212
|
+
const ok = await store.remove(id);
|
|
11213
|
+
return ok ? reply6(ctx, `\u5DF2\u79FB\u9664\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002`, `Removed notification channel \`${id}\`.`) : notFound(ctx, id);
|
|
11214
|
+
}
|
|
11215
|
+
case "enable":
|
|
11216
|
+
case "disable": {
|
|
11217
|
+
const id = rest[0];
|
|
11218
|
+
if (!id) return usage2(ctx);
|
|
11219
|
+
const enabled = sub === "enable";
|
|
11220
|
+
const ok = await store.setEnabled(id, enabled);
|
|
11221
|
+
if (!ok) return notFound(ctx, id);
|
|
11222
|
+
return reply6(ctx, `\u5DF2${enabled ? "\u542F\u7528" : "\u505C\u7528"}\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002`, `${enabled ? "Enabled" : "Disabled"} notification channel \`${id}\`.`);
|
|
11223
|
+
}
|
|
11224
|
+
default:
|
|
11225
|
+
return usage2(ctx);
|
|
11226
|
+
}
|
|
11227
|
+
}
|
|
11228
|
+
async function listChannels(ctx, store) {
|
|
11229
|
+
const channels = store.list();
|
|
11230
|
+
if (channels.length === 0) {
|
|
11231
|
+
await reply6(ctx, "\u5C1A\u672A\u914D\u7F6E\u4EFB\u4F55\u901A\u77E5\u6E20\u9053\u3002\u7528 `/channels add telegram|wecom <label> --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>` \u6DFB\u52A0\u3002", "No notification channels yet. Use `/channels add telegram|wecom <label> --destination <target> --secret <token>` to add one.");
|
|
11232
|
+
return;
|
|
11233
|
+
}
|
|
11234
|
+
const lines = channels.map((channel) => `- ${channel.enabled ? "\u{1F7E2}" : "\u26AA\uFE0F"} \`${channel.id}\` \u2014 ${channel.type} \xB7 ${channel.label} \xB7 ${maskSecret(channel.destination)}`);
|
|
11235
|
+
await reply6(ctx, ["**\u5DF2\u914D\u7F6E\u901A\u77E5\u6E20\u9053**", "", ...lines].join("\n"), ["**Configured notification channels**", "", ...lines].join("\n"));
|
|
11236
|
+
}
|
|
11237
|
+
async function showChannel(ctx, channel) {
|
|
11238
|
+
const zh = `**${channel.id}**
|
|
11239
|
+
- \u7C7B\u578B\uFF1A\`${channel.type}\`
|
|
11240
|
+
- \u540D\u79F0\uFF1A${channel.label}
|
|
11241
|
+
- \u76EE\u6807\uFF1A\`${maskSecret(channel.destination)}\`
|
|
11242
|
+
- \u72B6\u6001\uFF1A${channel.enabled ? "\u542F\u7528" : "\u505C\u7528"}
|
|
11243
|
+
- \u5BC6\u94A5\uFF1A\`${maskSecret(channel.secret)}\``;
|
|
11244
|
+
const en = `**${channel.id}**
|
|
11245
|
+
- Type: \`${channel.type}\`
|
|
11246
|
+
- Label: ${channel.label}
|
|
11247
|
+
- Target: \`${maskSecret(channel.destination)}\`
|
|
11248
|
+
- Status: ${channel.enabled ? "enabled" : "disabled"}
|
|
11249
|
+
- Secret: \`${maskSecret(channel.secret)}\``;
|
|
11250
|
+
await reply6(ctx, zh, en);
|
|
11251
|
+
}
|
|
11252
|
+
async function addChannel(ctx, store, raw) {
|
|
11253
|
+
const parsed = parseAddArgs(raw);
|
|
11254
|
+
if (!parsed || !parsed.type || !parsed.label || !parsed.destination || !parsed.secret) {
|
|
11255
|
+
await usage2(ctx);
|
|
11256
|
+
return;
|
|
11257
|
+
}
|
|
11258
|
+
if (!SINK_TYPES.includes(parsed.type)) {
|
|
11259
|
+
await reply6(ctx, `\u4E0D\u652F\u6301\u7684\u6E20\u9053\u7C7B\u578B\uFF1A\`${parsed.type}\`\u3002\u53EF\u7528\uFF1A\`${SINK_TYPES.join("|")}\`\u3002`, `Unsupported channel type: \`${parsed.type}\`. Available: \`${SINK_TYPES.join("|")}\`.`);
|
|
11260
|
+
return;
|
|
11261
|
+
}
|
|
11262
|
+
const id = parsed.id ?? `${parsed.type}-${slugify2(parsed.label)}`;
|
|
11263
|
+
if (store.get(id)) {
|
|
11264
|
+
await reply6(ctx, `\u901A\u77E5\u6E20\u9053 \`${id}\` \u5DF2\u5B58\u5728\u3002\u7528 \`/channels remove ${id}\` \u5220\u9664\u540E\u91CD\u5EFA\u3002`, `Notification channel \`${id}\` already exists. Remove it first with \`/channels remove ${id}\`.`);
|
|
11265
|
+
return;
|
|
11266
|
+
}
|
|
11267
|
+
const channel = {
|
|
11268
|
+
id,
|
|
11269
|
+
type: parsed.type,
|
|
11270
|
+
label: parsed.label,
|
|
11271
|
+
destination: parsed.destination,
|
|
11272
|
+
secret: parsed.secret,
|
|
11273
|
+
enabled: true
|
|
11274
|
+
};
|
|
11275
|
+
await store.add(channel);
|
|
11276
|
+
await reply6(ctx, `\u5DF2\u6DFB\u52A0\u901A\u77E5\u6E20\u9053 ${maskChannel(channel)}\u3002`, `Added notification channel ${maskChannel(channel)}.`);
|
|
11277
|
+
}
|
|
11278
|
+
async function notFound(ctx, id) {
|
|
11279
|
+
await reply6(ctx, `\u672A\u627E\u5230\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002\u7528 \`/channels list\` \u67E5\u770B\u3002`, `Unknown notification channel \`${id}\`. Use \`/channels list\`.`);
|
|
11280
|
+
}
|
|
11281
|
+
async function usage2(ctx) {
|
|
11282
|
+
await reply6(
|
|
11283
|
+
ctx,
|
|
11284
|
+
"\u7528\u6CD5\uFF1A`/channels list|show <id>|add <telegram|wecom> <label> [--id <id>] --destination <\u76EE\u6807> --secret <\u5BC6\u94A5>|remove <id>|enable <id>|disable <id>`",
|
|
11285
|
+
"Usage: `/channels list|show <id>|add <telegram|wecom> <label> [--id <id>] --destination <target> --secret <token>|remove <id>|enable <id>|disable <id>`"
|
|
10991
11286
|
);
|
|
10992
11287
|
}
|
|
11288
|
+
function parseAddArgs(raw) {
|
|
11289
|
+
if (!raw.trim()) return void 0;
|
|
11290
|
+
const idMatch = raw.match(/--id\s+(\S+)/);
|
|
11291
|
+
const destMatch = raw.match(/--destination\s+(\S+)/);
|
|
11292
|
+
const secretMatch = raw.match(/--secret\s+(\S+)/);
|
|
11293
|
+
const positionals = raw.replace(/\s*--id\s+\S+/, " ").replace(/\s*--destination\s+\S+/, " ").replace(/\s*--secret\s+\S+/, " ").trim().split(/\s+/).filter(Boolean);
|
|
11294
|
+
const [type, label2] = positionals;
|
|
11295
|
+
return {
|
|
11296
|
+
...idMatch ? { id: idMatch[1] } : {},
|
|
11297
|
+
...type ? { type } : {},
|
|
11298
|
+
...label2 ? { label: label2 } : {},
|
|
11299
|
+
...destMatch ? { destination: destMatch[1] } : {},
|
|
11300
|
+
...secretMatch ? { secret: secretMatch[1] } : {}
|
|
11301
|
+
};
|
|
11302
|
+
}
|
|
11303
|
+
function slugify2(value) {
|
|
11304
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "channel";
|
|
11305
|
+
}
|
|
10993
11306
|
|
|
10994
11307
|
// src/commands/replies.ts
|
|
10995
11308
|
async function handleReplies(args, ctx) {
|
|
10996
11309
|
const store = ctx.replyPolicies;
|
|
10997
|
-
if (!store) return
|
|
11310
|
+
if (!store) return reply7(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u56DE\u590D\u7B56\u7565\u3002", "Reply policies are unavailable in this runtime.");
|
|
10998
11311
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
10999
|
-
if (tokens.length === 0 || tokens[0] === "show") return
|
|
11000
|
-
if (!await canManageReplies(ctx)) return
|
|
11312
|
+
if (tokens.length === 0 || tokens[0] === "show") return reply7(ctx, describe2(store.get(ctx.scope), store.isConfigured(ctx.scope), true), describe2(store.get(ctx.scope), store.isConfigured(ctx.scope), false));
|
|
11313
|
+
if (!await canManageReplies(ctx)) return reply7(ctx, "\u4EC5 profile \u7BA1\u7406\u5458\u6216\u5F53\u524D\u7FA4\u7684\u7FA4\u4E3B/\u7FA4\u7BA1\u7406\u5458\u53EF\u4FEE\u6539\u56DE\u590D\u7B56\u7565\u3002", "Only profile admins or the current group owner/managers can change reply policies.");
|
|
11001
11314
|
if (tokens[0] === "default") {
|
|
11002
11315
|
await store.set(ctx.scope, void 0);
|
|
11003
|
-
return
|
|
11316
|
+
return reply7(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5373\u65F6\u56DE\u590D\u4E0E\u4EC5 messageId \u53BB\u91CD\u3002", "Restored immediate replies and message-ID-only deduplication.");
|
|
11004
11317
|
}
|
|
11005
|
-
if (tokens[0] !== "set") return
|
|
11318
|
+
if (tokens[0] !== "set") return usage3(ctx);
|
|
11006
11319
|
const policy = { ...store.get(ctx.scope) };
|
|
11007
11320
|
for (const token of tokens.slice(1)) {
|
|
11008
11321
|
const [key, raw, extra] = token.split("=");
|
|
11009
|
-
if (!raw || extra !== void 0) return
|
|
11322
|
+
if (!raw || extra !== void 0) return usage3(ctx);
|
|
11010
11323
|
const seconds = Number(raw);
|
|
11011
|
-
if (!Number.isInteger(seconds)) return
|
|
11324
|
+
if (!Number.isInteger(seconds)) return usage3(ctx);
|
|
11012
11325
|
if (key === "merge" && seconds >= 0 && seconds <= 300) policy.mergeWindowMs = seconds * 1e3;
|
|
11013
11326
|
else if (key === "batch" && seconds >= 1 && seconds <= 20) policy.maxBatchSize = seconds;
|
|
11014
11327
|
else if (key === "interval" && seconds >= 0 && seconds <= 3600) policy.minIntervalMs = seconds * 1e3;
|
|
11015
11328
|
else if (key === "dedupe" && seconds >= 0 && seconds <= 3600) policy.dedupeWindowMs = seconds * 1e3;
|
|
11016
|
-
else return
|
|
11329
|
+
else return usage3(ctx);
|
|
11017
11330
|
}
|
|
11018
|
-
if (tokens.length === 1) return
|
|
11331
|
+
if (tokens.length === 1) return usage3(ctx);
|
|
11019
11332
|
await store.set(ctx.scope, policy);
|
|
11020
|
-
await
|
|
11333
|
+
await reply7(ctx, `\u5DF2\u8BBE\u7F6E\u3002${describe2(policy, true, true)}`, `Updated. ${describe2(policy, true, false)}`);
|
|
11021
11334
|
}
|
|
11022
11335
|
async function canManageReplies(ctx) {
|
|
11023
11336
|
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
@@ -11031,10 +11344,10 @@ async function canManageReplies(ctx) {
|
|
|
11031
11344
|
function describe2(policy, configured, zh) {
|
|
11032
11345
|
return zh ? `\u56DE\u590D\u7B56\u7565\uFF1A**${configured ? "\u81EA\u5B9A\u4E49" : "\u9ED8\u8BA4"}** \xB7 \u5408\u5E76 ${String(policy.mergeWindowMs / 1e3)} \u79D2 \xB7 \u6BCF\u6279 ${String(policy.maxBatchSize)} \u4E2A\u4EFB\u52A1 \xB7 \u95F4\u9694 ${String(policy.minIntervalMs / 1e3)} \u79D2 \xB7 \u8FD1\u4F3C\u53BB\u91CD ${String(policy.dedupeWindowMs / 1e3)} \u79D2\u3002` : `Reply policy: **${configured ? "custom" : "default"}** \xB7 merge ${String(policy.mergeWindowMs / 1e3)}s \xB7 ${String(policy.maxBatchSize)} tasks/batch \xB7 interval ${String(policy.minIntervalMs / 1e3)}s \xB7 near-dedupe ${String(policy.dedupeWindowMs / 1e3)}s.`;
|
|
11033
11346
|
}
|
|
11034
|
-
async function
|
|
11035
|
-
await
|
|
11347
|
+
async function usage3(ctx) {
|
|
11348
|
+
await reply7(ctx, "\u7528\u6CD5\uFF1A`/replies [show|default|set merge=\u79D2 batch=\u6570\u91CF interval=\u79D2 dedupe=\u79D2]`", "Usage: `/replies [show|default|set merge=seconds batch=count interval=seconds dedupe=seconds]`");
|
|
11036
11349
|
}
|
|
11037
|
-
async function
|
|
11350
|
+
async function reply7(ctx, zh, en) {
|
|
11038
11351
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
11039
11352
|
}
|
|
11040
11353
|
|
|
@@ -11221,7 +11534,8 @@ var COMMAND_CATALOG = [
|
|
|
11221
11534
|
{ names: ["/isolation"], area: "policy", zh: "`[group|topic|member]` \u2014 \u7FA4\u4F1A\u8BDD\u9694\u79BB\uFF08\u8BBE\u7F6E\u9700\u7BA1\u7406\u5458\uFF09", en: "`[group|topic|member]` \u2014 group isolation (admin to set)" },
|
|
11222
11535
|
{ names: ["/role"], area: "configuration", zh: "`list|show|set|clear|save|remove \u2026` \u2014 \u7BA1\u7406\u89D2\u8272", en: "`list|show|set|clear|save|remove \u2026` \u2014 manage roles" },
|
|
11223
11536
|
{ names: ["/notify"], area: "communication", zh: "`<scope|chatId> <text>` / `list` \u2014 \u8DE8\u4F1A\u8BDD\u901A\u77E5\uFF08\u53D1\u9001\u9700\u7BA1\u7406\u5458\uFF09", en: "`<scope|chatId> <text>` / `list` \u2014 cross-session notification (admin to send)" },
|
|
11224
|
-
{ names: ["/notifications"], area: "communication", zh: "`[show|off|on \u2026]` \u2014 scope \u63D0\u9192\u7B56\u7565", en: "`[show|off|on \u2026]` \u2014 scope notification policy" },
|
|
11537
|
+
{ names: ["/notifications"], area: "communication", zh: "`[show|off|on \u2026]` \u2014 scope \u63D0\u9192\u7B56\u7565\uFF08\u652F\u6301 `sinks=`\uFF09", en: "`[show|off|on \u2026]` \u2014 scope notification policy (supports `sinks=`)" },
|
|
11538
|
+
{ names: ["/channels"], area: "configuration", zh: "`list|show|add|remove|enable|disable \u2026` \u2014 \u7BA1\u7406\u51FA\u7AD9\u901A\u77E5\u6E20\u9053\uFF08\u7BA1\u7406\u5458\uFF09", en: "`list|show|add|remove|enable|disable \u2026` \u2014 manage outbound notification channels (admin)" },
|
|
11225
11539
|
{ names: ["/replies"], area: "communication", zh: "`[show|default|set \u2026]` \u2014 \u56DE\u590D\u5408\u5E76\u3001\u9891\u7387\u4E0E\u53BB\u91CD", en: "`[show|default|set \u2026]` \u2014 reply batching, rate, and deduplication" },
|
|
11226
11540
|
{ names: ["/retention"], area: "session", zh: "`[N|default]` \u2014 \u4F1A\u8BDD\u4FDD\u7559\u6761\u6570", en: "`[N|default]` \u2014 retained live-message count" },
|
|
11227
11541
|
{ names: ["/archive"], area: "session", zh: "`[note]|send|list|clean \u2026` \u2014 \u7BA1\u7406\u5F52\u6863", en: "`[note]|send|list|clean \u2026` \u2014 manage archives" },
|
|
@@ -11299,6 +11613,7 @@ var HELP = [
|
|
|
11299
11613
|
"- `/notify <scope|chatId> <text>` \u2014 \u8DE8\u4F1A\u8BDD\u53D1\u9001\u901A\u77E5\uFF08\u7BA1\u7406\u5458\uFF09",
|
|
11300
11614
|
"- `/notify list` \u2014 \u67E5\u770B\u5DF2\u6CE8\u518C scope",
|
|
11301
11615
|
"- `/notifications [show|off|on \u2026]` \u2014 \u914D\u7F6E\u5F53\u524D scope \u7684\u5B8C\u6210 / \u5931\u8D25 / \u5BA1\u6279\u63D0\u9192",
|
|
11616
|
+
"- `/channels [list|show|add|remove|enable|disable \u2026]` \u2014 \u7BA1\u7406\u51FA\u7AD9\u901A\u77E5\u6E20\u9053\uFF08\u7BA1\u7406\u5458\uFF09\uFF0C\u8F6C\u53D1\u5230\u5FAE\u4FE1 / Telegram \u7B49",
|
|
11302
11617
|
"- `/replies [show|default|set \u2026]` \u2014 \u914D\u7F6E\u56DE\u590D\u5408\u5E76\u3001\u9891\u7387\u4E0E\u8FD1\u4F3C\u53BB\u91CD\uFF08profile \u7BA1\u7406\u5458\u6216\u5F53\u524D\u7FA4\u7BA1\u7406\u5458\u53EF\u4FEE\u6539\uFF09",
|
|
11303
11618
|
"- `/retention [N|default]` \u2014 \u67E5\u770B\u6216\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u4FDD\u7559\u6D88\u606F\u6761\u6570\uFF08\u8D85\u51FA\u81EA\u52A8\u5F52\u6863\uFF09",
|
|
11304
11619
|
"- `/archive [note]`\u3001`/archive send <id> [scope|chatId]`\u3001`/archive list [N]`\u3001`/archive clean` \u2014 \u5F52\u6863\u5E76\u4E0A\u4F20 / \u91CD\u53D1\u6216\u8F6C\u53D1 / \u67E5\u770B / \u6E05\u7406",
|
|
@@ -11360,7 +11675,7 @@ var HELP_EN = [
|
|
|
11360
11675
|
"- `/invite user|admin|group <id>` \u2014 manage access allowlists",
|
|
11361
11676
|
"- `/help` \u2014 show this help"
|
|
11362
11677
|
].join("\n");
|
|
11363
|
-
async function
|
|
11678
|
+
async function reply8(ctx, zhCn, enUs) {
|
|
11364
11679
|
await ctx.channel.sendMarkdown(ctx.chatId, enUs === void 0 ? zhCn : bilingualMarkdown(zhCn, enUs, ctx.languagePolicies?.get().plain), {
|
|
11365
11680
|
replyTo: ctx.messageId
|
|
11366
11681
|
});
|
|
@@ -11369,7 +11684,7 @@ async function handleNew(_args, ctx) {
|
|
|
11369
11684
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11370
11685
|
const interrupted = await ctx.activeRuns.interruptWorkspace(ctx.scope, cwd);
|
|
11371
11686
|
ctx.sessions.clear(ctx.scope, cwd);
|
|
11372
|
-
await
|
|
11687
|
+
await reply8(
|
|
11373
11688
|
ctx,
|
|
11374
11689
|
interrupted > 0 ? `\u5DF2\u4E2D\u65AD ${String(interrupted)} \u4E2A\u4EFB\u52A1\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002` : "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002",
|
|
11375
11690
|
interrupted > 0 ? `Interrupted ${String(interrupted)} task(s) and started a new session.` : "Started a new session."
|
|
@@ -11398,11 +11713,11 @@ function groupAppLink(chatId) {
|
|
|
11398
11713
|
async function handleNewGroup(args, ctx) {
|
|
11399
11714
|
const name = args.trim();
|
|
11400
11715
|
if (!name) {
|
|
11401
|
-
await
|
|
11716
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/newg <\u7FA4\u540D>` \u2014 \u81EA\u52A8\u65B0\u5EFA\u7FA4\u804A\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD", "Usage: `/newg <name>` \u2014 create a group and start a new session");
|
|
11402
11717
|
return;
|
|
11403
11718
|
}
|
|
11404
11719
|
if (name.length > MAX_GROUP_NAME_LENGTH) {
|
|
11405
|
-
await
|
|
11720
|
+
await reply8(
|
|
11406
11721
|
ctx,
|
|
11407
11722
|
`\u7FA4\u540D\u8FC7\u957F\uFF08\u4E0A\u9650 ${String(MAX_GROUP_NAME_LENGTH)} \u5B57\u7B26\uFF0C\u5F53\u524D ${String(name.length)}\uFF09\u3002`,
|
|
11408
11723
|
`The group name is too long (limit ${String(MAX_GROUP_NAME_LENGTH)} characters; received ${String(name.length)}).`
|
|
@@ -11410,11 +11725,11 @@ async function handleNewGroup(args, ctx) {
|
|
|
11410
11725
|
return;
|
|
11411
11726
|
}
|
|
11412
11727
|
if (!ctx.channel.createChat) {
|
|
11413
|
-
await
|
|
11728
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u81EA\u52A8\u5EFA\u7FA4\u3002", "This channel cannot create groups automatically.");
|
|
11414
11729
|
return;
|
|
11415
11730
|
}
|
|
11416
11731
|
if (!ctx.senderId) {
|
|
11417
|
-
await
|
|
11732
|
+
await reply8(ctx, "\u65E0\u6CD5\u8BC6\u522B\u53D1\u9001\u8005 open_id\uFF0C\u4E0D\u80FD\u81EA\u52A8\u5EFA\u7FA4\u3002", "The sender open_id is unavailable, so the group cannot be created automatically.");
|
|
11418
11733
|
return;
|
|
11419
11734
|
}
|
|
11420
11735
|
try {
|
|
@@ -11425,7 +11740,7 @@ async function handleNewGroup(args, ctx) {
|
|
|
11425
11740
|
inviteUserIds: [ctx.senderId],
|
|
11426
11741
|
userIdType: "open_id"
|
|
11427
11742
|
});
|
|
11428
|
-
await
|
|
11743
|
+
await reply8(
|
|
11429
11744
|
ctx,
|
|
11430
11745
|
[
|
|
11431
11746
|
`\u2705 \u5DF2\u521B\u5EFA\u7FA4\u804A\uFF1A**${name}**`,
|
|
@@ -11444,13 +11759,13 @@ async function handleNewGroup(args, ctx) {
|
|
|
11444
11759
|
);
|
|
11445
11760
|
} catch (error) {
|
|
11446
11761
|
const message = error instanceof Error ? error.message : String(error);
|
|
11447
|
-
await
|
|
11762
|
+
await reply8(ctx, `\u274C \u5EFA\u7FA4\u5931\u8D25\uFF1A\`${message}\``, `\u274C Failed to create the group: \`${message}\``);
|
|
11448
11763
|
}
|
|
11449
11764
|
}
|
|
11450
11765
|
async function handleCd(args, ctx) {
|
|
11451
11766
|
const path = args.trim();
|
|
11452
11767
|
if (!path) {
|
|
11453
|
-
await
|
|
11768
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`", "Usage: `/cd <path>`");
|
|
11454
11769
|
return;
|
|
11455
11770
|
}
|
|
11456
11771
|
const normalized = path.replace(/^~/u, "~");
|
|
@@ -11458,13 +11773,13 @@ async function handleCd(args, ctx) {
|
|
|
11458
11773
|
const cwd = resolve5(expanded);
|
|
11459
11774
|
const target = await stat8(cwd).catch(() => void 0);
|
|
11460
11775
|
if (!target?.isDirectory()) {
|
|
11461
|
-
await
|
|
11776
|
+
await reply8(ctx, `\u76EE\u5F55\u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55\uFF1A\`${cwd}\`\u3002`, `The path does not exist or is not a directory: \`${cwd}\`.`);
|
|
11462
11777
|
return;
|
|
11463
11778
|
}
|
|
11464
11779
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11465
11780
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
11466
11781
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
11467
|
-
await
|
|
11782
|
+
await reply8(
|
|
11468
11783
|
ctx,
|
|
11469
11784
|
`\u5DF2\u5207\u6362\u5DE5\u4F5C\u76EE\u5F55\uFF1A\`${cwd}\`\uFF1B\u8BE5\u5DE5\u4F5C\u533A\u7684\u4F1A\u8BDD\u4F1A\u72EC\u7ACB\u6062\u590D\u3002${interrupted > 0 ? `\u5DF2\u4E2D\u65AD\u539F\u5DE5\u4F5C\u533A ${String(interrupted)} \u4E2A\u8FD0\u884C\u4E2D\u4EFB\u52A1\uFF08\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\uFF09\u3002` : ""}`,
|
|
11470
11785
|
`Switched workspace to \`${cwd}\`; its independent session will resume.${interrupted > 0 ? ` Interrupted ${String(interrupted)} running task(s) in the previous workspace (session data was preserved).` : ""}`
|
|
@@ -11484,7 +11799,7 @@ async function handleWs(args, ctx) {
|
|
|
11484
11799
|
const lines = Object.entries(named).map(
|
|
11485
11800
|
([key, value]) => `- **${key}** \u2192 \`${value}\`${value === current ? " \u2190 \u5F53\u524D" : ""}`
|
|
11486
11801
|
);
|
|
11487
|
-
await
|
|
11802
|
+
await reply8(
|
|
11488
11803
|
ctx,
|
|
11489
11804
|
[
|
|
11490
11805
|
`\u5F53\u524D cwd\uFF1A\`${current}\``,
|
|
@@ -11501,29 +11816,29 @@ async function handleWs(args, ctx) {
|
|
|
11501
11816
|
}
|
|
11502
11817
|
if (sub === "save") {
|
|
11503
11818
|
if (!name) {
|
|
11504
|
-
await
|
|
11819
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`", "Usage: `/ws save <name>`");
|
|
11505
11820
|
return;
|
|
11506
11821
|
}
|
|
11507
11822
|
const current = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11508
11823
|
ctx.workspaces.saveNamed(name, current);
|
|
11509
|
-
await
|
|
11824
|
+
await reply8(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${current}\``, `Saved workspace: **${name}** \u2192 \`${current}\``);
|
|
11510
11825
|
return;
|
|
11511
11826
|
}
|
|
11512
11827
|
if (sub === "use") {
|
|
11513
11828
|
if (!name) {
|
|
11514
|
-
await
|
|
11829
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`", "Usage: `/ws use <name>`");
|
|
11515
11830
|
return;
|
|
11516
11831
|
}
|
|
11517
11832
|
const cwd = ctx.workspaces.getNamed(name);
|
|
11518
11833
|
if (!cwd) {
|
|
11519
|
-
await
|
|
11834
|
+
await reply8(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`, `Workspace not found: **${name}**`);
|
|
11520
11835
|
return;
|
|
11521
11836
|
}
|
|
11522
11837
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11523
11838
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
11524
11839
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
11525
11840
|
ctx.workspaces.touchNamed(name);
|
|
11526
|
-
await
|
|
11841
|
+
await reply8(
|
|
11527
11842
|
ctx,
|
|
11528
11843
|
`\u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${cwd}\`\uFF1B\u8BE5\u5DE5\u4F5C\u533A\u7684\u4F1A\u8BDD\u4F1A\u72EC\u7ACB\u6062\u590D\u3002${interrupted > 0 ? `\u5DF2\u4E2D\u65AD\u539F\u5DE5\u4F5C\u533A ${String(interrupted)} \u4E2A\u8FD0\u884C\u4E2D\u4EFB\u52A1\uFF08\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\uFF09\u3002` : ""}`,
|
|
11529
11844
|
`Switched to workspace **${name}** \u2192 \`${cwd}\`; its independent session will resume.${interrupted > 0 ? ` Interrupted ${String(interrupted)} running task(s) in the previous workspace (session data was preserved).` : ""}`
|
|
@@ -11532,14 +11847,14 @@ async function handleWs(args, ctx) {
|
|
|
11532
11847
|
}
|
|
11533
11848
|
if (sub === "remove") {
|
|
11534
11849
|
if (!name) {
|
|
11535
|
-
await
|
|
11850
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`", "Usage: `/ws remove <name>`");
|
|
11536
11851
|
return;
|
|
11537
11852
|
}
|
|
11538
11853
|
const removed = ctx.workspaces.removeNamed(name);
|
|
11539
|
-
await
|
|
11854
|
+
await reply8(ctx, removed ? `\u5DF2\u5220\u9664\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**` : `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`, removed ? `Removed workspace: **${name}**` : `Workspace not found: **${name}**`);
|
|
11540
11855
|
return;
|
|
11541
11856
|
}
|
|
11542
|
-
await
|
|
11857
|
+
await reply8(ctx, "\u672A\u77E5 `/ws` \u5B50\u547D\u4EE4\uFF0C\u8BF7\u4F7F\u7528 list / save / use / remove\u3002", "Unknown `/ws` subcommand. Use list / save / use / remove.");
|
|
11543
11858
|
}
|
|
11544
11859
|
async function statusCardInputFor(ctx) {
|
|
11545
11860
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -11586,6 +11901,7 @@ async function statusCardInputFor(ctx) {
|
|
|
11586
11901
|
permissionPolicy: ctx.permissionPolicies?.get(ctx.scope) ?? "ask",
|
|
11587
11902
|
executionMode: ctx.executionModes?.get(ctx.scope) ?? "balanced",
|
|
11588
11903
|
...notificationPreference ? { notificationPreference } : {},
|
|
11904
|
+
...ctx.notificationSinks ? { notificationChannels: ctx.notificationSinks.enabledChannels().map((channel) => channel.id) } : {},
|
|
11589
11905
|
...replyPolicy ? { replyPolicy, replyPolicyConfigured: ctx.replyPolicies?.isConfigured(ctx.scope) ?? false } : {},
|
|
11590
11906
|
role: role ? `\`${role.id}\` (${role.name})` : void 0,
|
|
11591
11907
|
metrics,
|
|
@@ -11628,16 +11944,16 @@ async function handleStatus(_args, ctx) {
|
|
|
11628
11944
|
} catch {
|
|
11629
11945
|
}
|
|
11630
11946
|
}
|
|
11631
|
-
await
|
|
11947
|
+
await reply8(ctx, bilingualMarkdown(statusCardMarkdown(input), statusCardMarkdownEnglish(input)));
|
|
11632
11948
|
}
|
|
11633
11949
|
async function handleIsolation(args, ctx) {
|
|
11634
11950
|
if (ctx.chatMode === "p2p") {
|
|
11635
|
-
await
|
|
11951
|
+
await reply8(ctx, "\u5F53\u524D\u662F\u79C1\u804A\uFF0Cscope \u5DF2\u6309\u4F1A\u8BDD\u5929\u7136\u9694\u79BB\uFF0C\u65E0\u9700\u8BBE\u7F6E\u3002", "This is a direct chat; its scope is already isolated, so no setting is needed.");
|
|
11636
11952
|
return;
|
|
11637
11953
|
}
|
|
11638
11954
|
const mode = args.trim().toLowerCase();
|
|
11639
11955
|
if (!mode) {
|
|
11640
|
-
await
|
|
11956
|
+
await reply8(
|
|
11641
11957
|
ctx,
|
|
11642
11958
|
`\u5F53\u524D\u7FA4\u4F1A\u8BDD\u9694\u79BB\u6A21\u5F0F\uFF1A**${ctx.isolationStore?.get(ctx.chatId) ?? "topic"}**\u3002\u53EF\u7528 \`/isolation group|topic|member\` \u5207\u6362\u3002`,
|
|
11643
11959
|
`Current group isolation mode: **${ctx.isolationStore?.get(ctx.chatId) ?? "topic"}**. Use \`/isolation group|topic|member\` to switch.`
|
|
@@ -11645,16 +11961,16 @@ async function handleIsolation(args, ctx) {
|
|
|
11645
11961
|
return;
|
|
11646
11962
|
}
|
|
11647
11963
|
if (mode !== "group" && mode !== "topic" && mode !== "member") {
|
|
11648
|
-
await
|
|
11964
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/isolation group|topic|member`", "Usage: `/isolation group|topic|member`");
|
|
11649
11965
|
return;
|
|
11650
11966
|
}
|
|
11651
|
-
if (!
|
|
11967
|
+
if (!requireAdmin4(ctx)) return;
|
|
11652
11968
|
if (!ctx.isolationStore) {
|
|
11653
|
-
await
|
|
11969
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u7FA4\u804A\u9694\u79BB\u7B56\u7565\u5B58\u50A8\u3002", "Group isolation policy storage is not enabled in this runtime.");
|
|
11654
11970
|
return;
|
|
11655
11971
|
}
|
|
11656
11972
|
ctx.isolationStore.set(ctx.chatId, mode);
|
|
11657
|
-
await
|
|
11973
|
+
await reply8(
|
|
11658
11974
|
ctx,
|
|
11659
11975
|
`\u5DF2\u5C06\u672C\u7FA4\u9694\u79BB\u6A21\u5F0F\u8BBE\u4E3A **${mode}**\uFF0C\u4ECE\u4E0B\u4E00\u6761\u6D88\u606F\u8D77\u751F\u6548\u3002\u5DF2\u6709 group / topic / member scope \u4E0E\u4F1A\u8BDD\u6570\u636E\u5747\u4FDD\u7559\uFF0C\u5207\u56DE\u540E\u53EF\u7EE7\u7EED\u4F7F\u7528\u3002`,
|
|
11660
11976
|
`Set this group\u2019s isolation mode to **${mode}**. It takes effect on the next message; existing group/topic/member scopes and session data are preserved.`
|
|
@@ -11681,25 +11997,25 @@ async function handleVersion(_args, ctx) {
|
|
|
11681
11997
|
lines.push("\u6700\u65B0\u7248\u672C\u67E5\u8BE2\u6682\u4E0D\u53EF\u7528\uFF08\u7F51\u7EDC / registry \u5F02\u5E38\uFF09\u3002");
|
|
11682
11998
|
english.push("Latest-version lookup is unavailable (network or registry error).");
|
|
11683
11999
|
}
|
|
11684
|
-
await
|
|
12000
|
+
await reply8(ctx, lines.join("\n"), english.join("\n"));
|
|
11685
12001
|
}
|
|
11686
12002
|
async function handleUpgrade(_args, ctx) {
|
|
11687
|
-
if (!
|
|
12003
|
+
if (!requireAdmin4(ctx)) return;
|
|
11688
12004
|
if (!ctx.senderId || !ctx.channelUpdates) {
|
|
11689
|
-
await
|
|
12005
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u65B9\u5F0F\u4E0D\u652F\u6301\u98DE\u4E66\u5185\u66F4\u65B0\u3002", "In-chat updates are unavailable in this runtime.");
|
|
11690
12006
|
return;
|
|
11691
12007
|
}
|
|
11692
12008
|
const result = await ctx.channelUpdates.check({ scope: ctx.scope, actorId: ctx.senderId });
|
|
11693
12009
|
if (result.kind === "unavailable") {
|
|
11694
|
-
await
|
|
12010
|
+
await reply8(ctx, "\u6682\u65F6\u65E0\u6CD5\u67E5\u8BE2 npm \u6700\u65B0\u7248\u672C\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002", "The latest npm version is temporarily unavailable. Try again later.");
|
|
11695
12011
|
return;
|
|
11696
12012
|
}
|
|
11697
12013
|
if (result.kind === "current") {
|
|
11698
|
-
await
|
|
12014
|
+
await reply8(ctx, `\u2705 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF1A\`${result.current}\`\u3002`, `\u2705 Already up to date: \`${result.current}\`.`);
|
|
11699
12015
|
return;
|
|
11700
12016
|
}
|
|
11701
12017
|
if (!ctx.channel.sendCard) {
|
|
11702
|
-
await
|
|
12018
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u65E0\u6CD5\u53D1\u9001\u66F4\u65B0\u786E\u8BA4\u5361\uFF0C\u672A\u6267\u884C\u4EFB\u4F55\u66F4\u65B0\u3002", "This channel cannot send an update confirmation card. Nothing was changed.");
|
|
11703
12019
|
return;
|
|
11704
12020
|
}
|
|
11705
12021
|
await ctx.channel.sendCard(ctx.chatId, renderUpdateCard({
|
|
@@ -11714,9 +12030,9 @@ async function handleUpgrade(_args, ctx) {
|
|
|
11714
12030
|
});
|
|
11715
12031
|
}
|
|
11716
12032
|
async function handleDoctor(_args, ctx) {
|
|
11717
|
-
if (!
|
|
12033
|
+
if (!requireAdmin4(ctx)) return;
|
|
11718
12034
|
if (!ctx.createDiagnosticBundle || !ctx.channel.sendFile) {
|
|
11719
|
-
await
|
|
12035
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u751F\u6210\u6216\u53D1\u9001\u8BCA\u65AD\u6587\u4EF6\u3002", "Diagnostic generation or file delivery is unavailable in this channel.");
|
|
11720
12036
|
return;
|
|
11721
12037
|
}
|
|
11722
12038
|
let file;
|
|
@@ -11733,7 +12049,7 @@ async function handleDoctor(_args, ctx) {
|
|
|
11733
12049
|
...status.jobs ? { jobs: status.jobs } : {}
|
|
11734
12050
|
}), ctx.diagnosticTimeoutMs?.generate ?? DOCTOR_GENERATE_TIMEOUT_MS);
|
|
11735
12051
|
} catch {
|
|
11736
|
-
await
|
|
12052
|
+
await reply8(
|
|
11737
12053
|
ctx,
|
|
11738
12054
|
"\u26A0\uFE0F \u8BCA\u65AD\u5305\u751F\u6210\u5931\u8D25\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u8BF7\u5728\u672C\u673A\u8FD0\u884C `dsh-lark-bot doctor`\u3002",
|
|
11739
12055
|
"\u26A0\uFE0F Failed to generate the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
@@ -11747,21 +12063,21 @@ async function handleDoctor(_args, ctx) {
|
|
|
11747
12063
|
}), ctx.diagnosticTimeoutMs?.upload ?? DOCTOR_UPLOAD_TIMEOUT_MS);
|
|
11748
12064
|
} catch (error) {
|
|
11749
12065
|
if (error instanceof OperationTimeoutError) {
|
|
11750
|
-
await
|
|
12066
|
+
await reply8(
|
|
11751
12067
|
ctx,
|
|
11752
12068
|
"\u26A0\uFE0F \u8BCA\u65AD\u5305\u4E0A\u4F20\u7B49\u5F85\u8D85\u65F6\uFF0C\u7ED3\u679C\u672A\u77E5\uFF1A\u6587\u4EF6\u4ECD\u53EF\u80FD\u7A0D\u540E\u5230\u8FBE\u3002\u8BF7\u5148\u68C0\u67E5\u5F53\u524D\u804A\u5929\uFF0C\u4E0D\u8981\u7ACB\u5373\u91CD\u8BD5\u3002",
|
|
11753
12069
|
"\u26A0\uFE0F Diagnostic upload timed out with an unknown result: the file may still arrive later. Check this chat before retrying."
|
|
11754
12070
|
);
|
|
11755
12071
|
return;
|
|
11756
12072
|
}
|
|
11757
|
-
await
|
|
12073
|
+
await reply8(
|
|
11758
12074
|
ctx,
|
|
11759
12075
|
"\u26A0\uFE0F \u8BCA\u65AD\u5305\u53D1\u9001\u5931\u8D25\u3002\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u82E5\u6301\u7EED\u5931\u8D25\uFF0C\u8BF7\u5728\u672C\u673A\u8FD0\u884C `dsh-lark-bot doctor`\u3002",
|
|
11760
12076
|
"\u26A0\uFE0F Failed to send the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
11761
12077
|
);
|
|
11762
12078
|
return;
|
|
11763
12079
|
}
|
|
11764
|
-
await
|
|
12080
|
+
await reply8(
|
|
11765
12081
|
ctx,
|
|
11766
12082
|
"\u2705 \u8131\u654F\u8BCA\u65AD\u5305\u5DF2\u751F\u6210\u5E76\u53D1\u9001\uFF0C\u53EF\u76F4\u63A5\u4E0B\u8F7D\u8F6C\u53D1\u7ED9\u7EF4\u62A4\u8005\u3002",
|
|
11767
12083
|
"\u2705 The redacted diagnostic bundle was generated and sent. You can download and forward it to a maintainer."
|
|
@@ -11771,18 +12087,18 @@ async function handleResume(_args, ctx) {
|
|
|
11771
12087
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11772
12088
|
const history = ctx.sessions.historyFor(ctx.scope, cwd);
|
|
11773
12089
|
if (history.length === 0) {
|
|
11774
|
-
await
|
|
12090
|
+
await reply8(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002", "This session has no conversation history.");
|
|
11775
12091
|
return;
|
|
11776
12092
|
}
|
|
11777
12093
|
const recent = history.slice(-6).map((message) => {
|
|
11778
12094
|
const speaker = message.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
11779
12095
|
return `${speaker} ${message.content.slice(0, 300)}`;
|
|
11780
12096
|
});
|
|
11781
|
-
await
|
|
12097
|
+
await reply8(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"), [`Current scope: \`${ctx.scope}\``, "", ...recent].join("\n"));
|
|
11782
12098
|
}
|
|
11783
12099
|
async function handleJobs(args, ctx) {
|
|
11784
12100
|
if (!ctx.jobs) {
|
|
11785
|
-
await
|
|
12101
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u6301\u4E45\u4EFB\u52A1\u8D26\u672C\u3002", "The durable job ledger is not enabled in this runtime.");
|
|
11786
12102
|
return;
|
|
11787
12103
|
}
|
|
11788
12104
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -11790,11 +12106,11 @@ async function handleJobs(args, ctx) {
|
|
|
11790
12106
|
const records = ctx.jobs.list(ctx.scope, cwd, 20);
|
|
11791
12107
|
if (sub === "retry") {
|
|
11792
12108
|
if (!id) {
|
|
11793
|
-
await
|
|
12109
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs retry <\u6D88\u606FID>`", "Usage: `/jobs retry <message-id>`");
|
|
11794
12110
|
return;
|
|
11795
12111
|
}
|
|
11796
12112
|
const queued = await ctx.requeueJob?.(id, ctx.scope, cwd);
|
|
11797
|
-
await
|
|
12113
|
+
await reply8(
|
|
11798
12114
|
ctx,
|
|
11799
12115
|
queued ? `\u267B\uFE0F \u5DF2\u5C06\u4EFB\u52A1 **${id}** \u91CD\u65B0\u5165\u961F\u3002\u91CD\u590D\u6267\u884C\u53EF\u80FD\u91CD\u590D\u5916\u90E8\u526F\u4F5C\u7528\uFF0C\u8BF7\u7559\u610F\u539F\u4EFB\u52A1 checkpoint\u3002` : `\u65E0\u6CD5\u91CD\u8BD5 **${id}**\uFF1A\u53EA\u5141\u8BB8\u91CD\u8BD5\u5F53\u524D workspace \u5185\u7684 failed / interrupted \u4EFB\u52A1\u3002`,
|
|
11800
12116
|
queued ? `\u267B\uFE0F Requeued job **${id}**. Retrying may repeat external side effects; review the original checkpoint.` : `Cannot retry **${id}**: only failed or interrupted jobs in the current workspace are eligible.`
|
|
@@ -11804,19 +12120,19 @@ async function handleJobs(args, ctx) {
|
|
|
11804
12120
|
if (sub === "show") {
|
|
11805
12121
|
const record = id ? ctx.jobs.get(id, ctx.scope, cwd) : void 0;
|
|
11806
12122
|
if (!record) {
|
|
11807
|
-
await
|
|
12123
|
+
await reply8(ctx, `\u5F53\u524D workspace \u672A\u627E\u5230\u4EFB\u52A1\uFF1A**${id ?? ""}**`, `Job not found in this workspace: **${id ?? ""}**`);
|
|
11808
12124
|
return;
|
|
11809
12125
|
}
|
|
11810
|
-
await
|
|
12126
|
+
await reply8(ctx, jobDetail(record, "zh"), jobDetail(record, "en"));
|
|
11811
12127
|
return;
|
|
11812
12128
|
}
|
|
11813
12129
|
if (sub && sub !== "list") {
|
|
11814
|
-
await
|
|
12130
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs [list|show <\u6D88\u606FID>|retry <\u6D88\u606FID>]`", "Usage: `/jobs [list|show <message-id>|retry <message-id>]`");
|
|
11815
12131
|
return;
|
|
11816
12132
|
}
|
|
11817
12133
|
const counts = ctx.jobs.counts(ctx.scope, cwd);
|
|
11818
12134
|
const rows = records.slice(0, 10);
|
|
11819
|
-
await
|
|
12135
|
+
await reply8(
|
|
11820
12136
|
ctx,
|
|
11821
12137
|
[
|
|
11822
12138
|
"**\u4EFB\u52A1\u5BF9\u8D26**",
|
|
@@ -11863,7 +12179,7 @@ async function handleStop(_args, ctx) {
|
|
|
11863
12179
|
});
|
|
11864
12180
|
let stopped = 0;
|
|
11865
12181
|
for (const scope of scopes) stopped += await ctx.activeRuns.interrupt(scope);
|
|
11866
|
-
await
|
|
12182
|
+
await reply8(
|
|
11867
12183
|
ctx,
|
|
11868
12184
|
stopped > 0 ? `\u5DF2\u8BF7\u6C42\u7EC8\u6B62\u5F53\u524D\u53CA\u5207\u6362\u524D\u9694\u79BB scope \u7684\u5168\u90E8 ${String(stopped)} \u4E2A\u4EFB\u52A1\u3002` : "\u5F53\u524D\u53CA\u5207\u6362\u524D\u9694\u79BB scope \u5747\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002",
|
|
11869
12185
|
stopped > 0 ? `Requested termination of all ${String(stopped)} task(s) in the current and previously selected isolation scopes.` : "No tasks are running in the current or previously selected isolation scopes."
|
|
@@ -11874,7 +12190,7 @@ async function handleTimeout(args, ctx) {
|
|
|
11874
12190
|
const effectiveMs = ctx.runPolicies.get(ctx.scope) ?? ctx.defaultRunTimeoutMs;
|
|
11875
12191
|
if (!input) {
|
|
11876
12192
|
const minutes2 = effectiveMs > 0 ? Math.round(effectiveMs / 6e4) : 0;
|
|
11877
|
-
await
|
|
12193
|
+
await reply8(
|
|
11878
12194
|
ctx,
|
|
11879
12195
|
minutes2 > 0 ? `\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\uFF1A\u6301\u7EED\u65E0\u6D3B\u52A8\u4E8B\u4EF6 ${minutes2} \u5206\u949F\u624D\u7EC8\u6B62\u3002\u53EF\u7528 \`/timeout <N|off|default>\` \u8C03\u6574\u3002` : "\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\uFF1A\u5173\u95ED\u3002",
|
|
11880
12196
|
minutes2 > 0 ? `Session idle timeout: stop after ${minutes2} minutes without activity events. Use \`/timeout <N|off|default>\` to change it.` : "Session idle timeout: off."
|
|
@@ -11883,27 +12199,27 @@ async function handleTimeout(args, ctx) {
|
|
|
11883
12199
|
}
|
|
11884
12200
|
if (input === "off") {
|
|
11885
12201
|
ctx.runPolicies.set(ctx.scope, 0);
|
|
11886
|
-
await
|
|
12202
|
+
await reply8(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\u3002", "Disabled the session idle timeout.");
|
|
11887
12203
|
return;
|
|
11888
12204
|
}
|
|
11889
12205
|
if (input === "default") {
|
|
11890
12206
|
ctx.runPolicies.clear(ctx.scope);
|
|
11891
|
-
await
|
|
12207
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u7A7A\u95F2\u8D85\u65F6\u3002", "Restored the default idle timeout.");
|
|
11892
12208
|
return;
|
|
11893
12209
|
}
|
|
11894
12210
|
const minutes = Number(input);
|
|
11895
12211
|
if (!Number.isInteger(minutes) || minutes <= 0) {
|
|
11896
|
-
await
|
|
12212
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/timeout <N|off|default>`\uFF0CN \u4E3A\u5927\u4E8E 0 \u7684\u5206\u949F\u6570\u3002", "Usage: `/timeout <N|off|default>`, where N is a positive number of minutes.");
|
|
11897
12213
|
return;
|
|
11898
12214
|
}
|
|
11899
12215
|
ctx.runPolicies.set(ctx.scope, minutes * 6e4);
|
|
11900
|
-
await
|
|
12216
|
+
await reply8(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\uFF1A\u6301\u7EED\u65E0\u6D3B\u52A8\u4E8B\u4EF6 ${minutes} \u5206\u949F\u624D\u7EC8\u6B62\u3002`, `Set the session idle timeout to ${minutes} minutes without activity events.`);
|
|
11901
12217
|
}
|
|
11902
12218
|
async function handleConcurrency(args, ctx) {
|
|
11903
12219
|
const input = args.trim();
|
|
11904
12220
|
const effective = ctx.concurrencyStore.get(ctx.scope) ?? ctx.defaultScopeConcurrency;
|
|
11905
12221
|
if (!input) {
|
|
11906
|
-
await
|
|
12222
|
+
await reply8(
|
|
11907
12223
|
ctx,
|
|
11908
12224
|
`\u5F53\u524D scope \u7684\u5E76\u884C\u4EFB\u52A1\u6570\uFF1A**${String(effective)}**\u3002\u53EF\u7528 \`/concurrency <N|default>\` \u8C03\u6574\uFF08N \u2265 1\uFF09\u3002`,
|
|
11909
12225
|
`Parallel tasks for this scope: **${String(effective)}**. Use \`/concurrency <N|default>\` to change it (N \u2265 1).`
|
|
@@ -11912,16 +12228,16 @@ async function handleConcurrency(args, ctx) {
|
|
|
11912
12228
|
}
|
|
11913
12229
|
if (input === "default") {
|
|
11914
12230
|
ctx.concurrencyStore.clear(ctx.scope);
|
|
11915
|
-
await
|
|
12231
|
+
await reply8(ctx, `\u5DF2\u6062\u590D\u9ED8\u8BA4\u5E76\u884C\u4EFB\u52A1\u6570\uFF08${String(ctx.defaultScopeConcurrency)}\uFF09\u3002`, `Restored the default parallel-task limit (${String(ctx.defaultScopeConcurrency)}).`);
|
|
11916
12232
|
return;
|
|
11917
12233
|
}
|
|
11918
12234
|
const n = Number(input);
|
|
11919
12235
|
if (!Number.isInteger(n) || n < 1) {
|
|
11920
|
-
await
|
|
12236
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/concurrency <N|default>`\uFF0CN \u4E3A\u5927\u4E8E\u7B49\u4E8E 1 \u7684\u6574\u6570\u3002", "Usage: `/concurrency <N|default>`, where N is an integer of at least 1.");
|
|
11921
12237
|
return;
|
|
11922
12238
|
}
|
|
11923
12239
|
ctx.concurrencyStore.set(ctx.scope, n);
|
|
11924
|
-
await
|
|
12240
|
+
await reply8(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D scope \u7684\u5E76\u884C\u4EFB\u52A1\u6570\uFF1A**${String(n)}**\u3002`, `Set parallel tasks for this scope to **${String(n)}**.`);
|
|
11925
12241
|
}
|
|
11926
12242
|
async function handlePermission(args, ctx) {
|
|
11927
12243
|
const [rawPolicy = "", rawTarget = "", ...extra] = args.trim().split(/\s+/);
|
|
@@ -11933,7 +12249,7 @@ async function handlePermission(args, ctx) {
|
|
|
11933
12249
|
};
|
|
11934
12250
|
if (!input) {
|
|
11935
12251
|
const current = ctx.permissionPolicies?.get(ctx.scope) ?? "ask";
|
|
11936
|
-
await
|
|
12252
|
+
await reply8(
|
|
11937
12253
|
ctx,
|
|
11938
12254
|
`\u5F53\u524D scope \u7684\u5DE5\u5177\u6743\u9650\u7B56\u7565\uFF1A**${labels[current].zh}**\uFF08\`${current}\`\uFF09\u3002\u53EF\u7528 \`/permission ask|allow|deny\` \u8C03\u6574\uFF08\u4EC5\u7BA1\u7406\u5458\uFF09\u3002`,
|
|
11939
12255
|
`Tool permission policy for this scope: **${labels[current].en}** (\`${current}\`). Use \`/permission ask|allow|deny\` to change it (admin only).`
|
|
@@ -11941,21 +12257,21 @@ async function handlePermission(args, ctx) {
|
|
|
11941
12257
|
return;
|
|
11942
12258
|
}
|
|
11943
12259
|
if (input !== "ask" && input !== "allow" && input !== "deny" || extra.length > 0) {
|
|
11944
|
-
await
|
|
12260
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/permission [ask|allow|deny] [scope]`", "Usage: `/permission [ask|allow|deny] [scope]`");
|
|
11945
12261
|
return;
|
|
11946
12262
|
}
|
|
11947
|
-
if (!
|
|
12263
|
+
if (!requireAdmin4(ctx)) return;
|
|
11948
12264
|
if (!ctx.permissionPolicies) {
|
|
11949
|
-
await
|
|
12265
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u5DE5\u5177\u6743\u9650\u7B56\u7565\u5B58\u50A8\u3002", "Tool permission policy storage is not enabled in this runtime.");
|
|
11950
12266
|
return;
|
|
11951
12267
|
}
|
|
11952
12268
|
const targetScope = rawTarget || ctx.scope;
|
|
11953
12269
|
if (targetScope !== ctx.chatId && !targetScope.startsWith(`${ctx.chatId}:`)) {
|
|
11954
|
-
await
|
|
12270
|
+
await reply8(ctx, "\u53EA\u80FD\u4FEE\u6539\u5F53\u524D\u804A\u5929\u5185\u7684 scope\u3002\u8BF7\u4ECE\u76EE\u6807\u4F1A\u8BDD `/status` \u590D\u5236\u5B8C\u6574 scope\u3002", "You can only modify a scope in the current chat. Copy the full scope from the target session\u2019s `/status`.");
|
|
11955
12271
|
return;
|
|
11956
12272
|
}
|
|
11957
12273
|
await ctx.permissionPolicies.set(targetScope, input);
|
|
11958
|
-
await
|
|
12274
|
+
await reply8(
|
|
11959
12275
|
ctx,
|
|
11960
12276
|
`\u5DF2\u5C06 scope \`${targetScope}\` \u7684\u5DE5\u5177\u6743\u9650\u7B56\u7565\u8BBE\u4E3A **${labels[input].zh}**\uFF08\`${input}\`\uFF09\uFF0C\u4ECE\u4E0B\u4E00\u6B21\u5DE5\u5177\u5BA1\u6279\u8D77\u751F\u6548\u3002\u8BA1\u5212\u5BA1\u6279\u4ECD\u4F1A\u7167\u5E38\u6267\u884C\u3002`,
|
|
11961
12277
|
`Set scope \`${targetScope}\`'s tool permission policy to **${labels[input].en}** (\`${input}\`). It applies to the next tool approval; plan approval remains enforced.`
|
|
@@ -11965,7 +12281,7 @@ async function handleDensity(args, ctx) {
|
|
|
11965
12281
|
const input = args.trim().toLowerCase();
|
|
11966
12282
|
if (!input) {
|
|
11967
12283
|
const current = ctx.densityStore?.get(ctx.scope) ?? "standard";
|
|
11968
|
-
await
|
|
12284
|
+
await reply8(
|
|
11969
12285
|
ctx,
|
|
11970
12286
|
`\u5F53\u524D\u5361\u7247\u5BC6\u5EA6\uFF1A**${current}**\u3002\u53EF\u7528 \`/density compact|standard|detailed\` \u8C03\u6574\u3002`,
|
|
11971
12287
|
`Current card density: **${current}**. Use \`/density compact|standard|detailed\` to change it.`
|
|
@@ -11974,21 +12290,21 @@ async function handleDensity(args, ctx) {
|
|
|
11974
12290
|
}
|
|
11975
12291
|
if (input === "default") {
|
|
11976
12292
|
ctx.densityStore?.clear(ctx.scope);
|
|
11977
|
-
await
|
|
12293
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002", "Restored the default card density.");
|
|
11978
12294
|
return;
|
|
11979
12295
|
}
|
|
11980
12296
|
const density = parseCardDensity(input);
|
|
11981
12297
|
if (!density) {
|
|
11982
|
-
await
|
|
12298
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`", "Usage: `/density [compact|standard|detailed|default]`");
|
|
11983
12299
|
return;
|
|
11984
12300
|
}
|
|
11985
12301
|
ctx.densityStore?.set(ctx.scope, density);
|
|
11986
|
-
await
|
|
12302
|
+
await reply8(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u5361\u7247\u5BC6\u5EA6\uFF1A**${density}**\u3002`, `Set this session\u2019s card density to **${density}**.`);
|
|
11987
12303
|
}
|
|
11988
12304
|
async function handleExecutionMode(args, ctx) {
|
|
11989
12305
|
const input = args.trim().toLowerCase();
|
|
11990
12306
|
if (!ctx.executionModes) {
|
|
11991
|
-
await
|
|
12307
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u73AF\u5883\u672A\u542F\u7528\u6267\u884C\u6A21\u5F0F\u5B58\u50A8\u3002", "Execution mode storage is unavailable in this runtime.");
|
|
11992
12308
|
return;
|
|
11993
12309
|
}
|
|
11994
12310
|
if (!input) {
|
|
@@ -12005,7 +12321,7 @@ async function handleExecutionMode(args, ctx) {
|
|
|
12005
12321
|
} catch {
|
|
12006
12322
|
}
|
|
12007
12323
|
}
|
|
12008
|
-
await
|
|
12324
|
+
await reply8(
|
|
12009
12325
|
ctx,
|
|
12010
12326
|
`\u5F53\u524D\u6267\u884C\u6A21\u5F0F\uFF1A**${current}**\u3002\u53EF\u7528 \`/mode quick|balanced|deep\` \u5207\u6362\uFF1B\u4E0B\u4E00\u8F6E\u751F\u6548\u3002`,
|
|
12011
12327
|
`Current execution mode: **${current}**. Use \`/mode quick|balanced|deep\`; changes apply next turn.`
|
|
@@ -12013,18 +12329,18 @@ async function handleExecutionMode(args, ctx) {
|
|
|
12013
12329
|
return;
|
|
12014
12330
|
}
|
|
12015
12331
|
if (input !== "quick" && input !== "balanced" && input !== "deep") {
|
|
12016
|
-
await
|
|
12332
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/mode [quick|balanced|deep]`", "Usage: `/mode [quick|balanced|deep]`");
|
|
12017
12333
|
return;
|
|
12018
12334
|
}
|
|
12019
12335
|
await ctx.executionModes.set(ctx.scope, input);
|
|
12020
|
-
await
|
|
12336
|
+
await reply8(
|
|
12021
12337
|
ctx,
|
|
12022
12338
|
`\u5DF2\u5C06\u5F53\u524D\u4F1A\u8BDD\u6267\u884C\u6A21\u5F0F\u8BBE\u4E3A **${input}**\uFF0C\u4ECE\u4E0B\u4E00\u8F6E\u751F\u6548\uFF1B\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1\u4E0E\u73B0\u6709\u4E0A\u4E0B\u6587\u4E0D\u53D7\u5F71\u54CD\u3002`,
|
|
12023
12339
|
`Set this session\u2019s execution mode to **${input}** for the next turn. Active work and existing context are unchanged.`
|
|
12024
12340
|
);
|
|
12025
12341
|
}
|
|
12026
12342
|
async function handleEffortAlias(args, ctx) {
|
|
12027
|
-
await
|
|
12343
|
+
await reply8(
|
|
12028
12344
|
ctx,
|
|
12029
12345
|
"`/effort` \u662F `/mode` \u7684\u517C\u5BB9\u522B\u540D\uFF0C\u53EA\u63A7\u5236\u4EFB\u52A1\u6267\u884C\u5F3A\u5EA6\uFF1B\u6A21\u578B\u63A8\u7406\u6863\u4F4D\u7531\u6A21\u578B\u9002\u914D\u5668\u51B3\u5B9A\u3002",
|
|
12030
12346
|
"`/effort` is a compatibility alias for `/mode`; it controls task execution intensity, not the model reasoning-effort setting."
|
|
@@ -12034,11 +12350,11 @@ async function handleEffortAlias(args, ctx) {
|
|
|
12034
12350
|
async function handleAsk(args, ctx) {
|
|
12035
12351
|
const question = args.trim();
|
|
12036
12352
|
if (!question) {
|
|
12037
|
-
await
|
|
12353
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`", "Usage: `/ask <question>`");
|
|
12038
12354
|
return;
|
|
12039
12355
|
}
|
|
12040
12356
|
if (!ctx.questions) {
|
|
12041
|
-
await
|
|
12357
|
+
await reply8(ctx, "\u95EE\u7B54\u5361\u672A\u542F\u7528\uFF08\u8BF7\u786E\u8BA4 questions \u5DF2\u63A5\u7EBF\uFF09\u3002", "Question cards are disabled (check the questions integration).");
|
|
12042
12358
|
return;
|
|
12043
12359
|
}
|
|
12044
12360
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -12060,18 +12376,18 @@ async function handleAsk(args, ctx) {
|
|
|
12060
12376
|
if (answer !== void 0) {
|
|
12061
12377
|
const text = Array.isArray(answer) ? answer.join("\u3001") : answer;
|
|
12062
12378
|
ctx.sessions.recordExchange(ctx.scope, cwd, [text], void 0);
|
|
12063
|
-
await
|
|
12379
|
+
await reply8(ctx, "\u5DF2\u8BB0\u5F55\u4F60\u7684\u56DE\u7B54\uFF0C\u5E76\u5199\u5165\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002", "Recorded your answer in the session context.");
|
|
12064
12380
|
} else {
|
|
12065
|
-
await
|
|
12381
|
+
await reply8(ctx, "\u672A\u6536\u5230\u56DE\u7B54\uFF08\u5361\u7247\u53EF\u80FD\u5DF2\u8D85\u65F6\u6216\u88AB\u5FFD\u7565\uFF09\u3002", "No answer was received (the card may have expired or been ignored).");
|
|
12066
12382
|
}
|
|
12067
12383
|
}
|
|
12068
12384
|
async function handleInvite(args, ctx) {
|
|
12069
12385
|
const [kind, ...rest] = args.trim().split(/\s+/);
|
|
12070
12386
|
const id = rest.join(" ").trim();
|
|
12071
|
-
if (kind !== "list" && !
|
|
12387
|
+
if (kind !== "list" && !requireAdmin4(ctx)) return;
|
|
12072
12388
|
if (kind === "list") {
|
|
12073
12389
|
const snapshot = ctx.accessManager.snapshot();
|
|
12074
|
-
await
|
|
12390
|
+
await reply8(
|
|
12075
12391
|
ctx,
|
|
12076
12392
|
[
|
|
12077
12393
|
"**\u8BBF\u95EE\u767D\u540D\u5355**",
|
|
@@ -12089,7 +12405,7 @@ async function handleInvite(args, ctx) {
|
|
|
12089
12405
|
return;
|
|
12090
12406
|
}
|
|
12091
12407
|
if (!kind || !id) {
|
|
12092
|
-
await
|
|
12408
|
+
await reply8(
|
|
12093
12409
|
ctx,
|
|
12094
12410
|
"\u7528\u6CD5\uFF1A`/invite user|admin|group <id>`\u3001`/invite list`\u3001`/invite remove user|group <id>`",
|
|
12095
12411
|
"Usage: `/invite user|admin|group <id>`, `/invite list`, or `/invite remove user|group <id>`"
|
|
@@ -12098,39 +12414,39 @@ async function handleInvite(args, ctx) {
|
|
|
12098
12414
|
}
|
|
12099
12415
|
if (kind === "user") {
|
|
12100
12416
|
await ctx.accessManager.addUser(id);
|
|
12101
|
-
await
|
|
12417
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``, `Allowed user: \`${id}\``);
|
|
12102
12418
|
return;
|
|
12103
12419
|
}
|
|
12104
12420
|
if (kind === "admin") {
|
|
12105
12421
|
await ctx.accessManager.addAdmin(id);
|
|
12106
|
-
await
|
|
12422
|
+
await reply8(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``, `Granted admin access to: \`${id}\``);
|
|
12107
12423
|
return;
|
|
12108
12424
|
}
|
|
12109
12425
|
if (kind === "group") {
|
|
12110
12426
|
await ctx.accessManager.addChat(id);
|
|
12111
|
-
await
|
|
12427
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``, `Allowed group chat: \`${id}\``);
|
|
12112
12428
|
return;
|
|
12113
12429
|
}
|
|
12114
12430
|
if (kind === "remove") {
|
|
12115
12431
|
const [sub, target] = rest;
|
|
12116
12432
|
if (sub === "user" && target) {
|
|
12117
12433
|
await ctx.accessManager.removeUser(target);
|
|
12118
|
-
await
|
|
12434
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``, `Removed user: \`${target}\``);
|
|
12119
12435
|
return;
|
|
12120
12436
|
}
|
|
12121
12437
|
if (sub === "group" && target) {
|
|
12122
12438
|
await ctx.accessManager.removeChat(target);
|
|
12123
|
-
await
|
|
12439
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``, `Removed group chat: \`${target}\``);
|
|
12124
12440
|
return;
|
|
12125
12441
|
}
|
|
12126
|
-
await
|
|
12442
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/invite remove user <id>` \u6216 `/invite remove group <chatId>`", "Usage: `/invite remove user <id>` or `/invite remove group <chatId>`");
|
|
12127
12443
|
return;
|
|
12128
12444
|
}
|
|
12129
|
-
await
|
|
12445
|
+
await reply8(ctx, "\u672A\u77E5 `/invite` \u7C7B\u578B\uFF0C\u8BF7\u4F7F\u7528 user / admin / group / list / remove\u3002", "Unknown `/invite` type. Use user / admin / group / list / remove.");
|
|
12130
12446
|
}
|
|
12131
|
-
function
|
|
12447
|
+
function requireAdmin4(ctx) {
|
|
12132
12448
|
if (!ctx.accessManager.isAdmin(ctx.senderId)) {
|
|
12133
|
-
void
|
|
12449
|
+
void reply8(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u8BE5\u64CD\u4F5C\u3002", "Only admins can perform this operation.");
|
|
12134
12450
|
return false;
|
|
12135
12451
|
}
|
|
12136
12452
|
return true;
|
|
@@ -12183,20 +12499,20 @@ async function handleKeyDispatch(args, ctx) {
|
|
|
12183
12499
|
await handleKey(args, ctx);
|
|
12184
12500
|
}
|
|
12185
12501
|
async function handleHelp(_args, ctx) {
|
|
12186
|
-
await
|
|
12502
|
+
await reply8(ctx, renderCommandHelp("zh"), renderCommandHelp("en"));
|
|
12187
12503
|
}
|
|
12188
12504
|
async function handleLanguage(args, ctx) {
|
|
12189
12505
|
if (!ctx.languagePolicies) {
|
|
12190
|
-
await
|
|
12506
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u65F6\u672A\u542F\u7528\u8BED\u8A00\u7B56\u7565\u3002", "Language policy is unavailable in this runtime.");
|
|
12191
12507
|
return;
|
|
12192
12508
|
}
|
|
12193
12509
|
const [action = "show", field, value] = args.trim().split(/\s+/);
|
|
12194
12510
|
if (action === "show") {
|
|
12195
12511
|
const policy = ctx.languagePolicies.get();
|
|
12196
|
-
await
|
|
12512
|
+
await reply8(ctx, `\u8BED\u8A00\u7B56\u7565\uFF1AUI=\u6309\u67E5\u770B\u8005\uFF1B\u666E\u901A\u6587\u672C=${policy.plain}\uFF1BAgent=${policy.agent}`, `Language policy: UI=per viewer; plain=${policy.plain}; agent=${policy.agent}`);
|
|
12197
12513
|
return;
|
|
12198
12514
|
}
|
|
12199
|
-
if (!
|
|
12515
|
+
if (!requireAdmin4(ctx)) return;
|
|
12200
12516
|
if (action === "set" && field === "plain" && (value === "bilingual" || value === "zh" || value === "en")) {
|
|
12201
12517
|
await ctx.languagePolicies.set({ plain: value });
|
|
12202
12518
|
} else if (action === "set" && field === "agent" && (value === "auto" || value === "zh" || value === "en")) {
|
|
@@ -12204,7 +12520,7 @@ async function handleLanguage(args, ctx) {
|
|
|
12204
12520
|
} else if (action === "reset" && (field === void 0 || field === "all" || field === "plain" || field === "agent")) {
|
|
12205
12521
|
await ctx.languagePolicies.reset(field ?? "all");
|
|
12206
12522
|
} else {
|
|
12207
|
-
await
|
|
12523
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/language show|set plain <bilingual|zh|en>|set agent <auto|zh|en>|reset [plain|agent|all]`", "Usage: `/language show|set plain <bilingual|zh|en>|set agent <auto|zh|en>|reset [plain|agent|all]`");
|
|
12208
12524
|
return;
|
|
12209
12525
|
}
|
|
12210
12526
|
process.env.DSH_LARK_REPLY_LANG = ctx.languagePolicies.get().plain;
|
|
@@ -12215,37 +12531,37 @@ function parseSecretTarget(value) {
|
|
|
12215
12531
|
}
|
|
12216
12532
|
async function handleSecret(args, ctx) {
|
|
12217
12533
|
if (!ctx.secretService) {
|
|
12218
|
-
await
|
|
12534
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u65F6\u672A\u542F\u7528\u5B89\u5168\u5BC6\u94A5\u91C7\u96C6\u3002", "Secure secret collection is unavailable in this runtime.");
|
|
12219
12535
|
return;
|
|
12220
12536
|
}
|
|
12221
12537
|
const [action, targetRaw, referenceRaw, ...purposeParts] = args.trim().split(/\s+/);
|
|
12222
12538
|
const target = parseSecretTarget(targetRaw);
|
|
12223
12539
|
const reference = referenceRaw?.trim();
|
|
12224
12540
|
if (!target || !reference || !["status", "set", "remove"].includes(action ?? "")) {
|
|
12225
|
-
await
|
|
12541
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/secret status|set|remove <dsh-credential|app-secret> <\u5F15\u7528>`", "Usage: `/secret status|set|remove <dsh-credential|app-secret> <reference>`");
|
|
12226
12542
|
return;
|
|
12227
12543
|
}
|
|
12228
|
-
if (action !== "status" && !
|
|
12544
|
+
if (action !== "status" && !requireAdmin4(ctx)) return;
|
|
12229
12545
|
if (action === "status") {
|
|
12230
12546
|
const configured = await ctx.secretService.configured(target, reference);
|
|
12231
|
-
await
|
|
12547
|
+
await reply8(ctx, `${target} \`${reference}\`\uFF1A${configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`, `${target} \`${reference}\`: ${configured ? "configured" : "not configured"}`);
|
|
12232
12548
|
return;
|
|
12233
12549
|
}
|
|
12234
12550
|
if (action === "remove") {
|
|
12235
12551
|
const removed = await ctx.secretService.remove(target, reference);
|
|
12236
|
-
await
|
|
12552
|
+
await reply8(ctx, removed ? "\u2705 \u5BC6\u94A5\u5DF2\u5220\u9664\u3002" : "\u8BE5\u5F15\u7528\u672A\u914D\u7F6E\u5BC6\u94A5\u3002", removed ? "\u2705 Secret removed." : "No secret is configured for that reference.");
|
|
12237
12553
|
return;
|
|
12238
12554
|
}
|
|
12239
12555
|
if (!ctx.senderId) {
|
|
12240
|
-
await
|
|
12556
|
+
await reply8(ctx, "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\uFF0C\u4E0D\u80FD\u6253\u5F00\u5B89\u5168\u8868\u5355\u3002", "The operator cannot be identified, so the secure form cannot be opened.");
|
|
12241
12557
|
return;
|
|
12242
12558
|
}
|
|
12243
12559
|
await ctx.secretService.request({ scope: ctx.scope, chatId: ctx.chatId, ...ctx.threadId ? { threadId: ctx.threadId } : {}, ownerId: ctx.senderId, target, reference, purpose: purposeParts.join(" ") || "Configure a protected value" });
|
|
12244
|
-
await
|
|
12560
|
+
await reply8(ctx, "\u5DF2\u53D1\u9001\u4EC5\u9650\u4F60\u63D0\u4EA4\u7684\u5B89\u5168\u8868\u5355\uFF1B\u8BF7\u52FF\u5728\u666E\u901A\u6D88\u606F\u4E2D\u7C98\u8D34\u5BC6\u94A5\u3002", "A secure owner-only form was sent. Do not paste the secret into ordinary chat.");
|
|
12245
12561
|
}
|
|
12246
12562
|
async function handleSessionProjection(args, ctx) {
|
|
12247
12563
|
if (!ctx.sessionProjection) {
|
|
12248
|
-
await
|
|
12564
|
+
await reply8(
|
|
12249
12565
|
ctx,
|
|
12250
12566
|
"\u5F53\u524D adapter \u672A\u63D0\u4F9B DSH session \u6295\u5F71\uFF1B\u8BF7\u4F7F\u7528 `web` adapter \u5E76\u786E\u8BA4 Web host \u53EF\u7528\u3002",
|
|
12251
12567
|
"The current adapter does not provide DSH session projection; use the `web` adapter and ensure the Web host is reachable."
|
|
@@ -12275,6 +12591,7 @@ var handlers = {
|
|
|
12275
12591
|
"/role": handleRole,
|
|
12276
12592
|
"/notify": handleNotify,
|
|
12277
12593
|
"/notifications": handleNotifications,
|
|
12594
|
+
"/channels": handleChannels,
|
|
12278
12595
|
"/replies": handleReplies,
|
|
12279
12596
|
"/retention": handleRetention,
|
|
12280
12597
|
"/archive": handleArchive,
|
|
@@ -12754,24 +13071,26 @@ var GroupMessagePoller = class {
|
|
|
12754
13071
|
|
|
12755
13072
|
// src/bridge/reconnect-notifier.ts
|
|
12756
13073
|
var ReconnectNotifier = class {
|
|
12757
|
-
constructor(channel, directory, now = Date.now, reconciliation) {
|
|
13074
|
+
constructor(channel, directory, now = Date.now, reconciliation, onFault) {
|
|
12758
13075
|
this.channel = channel;
|
|
12759
13076
|
this.directory = directory;
|
|
12760
13077
|
this.now = now;
|
|
12761
13078
|
this.reconciliation = reconciliation;
|
|
13079
|
+
this.onFault = onFault;
|
|
12762
13080
|
}
|
|
12763
13081
|
channel;
|
|
12764
13082
|
directory;
|
|
12765
13083
|
now;
|
|
12766
13084
|
reconciliation;
|
|
13085
|
+
onFault;
|
|
12767
13086
|
startedAt;
|
|
12768
13087
|
async reconnecting() {
|
|
12769
13088
|
if (this.startedAt !== void 0) return;
|
|
12770
13089
|
this.startedAt = this.now();
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
);
|
|
13090
|
+
const zhCn = "\u26A0\uFE0F \u673A\u5668\u4EBA\u8FDE\u63A5\u4E0D\u7A33\u5B9A\uFF0C\u6B63\u5728\u81EA\u52A8\u91CD\u8FDE\uFF1B\u671F\u95F4\u7684\u65B0\u6D88\u606F\u53EF\u80FD\u5EF6\u8FDF\u5904\u7406\u3002";
|
|
13091
|
+
const enUs = "\u26A0\uFE0F The bot connection is unstable and reconnecting automatically. New messages may be delayed.";
|
|
13092
|
+
await this.send(zhCn, enUs);
|
|
13093
|
+
await this.fault(this.directory?.recentDestination()?.scope, { zh: zhCn, en: enUs });
|
|
12775
13094
|
}
|
|
12776
13095
|
async reconnected() {
|
|
12777
13096
|
if (this.startedAt === void 0) return;
|
|
@@ -12779,12 +13098,12 @@ var ReconnectNotifier = class {
|
|
|
12779
13098
|
this.startedAt = void 0;
|
|
12780
13099
|
const target = this.directory?.recentDestination();
|
|
12781
13100
|
const summary = target ? this.reconciliation?.(target.scope) : void 0;
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
${summary
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
);
|
|
13101
|
+
const zhCn = `\u2705 \u673A\u5668\u4EBA\u8FDE\u63A5\u5DF2\u6062\u590D\uFF08\u4E2D\u65AD\u7EA6 ${formatDuration(elapsed)}\uFF09\u3002${summary ? `
|
|
13102
|
+
${summary.zhCn}` : ""}`;
|
|
13103
|
+
const enUs = `\u2705 The bot connection recovered after about ${formatDurationEnglish(elapsed)}.${summary ? `
|
|
13104
|
+
${summary.enUs}` : ""}`;
|
|
13105
|
+
await this.send(zhCn, enUs);
|
|
13106
|
+
await this.fault(target?.scope, { zh: zhCn, en: enUs });
|
|
12788
13107
|
}
|
|
12789
13108
|
async send(zhCn, enUs) {
|
|
12790
13109
|
const target = this.directory?.recentDestination();
|
|
@@ -12794,6 +13113,13 @@ ${summary.enUs}` : ""}`
|
|
|
12794
13113
|
...target.threadId ? { threadId: target.threadId } : {}
|
|
12795
13114
|
});
|
|
12796
13115
|
}
|
|
13116
|
+
async fault(scope, title) {
|
|
13117
|
+
if (!scope || !this.onFault) return;
|
|
13118
|
+
try {
|
|
13119
|
+
await this.onFault(scope, title);
|
|
13120
|
+
} catch (error) {
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
12797
13123
|
};
|
|
12798
13124
|
function formatDurationEnglish(ms) {
|
|
12799
13125
|
if (ms < 1e3) return "less than 1 second";
|
|
@@ -13996,7 +14322,8 @@ async function startChannel(deps) {
|
|
|
13996
14322
|
zhCn: `\u8D26\u672C\u5BF9\u8D26\uFF1Aqueued ${counts.queued} \xB7 running ${counts.running} \xB7 interrupted ${counts.interrupted} \xB7 failed ${counts.failed}\uFF1B\u8BE6\u60C5\u89C1 \`/jobs\`\u3002`,
|
|
13997
14323
|
enUs: `Ledger reconciliation: queued ${counts.queued} \xB7 running ${counts.running} \xB7 interrupted ${counts.interrupted} \xB7 failed ${counts.failed}; use \`/jobs\` for details.`
|
|
13998
14324
|
};
|
|
13999
|
-
} : void 0
|
|
14325
|
+
} : void 0,
|
|
14326
|
+
deps.faultNotifier
|
|
14000
14327
|
);
|
|
14001
14328
|
const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
|
|
14002
14329
|
let groupPoller;
|
|
@@ -14131,6 +14458,8 @@ async function startChannel(deps) {
|
|
|
14131
14458
|
...deps.permissionPolicies ? { permissionPolicies: deps.permissionPolicies } : {},
|
|
14132
14459
|
...deps.notificationPreferences ? { notificationPreferences: deps.notificationPreferences } : {},
|
|
14133
14460
|
...deps.defaultNotificationPreference ? { defaultNotificationPreference: deps.defaultNotificationPreference } : {},
|
|
14461
|
+
...deps.notificationChannels ? { notificationChannels: deps.notificationChannels } : {},
|
|
14462
|
+
...deps.notificationSinks ? { notificationSinks: deps.notificationSinks } : {},
|
|
14134
14463
|
...deps.replyPolicies ? { replyPolicies: deps.replyPolicies } : {},
|
|
14135
14464
|
...deps.executionModes ? { executionModes: deps.executionModes } : {},
|
|
14136
14465
|
...deps.languagePolicies ? { languagePolicies: deps.languagePolicies } : {},
|
|
@@ -15495,6 +15824,11 @@ var NotificationDispatcher = class {
|
|
|
15495
15824
|
setDefaultPreference(preference) {
|
|
15496
15825
|
this.defaultPreference = clonePreference(preference);
|
|
15497
15826
|
}
|
|
15827
|
+
/**
|
|
15828
|
+
* Primary notification path: send to Feishu (default first-class route) and,
|
|
15829
|
+
* when the scope preference lists extra channels, fan out to those sinks.
|
|
15830
|
+
* Returns `false` when the event is disabled or no Feishu destination exists.
|
|
15831
|
+
*/
|
|
15498
15832
|
async notify(scope, event, detail) {
|
|
15499
15833
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
15500
15834
|
if (!preference?.events.includes(event)) return false;
|
|
@@ -15503,21 +15837,52 @@ var NotificationDispatcher = class {
|
|
|
15503
15837
|
log.warn("notification", "target-unavailable", { scope, target: preference.target });
|
|
15504
15838
|
return false;
|
|
15505
15839
|
}
|
|
15506
|
-
const title = event
|
|
15840
|
+
const title = titleForEvent(event);
|
|
15507
15841
|
try {
|
|
15508
|
-
await this.deps.send(destination.chatId,
|
|
15509
|
-
`${title[0]}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
15510
|
-
`${title[1]} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
15511
|
-
), {
|
|
15842
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
15512
15843
|
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
15513
15844
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
15514
15845
|
...preference.mentionUserIds.length > 0 ? { mentions: preference.mentionUserIds.map((userId) => ({ userId })) } : {}
|
|
15515
15846
|
});
|
|
15516
|
-
return true;
|
|
15517
15847
|
} catch (error) {
|
|
15518
15848
|
log.warn("notification", "send-failed", { scope, event, error });
|
|
15519
15849
|
return false;
|
|
15520
15850
|
}
|
|
15851
|
+
if (preference.sinks.length > 0) {
|
|
15852
|
+
await this.broadcastSinks(preference.sinks, scope, event, title, detail);
|
|
15853
|
+
}
|
|
15854
|
+
return true;
|
|
15855
|
+
}
|
|
15856
|
+
/**
|
|
15857
|
+
* Safety-net / fault class notification (issue #113): broadcast to every
|
|
15858
|
+
* enabled outbound sink regardless of the per-scope opt-in, so a crash /
|
|
15859
|
+
* reconnect / heartbeat anomaly is seen even when a scope has not enabled
|
|
15860
|
+
* proactive Feishu reminders. When the scope preference explicitly enables
|
|
15861
|
+
* the `urgent` event it is also sent to Feishu.
|
|
15862
|
+
*/
|
|
15863
|
+
async notifyUrgent(scope, title, detail) {
|
|
15864
|
+
const channels = this.deps.sinks?.enabledChannels() ?? [];
|
|
15865
|
+
if (channels.length > 0) {
|
|
15866
|
+
await this.broadcastSinks(
|
|
15867
|
+
channels.map((channel) => channel.id),
|
|
15868
|
+
scope,
|
|
15869
|
+
"urgent",
|
|
15870
|
+
title,
|
|
15871
|
+
detail
|
|
15872
|
+
);
|
|
15873
|
+
}
|
|
15874
|
+
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
15875
|
+
if (!preference?.events.includes("urgent")) return;
|
|
15876
|
+
const destination = preference.target ? this.deps.scopeDirectory.resolve(preference.target) ?? this.deps.scopeDirectory.resolveChat(preference.target) : this.deps.scopeDirectory.resolve(scope);
|
|
15877
|
+
if (!destination) return;
|
|
15878
|
+
try {
|
|
15879
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
15880
|
+
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
15881
|
+
...destination.messageId ? { replyTo: destination.messageId } : {}
|
|
15882
|
+
});
|
|
15883
|
+
} catch (error) {
|
|
15884
|
+
log.warn("notification", "urgent-send-failed", { scope, error });
|
|
15885
|
+
}
|
|
15521
15886
|
}
|
|
15522
15887
|
scheduleApprovalReminder(scope, toolName) {
|
|
15523
15888
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
@@ -15528,11 +15893,197 @@ var NotificationDispatcher = class {
|
|
|
15528
15893
|
timer.unref?.();
|
|
15529
15894
|
return () => clearTimeout(timer);
|
|
15530
15895
|
}
|
|
15896
|
+
async broadcastSinks(channelIds, scope, event, title, detail) {
|
|
15897
|
+
if (!this.deps.sinks || channelIds.length === 0) return;
|
|
15898
|
+
try {
|
|
15899
|
+
const outcome = await this.deps.sinks.broadcast(channelIds, {
|
|
15900
|
+
scope,
|
|
15901
|
+
event,
|
|
15902
|
+
title,
|
|
15903
|
+
...detail === void 0 ? {} : { detail }
|
|
15904
|
+
});
|
|
15905
|
+
if (outcome.failures.length > 0) {
|
|
15906
|
+
log.warn("notification", "sink-failures", { scope, event, failures: outcome.failures });
|
|
15907
|
+
}
|
|
15908
|
+
} catch (error) {
|
|
15909
|
+
log.warn("notification", "sink-broadcast-error", { scope, event, error });
|
|
15910
|
+
}
|
|
15911
|
+
}
|
|
15531
15912
|
};
|
|
15913
|
+
function titleForEvent(event) {
|
|
15914
|
+
switch (event) {
|
|
15915
|
+
case "completed":
|
|
15916
|
+
return { zh: "\u2705 \u4EFB\u52A1\u5DF2\u5B8C\u6210", en: "\u2705 Task completed" };
|
|
15917
|
+
case "failed":
|
|
15918
|
+
return { zh: "\u26A0\uFE0F \u4EFB\u52A1\u6267\u884C\u5931\u8D25", en: "\u26A0\uFE0F Task failed" };
|
|
15919
|
+
case "approval":
|
|
15920
|
+
return { zh: "\u23F3 \u4EFB\u52A1\u4ECD\u5728\u7B49\u5F85\u64CD\u4F5C\u5BA1\u6279", en: "\u23F3 Task is still waiting for tool approval" };
|
|
15921
|
+
case "urgent":
|
|
15922
|
+
return { zh: "\u{1F534} \u7A81\u53D1 / \u6545\u969C", en: "\u{1F534} Urgent / fault" };
|
|
15923
|
+
}
|
|
15924
|
+
}
|
|
15925
|
+
function renderFeishuBody(title, scope, detail) {
|
|
15926
|
+
return bilingualMarkdown(
|
|
15927
|
+
`${title.zh}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
15928
|
+
`${title.en} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
15929
|
+
);
|
|
15930
|
+
}
|
|
15532
15931
|
function clonePreference(preference) {
|
|
15533
|
-
return preference ? { ...preference, events: [...preference.events], mentionUserIds: [...preference.mentionUserIds] } : void 0;
|
|
15932
|
+
return preference ? { ...preference, events: [...preference.events], mentionUserIds: [...preference.mentionUserIds], sinks: [...preference.sinks] } : void 0;
|
|
15534
15933
|
}
|
|
15535
15934
|
|
|
15935
|
+
// src/notify/sinks/http.ts
|
|
15936
|
+
async function postJson(url, body3) {
|
|
15937
|
+
const controller = new AbortController();
|
|
15938
|
+
const timer = setTimeout(() => controller.abort(), SINK_HTTP_TIMEOUT_MS);
|
|
15939
|
+
timer.unref?.();
|
|
15940
|
+
try {
|
|
15941
|
+
return await fetch(url, {
|
|
15942
|
+
method: "POST",
|
|
15943
|
+
headers: { "content-type": "application/json" },
|
|
15944
|
+
body: body3,
|
|
15945
|
+
signal: controller.signal
|
|
15946
|
+
});
|
|
15947
|
+
} finally {
|
|
15948
|
+
clearTimeout(timer);
|
|
15949
|
+
}
|
|
15950
|
+
}
|
|
15951
|
+
|
|
15952
|
+
// src/notify/sinks/text.ts
|
|
15953
|
+
function renderSinkText(message) {
|
|
15954
|
+
const zhLine = `${message.title.zh}\uFF08scope \`${message.scope}\`\uFF09${message.detail ? `\uFF1A${message.detail}` : ""}`;
|
|
15955
|
+
const enLine = `${message.title.en} (scope \`${message.scope}\`)${message.detail ? `: ${message.detail}` : ""}`;
|
|
15956
|
+
return `${stripMarkdown(zhLine)}
|
|
15957
|
+
|
|
15958
|
+
${stripMarkdown(enLine)}`;
|
|
15959
|
+
}
|
|
15960
|
+
function escapeHtml(value) {
|
|
15961
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
15962
|
+
}
|
|
15963
|
+
function stripMarkdown(value) {
|
|
15964
|
+
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("*", "");
|
|
15965
|
+
}
|
|
15966
|
+
|
|
15967
|
+
// src/notify/sinks/telegram.ts
|
|
15968
|
+
var TelegramSink = class {
|
|
15969
|
+
constructor(baseUrl = "https://api.telegram.org") {
|
|
15970
|
+
this.baseUrl = baseUrl;
|
|
15971
|
+
}
|
|
15972
|
+
baseUrl;
|
|
15973
|
+
type = "telegram";
|
|
15974
|
+
async send(channel, message) {
|
|
15975
|
+
const token = channel.secret;
|
|
15976
|
+
if (!token) {
|
|
15977
|
+
log.warn("sink:telegram", "missing-token", { channel: channel.id });
|
|
15978
|
+
return false;
|
|
15979
|
+
}
|
|
15980
|
+
const url = `${this.baseUrl}/bot${encodeURIComponent(token)}/sendMessage`;
|
|
15981
|
+
const body3 = JSON.stringify({
|
|
15982
|
+
chat_id: channel.destination,
|
|
15983
|
+
text: escapeHtml(renderSinkText(message)),
|
|
15984
|
+
parse_mode: "HTML",
|
|
15985
|
+
disable_web_page_preview: true
|
|
15986
|
+
});
|
|
15987
|
+
try {
|
|
15988
|
+
const response = await postJson(url, body3);
|
|
15989
|
+
const payload = await response.json();
|
|
15990
|
+
if (!response.ok || payload.ok !== true) {
|
|
15991
|
+
log.warn("sink:telegram", "send-failed", {
|
|
15992
|
+
channel: channel.id,
|
|
15993
|
+
error: payload.description ?? `http ${response.status}`
|
|
15994
|
+
});
|
|
15995
|
+
return false;
|
|
15996
|
+
}
|
|
15997
|
+
return true;
|
|
15998
|
+
} catch (error) {
|
|
15999
|
+
log.warn("sink:telegram", "send-error", { channel: channel.id, error });
|
|
16000
|
+
return false;
|
|
16001
|
+
}
|
|
16002
|
+
}
|
|
16003
|
+
};
|
|
16004
|
+
|
|
16005
|
+
// src/notify/sinks/wecom.ts
|
|
16006
|
+
var WeComSink = class {
|
|
16007
|
+
constructor(baseUrl = "https://qyapi.weixin.qq.com") {
|
|
16008
|
+
this.baseUrl = baseUrl;
|
|
16009
|
+
}
|
|
16010
|
+
baseUrl;
|
|
16011
|
+
type = "wecom";
|
|
16012
|
+
async send(channel, message) {
|
|
16013
|
+
const key = channel.destination;
|
|
16014
|
+
if (!key) {
|
|
16015
|
+
log.warn("sink:wecom", "missing-key", { channel: channel.id });
|
|
16016
|
+
return false;
|
|
16017
|
+
}
|
|
16018
|
+
const url = `${this.baseUrl}/cgi-bin/webhook/send?key=${encodeURIComponent(key)}`;
|
|
16019
|
+
const body3 = JSON.stringify({
|
|
16020
|
+
msgtype: "text",
|
|
16021
|
+
text: { content: renderSinkText(message) }
|
|
16022
|
+
});
|
|
16023
|
+
try {
|
|
16024
|
+
const response = await postJson(url, body3);
|
|
16025
|
+
const payload = await response.json();
|
|
16026
|
+
if (!response.ok || payload.errcode !== 0) {
|
|
16027
|
+
log.warn("sink:wecom", "send-failed", {
|
|
16028
|
+
channel: channel.id,
|
|
16029
|
+
error: payload.errmsg ?? `http ${response.status}`
|
|
16030
|
+
});
|
|
16031
|
+
return false;
|
|
16032
|
+
}
|
|
16033
|
+
return true;
|
|
16034
|
+
} catch (error) {
|
|
16035
|
+
log.warn("sink:wecom", "send-error", { channel: channel.id, error });
|
|
16036
|
+
return false;
|
|
16037
|
+
}
|
|
16038
|
+
}
|
|
16039
|
+
};
|
|
16040
|
+
|
|
16041
|
+
// src/notify/sinks/registry.ts
|
|
16042
|
+
var OutboundSinkRegistry = class {
|
|
16043
|
+
constructor(store, sinks = [new TelegramSink(), new WeComSink()]) {
|
|
16044
|
+
this.store = store;
|
|
16045
|
+
this.sinks = sinks;
|
|
16046
|
+
}
|
|
16047
|
+
store;
|
|
16048
|
+
sinks;
|
|
16049
|
+
/** Resolve only the enabled channels referenced by `channelIds`. */
|
|
16050
|
+
channelsForIds(channelIds) {
|
|
16051
|
+
const seen = new Set(channelIds);
|
|
16052
|
+
return this.store.list().filter((channel) => channel.enabled && seen.has(channel.id));
|
|
16053
|
+
}
|
|
16054
|
+
/** Channels currently enabled (for `/status` and `/channels`). */
|
|
16055
|
+
enabledChannels() {
|
|
16056
|
+
return this.store.list().filter((channel) => channel.enabled);
|
|
16057
|
+
}
|
|
16058
|
+
/** Best-effort fan-out; a failing channel never blocks the others. */
|
|
16059
|
+
async broadcast(channelIds, message) {
|
|
16060
|
+
const channelIdsToSend = [...new Set(channelIds)];
|
|
16061
|
+
const channels = this.channelsForIds(channelIdsToSend);
|
|
16062
|
+
const failures = [];
|
|
16063
|
+
let delivered = 0;
|
|
16064
|
+
for (const channel of channels) {
|
|
16065
|
+
const sink = this.sinkFor(channel.type);
|
|
16066
|
+
if (!sink) {
|
|
16067
|
+
log.warn("sinks", "unsupported-type", { channel: channel.id, type: channel.type });
|
|
16068
|
+
failures.push(channel.id);
|
|
16069
|
+
continue;
|
|
16070
|
+
}
|
|
16071
|
+
let ok = false;
|
|
16072
|
+
try {
|
|
16073
|
+
ok = await sink.send(channel, message);
|
|
16074
|
+
} catch (error) {
|
|
16075
|
+
log.warn("sinks", "channel-send-error", { channel: channel.id, error });
|
|
16076
|
+
}
|
|
16077
|
+
if (ok) delivered += 1;
|
|
16078
|
+
else failures.push(channel.id);
|
|
16079
|
+
}
|
|
16080
|
+
return { delivered, failures, total: channels.length };
|
|
16081
|
+
}
|
|
16082
|
+
sinkFor(type) {
|
|
16083
|
+
return this.sinks.find((sink) => sink.type === type);
|
|
16084
|
+
}
|
|
16085
|
+
};
|
|
16086
|
+
|
|
15536
16087
|
// src/bridge/reply-dispatcher.ts
|
|
15537
16088
|
var ReplyDispatcher = class {
|
|
15538
16089
|
constructor(deps) {
|
|
@@ -15591,8 +16142,8 @@ var ReplyDispatcher = class {
|
|
|
15591
16142
|
}
|
|
15592
16143
|
}
|
|
15593
16144
|
};
|
|
15594
|
-
function replyRoute(
|
|
15595
|
-
return `${
|
|
16145
|
+
function replyRoute(reply9) {
|
|
16146
|
+
return `${reply9.chatId}\0${reply9.options?.threadId ?? ""}`;
|
|
15596
16147
|
}
|
|
15597
16148
|
function mergedMarkdown(batch) {
|
|
15598
16149
|
if (batch.length === 1) return batch[0].markdown;
|
|
@@ -15727,7 +16278,7 @@ async function onboardPersonalAgent(deps = {}) {
|
|
|
15727
16278
|
}
|
|
15728
16279
|
|
|
15729
16280
|
// src/media/attachments.ts
|
|
15730
|
-
import { mkdir as mkdir16, readFile as
|
|
16281
|
+
import { mkdir as mkdir16, readFile as readFile24, rename as rename3, rm as rm9, stat as stat9 } from "fs/promises";
|
|
15731
16282
|
import { join as join23 } from "path";
|
|
15732
16283
|
var MAX_TEXT_FILE_BYTES = 256e3;
|
|
15733
16284
|
function assertSafeMediaName(mediaDir, destination) {
|
|
@@ -15768,7 +16319,7 @@ async function prepareAttachments(channel, message, mediaDir) {
|
|
|
15768
16319
|
result.textFileNotes.push(`[attachment: ${resource.fileName ?? resource.fileKey}] ${destination}`);
|
|
15769
16320
|
continue;
|
|
15770
16321
|
}
|
|
15771
|
-
const content = await
|
|
16322
|
+
const content = await readFile24(destination, "utf8");
|
|
15772
16323
|
const safeContent = truncateUtf8Safe(content, MAX_TEXT_FILE_BYTES);
|
|
15773
16324
|
result.textFileNotes.push(
|
|
15774
16325
|
`[attachment: ${resource.fileName ?? resource.fileKey}]
|
|
@@ -15779,7 +16330,7 @@ ${safeContent}`
|
|
|
15779
16330
|
}
|
|
15780
16331
|
|
|
15781
16332
|
// src/session/store.ts
|
|
15782
|
-
import { readFile as
|
|
16333
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
15783
16334
|
var MAX_CONTEXT_SNAPSHOTS_PER_WORKSPACE = 32;
|
|
15784
16335
|
var SessionStore = class {
|
|
15785
16336
|
constructor(path) {
|
|
@@ -15794,7 +16345,7 @@ var SessionStore = class {
|
|
|
15794
16345
|
pendingArchive = Promise.resolve();
|
|
15795
16346
|
async load() {
|
|
15796
16347
|
try {
|
|
15797
|
-
const raw = JSON.parse(await
|
|
16348
|
+
const raw = JSON.parse(await readFile25(this.path, "utf8"));
|
|
15798
16349
|
const legacy = raw.schema !== 2 || !isObject(raw.workspaces);
|
|
15799
16350
|
this.data = legacy ? migrateLegacy(raw) : normalizeV2(raw.workspaces);
|
|
15800
16351
|
this.legacyScopes = legacy ? new Set(Object.keys(this.data.workspaces)) : /* @__PURE__ */ new Set();
|
|
@@ -15891,13 +16442,13 @@ var SessionStore = class {
|
|
|
15891
16442
|
fullHistoryFor(scopeId, cwd) {
|
|
15892
16443
|
return this.historyFor(scopeId, cwd);
|
|
15893
16444
|
}
|
|
15894
|
-
recordUsage(scopeId, cwd,
|
|
16445
|
+
recordUsage(scopeId, cwd, usage4) {
|
|
15895
16446
|
const state = this.ensureState(scopeId, cwd);
|
|
15896
16447
|
const next = { ...state.metrics };
|
|
15897
|
-
addMetric(next, "inputTokens",
|
|
15898
|
-
addMetric(next, "outputTokens",
|
|
15899
|
-
addMetric(next, "cacheReadTokens",
|
|
15900
|
-
addMetric(next, "cacheWriteTokens",
|
|
16448
|
+
addMetric(next, "inputTokens", usage4.inputTokens);
|
|
16449
|
+
addMetric(next, "outputTokens", usage4.outputTokens);
|
|
16450
|
+
addMetric(next, "cacheReadTokens", usage4.cacheReadTokens);
|
|
16451
|
+
addMetric(next, "cacheWriteTokens", usage4.cacheWriteTokens);
|
|
15901
16452
|
state.metrics = next;
|
|
15902
16453
|
this.schedulePersist();
|
|
15903
16454
|
}
|
|
@@ -16113,7 +16664,7 @@ function normalizeContextSnapshot(value) {
|
|
|
16113
16664
|
}
|
|
16114
16665
|
|
|
16115
16666
|
// src/session/projection-store.ts
|
|
16116
|
-
import { readFile as
|
|
16667
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
16117
16668
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
16118
16669
|
var MAX_RECENT_MESSAGES = 64;
|
|
16119
16670
|
var MAX_PROMPT_CORRELATIONS = 64;
|
|
@@ -16127,7 +16678,7 @@ var SessionProjectionStore = class {
|
|
|
16127
16678
|
saving = Promise.resolve();
|
|
16128
16679
|
async load() {
|
|
16129
16680
|
try {
|
|
16130
|
-
const parsed = JSON.parse(await
|
|
16681
|
+
const parsed = JSON.parse(await readFile26(this.path, "utf8"));
|
|
16131
16682
|
const bindings = Object.fromEntries(
|
|
16132
16683
|
Object.values(parsed.bindings ?? {}).flatMap((value) => {
|
|
16133
16684
|
const normalized = normalizeBinding(value);
|
|
@@ -16339,7 +16890,7 @@ function normalizeCorrelation(value) {
|
|
|
16339
16890
|
// src/session/archive.ts
|
|
16340
16891
|
import { execFile as execFile2 } from "child_process";
|
|
16341
16892
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
16342
|
-
import { access, mkdir as mkdir17, readdir as readdir3, readFile as
|
|
16893
|
+
import { access, mkdir as mkdir17, readdir as readdir3, readFile as readFile27, unlink, writeFile as writeFile8 } from "fs/promises";
|
|
16343
16894
|
import { join as join24 } from "path";
|
|
16344
16895
|
import { promisify } from "util";
|
|
16345
16896
|
var execFileAsync = promisify(execFile2);
|
|
@@ -16521,8 +17072,8 @@ var SessionArchive = class {
|
|
|
16521
17072
|
const jsonlPath = join24(scopeDir, `${id}.jsonl`);
|
|
16522
17073
|
const markdownPath = join24(scopeDir, `${id}.md`);
|
|
16523
17074
|
const [jsonl, markdown2] = await Promise.all([
|
|
16524
|
-
|
|
16525
|
-
|
|
17075
|
+
readFile27(jsonlPath, "utf8"),
|
|
17076
|
+
readFile27(markdownPath, "utf8")
|
|
16526
17077
|
]);
|
|
16527
17078
|
const [first = "", ...rest] = jsonl.split("\n");
|
|
16528
17079
|
const header = JSON.parse(first);
|
|
@@ -16637,7 +17188,7 @@ async function defaultRunGit(args, cwd) {
|
|
|
16637
17188
|
}
|
|
16638
17189
|
async function readArchiveHeader(jsonlPath) {
|
|
16639
17190
|
try {
|
|
16640
|
-
const raw = await
|
|
17191
|
+
const raw = await readFile27(jsonlPath, "utf8");
|
|
16641
17192
|
const firstLine = raw.split("\n", 1)[0];
|
|
16642
17193
|
if (!firstLine) return void 0;
|
|
16643
17194
|
const header = JSON.parse(firstLine);
|
|
@@ -16665,7 +17216,7 @@ async function safeRemove(path) {
|
|
|
16665
17216
|
import { execFile as execFile3 } from "child_process";
|
|
16666
17217
|
import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
|
|
16667
17218
|
import { access as access2, copyFile, mkdir as mkdir18, realpath as realpath2 } from "fs/promises";
|
|
16668
|
-
import { dirname as
|
|
17219
|
+
import { dirname as dirname16, join as join25 } from "path";
|
|
16669
17220
|
import { promisify as promisify2 } from "util";
|
|
16670
17221
|
var execFileAsync2 = promisify2(execFile3);
|
|
16671
17222
|
async function defaultRunGit2(args, cwd) {
|
|
@@ -16684,10 +17235,10 @@ async function exists2(path) {
|
|
|
16684
17235
|
}
|
|
16685
17236
|
}
|
|
16686
17237
|
async function defaultCopyRulesFile(source, target) {
|
|
16687
|
-
await mkdir18(
|
|
17238
|
+
await mkdir18(dirname16(target), { recursive: true });
|
|
16688
17239
|
await copyFile(source, target);
|
|
16689
17240
|
}
|
|
16690
|
-
function
|
|
17241
|
+
function slugify3(scope) {
|
|
16691
17242
|
const slug = scope.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.]+/, "").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
16692
17243
|
return slug || "session";
|
|
16693
17244
|
}
|
|
@@ -16703,7 +17254,7 @@ var GitWorktreeManager = class {
|
|
|
16703
17254
|
async ensure(scope, base) {
|
|
16704
17255
|
const isGit = await this.isGitRepository(base);
|
|
16705
17256
|
if (!isGit) return { cwd: base, created: false };
|
|
16706
|
-
const scopeSlug =
|
|
17257
|
+
const scopeSlug = slugify3(scope);
|
|
16707
17258
|
const project = createHash2("sha256").update(base).digest("hex").slice(0, 10);
|
|
16708
17259
|
const slug = `${scopeSlug}-${project}`;
|
|
16709
17260
|
const target = join25(this.options.worktreesRoot, slug);
|
|
@@ -16740,7 +17291,7 @@ var GitWorktreeManager = class {
|
|
|
16740
17291
|
}
|
|
16741
17292
|
/** Owning main worktree for the legacy scope-only linked worktree. */
|
|
16742
17293
|
async legacyWorkspaceBase(scope) {
|
|
16743
|
-
const legacyTarget = join25(this.options.worktreesRoot,
|
|
17294
|
+
const legacyTarget = join25(this.options.worktreesRoot, slugify3(scope));
|
|
16744
17295
|
if (!await exists2(legacyTarget)) return void 0;
|
|
16745
17296
|
try {
|
|
16746
17297
|
const output = await this.runGit(["worktree", "list", "--porcelain"], legacyTarget);
|
|
@@ -16772,7 +17323,7 @@ async function samePath(left, right) {
|
|
|
16772
17323
|
}
|
|
16773
17324
|
|
|
16774
17325
|
// src/workspace/store.ts
|
|
16775
|
-
import { readFile as
|
|
17326
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
16776
17327
|
var WorkspaceStore = class {
|
|
16777
17328
|
data = { chats: {}, named: {}, lastUsed: {} };
|
|
16778
17329
|
saving = Promise.resolve();
|
|
@@ -16782,7 +17333,7 @@ var WorkspaceStore = class {
|
|
|
16782
17333
|
}
|
|
16783
17334
|
async load() {
|
|
16784
17335
|
try {
|
|
16785
|
-
const raw = await
|
|
17336
|
+
const raw = await readFile28(this.path, "utf8");
|
|
16786
17337
|
const parsed = JSON.parse(raw);
|
|
16787
17338
|
this.data = {
|
|
16788
17339
|
chats: parsed.chats ?? {},
|
|
@@ -16901,8 +17452,8 @@ var UpdateNotifier = class {
|
|
|
16901
17452
|
};
|
|
16902
17453
|
|
|
16903
17454
|
// src/bot/fleet-store.ts
|
|
16904
|
-
import { mkdir as mkdir19, readFile as
|
|
16905
|
-
import { dirname as
|
|
17455
|
+
import { mkdir as mkdir19, readFile as readFile29 } from "fs/promises";
|
|
17456
|
+
import { dirname as dirname17 } from "path";
|
|
16906
17457
|
function validBotInstanceName(name) {
|
|
16907
17458
|
return /^[a-z][a-z0-9-]{0,31}$/u.test(name);
|
|
16908
17459
|
}
|
|
@@ -16914,7 +17465,7 @@ var BotFleetStore = class {
|
|
|
16914
17465
|
data = { schemaVersion: 1, bots: {} };
|
|
16915
17466
|
async load() {
|
|
16916
17467
|
try {
|
|
16917
|
-
const parsed = JSON.parse(await
|
|
17468
|
+
const parsed = JSON.parse(await readFile29(this.path, "utf8"));
|
|
16918
17469
|
this.data = {
|
|
16919
17470
|
schemaVersion: 1,
|
|
16920
17471
|
bots: Object.fromEntries(
|
|
@@ -17023,7 +17574,7 @@ var BotFleetStore = class {
|
|
|
17023
17574
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
17024
17575
|
}
|
|
17025
17576
|
async persist() {
|
|
17026
|
-
await mkdir19(
|
|
17577
|
+
await mkdir19(dirname17(this.path), { recursive: true });
|
|
17027
17578
|
await writeFileAtomic(this.path, `${JSON.stringify(this.data, null, 2)}
|
|
17028
17579
|
`, { mode: 384 });
|
|
17029
17580
|
}
|
|
@@ -17042,8 +17593,8 @@ function isFleetEntry(value) {
|
|
|
17042
17593
|
}
|
|
17043
17594
|
|
|
17044
17595
|
// src/bot/handoff-guard.ts
|
|
17045
|
-
import { mkdir as mkdir20, readFile as
|
|
17046
|
-
import { dirname as
|
|
17596
|
+
import { mkdir as mkdir20, readFile as readFile30 } from "fs/promises";
|
|
17597
|
+
import { dirname as dirname18 } from "path";
|
|
17047
17598
|
var BotHandoffGuard = class {
|
|
17048
17599
|
constructor(path) {
|
|
17049
17600
|
this.path = path;
|
|
@@ -17083,7 +17634,7 @@ var BotHandoffGuard = class {
|
|
|
17083
17634
|
}
|
|
17084
17635
|
async read() {
|
|
17085
17636
|
try {
|
|
17086
|
-
const parsed = JSON.parse(await
|
|
17637
|
+
const parsed = JSON.parse(await readFile30(this.path, "utf8"));
|
|
17087
17638
|
return { schemaVersion: 1, chats: parsed.chats ?? {} };
|
|
17088
17639
|
} catch (error) {
|
|
17089
17640
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -17091,7 +17642,7 @@ var BotHandoffGuard = class {
|
|
|
17091
17642
|
}
|
|
17092
17643
|
}
|
|
17093
17644
|
async write(data) {
|
|
17094
|
-
await mkdir20(
|
|
17645
|
+
await mkdir20(dirname18(this.path), { recursive: true });
|
|
17095
17646
|
await writeFileAtomic(this.path, `${JSON.stringify(data, null, 2)}
|
|
17096
17647
|
`, { mode: 384 });
|
|
17097
17648
|
}
|
|
@@ -17109,7 +17660,7 @@ init_dsh_runtime();
|
|
|
17109
17660
|
import { homedir as homedir16 } from "os";
|
|
17110
17661
|
|
|
17111
17662
|
// src/bot/job-ledger.ts
|
|
17112
|
-
import { readFile as
|
|
17663
|
+
import { readFile as readFile31, rename as rename4 } from "fs/promises";
|
|
17113
17664
|
var EMPTY_COUNTS = {
|
|
17114
17665
|
queued: 0,
|
|
17115
17666
|
running: 0,
|
|
@@ -17132,7 +17683,7 @@ var JobLedger = class {
|
|
|
17132
17683
|
if (this.path === ":memory:") return;
|
|
17133
17684
|
let raw;
|
|
17134
17685
|
try {
|
|
17135
|
-
raw = await
|
|
17686
|
+
raw = await readFile31(this.path, "utf8");
|
|
17136
17687
|
} catch (error) {
|
|
17137
17688
|
if (error.code === "ENOENT") return;
|
|
17138
17689
|
throw error;
|
|
@@ -17873,8 +18424,8 @@ init_own_package();
|
|
|
17873
18424
|
// src/guardian/update-handoff.ts
|
|
17874
18425
|
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
17875
18426
|
import { spawn as spawn7 } from "child_process";
|
|
17876
|
-
import { chmod, mkdir as mkdir21, readFile as
|
|
17877
|
-
import { dirname as
|
|
18427
|
+
import { chmod, mkdir as mkdir21, readFile as readFile32, rm as rm10 } from "fs/promises";
|
|
18428
|
+
import { dirname as dirname19, join as join26 } from "path";
|
|
17878
18429
|
init_own_package();
|
|
17879
18430
|
init_process();
|
|
17880
18431
|
function guardianUpdateFailureHint(code) {
|
|
@@ -17903,7 +18454,7 @@ function guardianUpdateFailureHint(code) {
|
|
|
17903
18454
|
}
|
|
17904
18455
|
async function loadState(file) {
|
|
17905
18456
|
try {
|
|
17906
|
-
const parsed = JSON.parse(await
|
|
18457
|
+
const parsed = JSON.parse(await readFile32(file, "utf8"));
|
|
17907
18458
|
return parsed.schemaVersion === 1 && typeof parsed.id === "string" ? parsed : void 0;
|
|
17908
18459
|
} catch (error) {
|
|
17909
18460
|
if (error.code === "ENOENT") return void 0;
|
|
@@ -17911,7 +18462,7 @@ async function loadState(file) {
|
|
|
17911
18462
|
}
|
|
17912
18463
|
}
|
|
17913
18464
|
async function saveState(file, state) {
|
|
17914
|
-
await mkdir21(
|
|
18465
|
+
await mkdir21(dirname19(file), { recursive: true });
|
|
17915
18466
|
await writeFileAtomic(file, `${JSON.stringify(state, null, 2)}
|
|
17916
18467
|
`, { mode: 384 });
|
|
17917
18468
|
}
|
|
@@ -17986,7 +18537,7 @@ async function runGuardianUpdateWorker(request, options = {}) {
|
|
|
17986
18537
|
if (delayMs > 0) await new Promise((resolve6) => setTimeout(resolve6, delayMs));
|
|
17987
18538
|
const spec = `${state.packageName}@${state.targetVersion}`;
|
|
17988
18539
|
const run = options.run ?? captureOutput;
|
|
17989
|
-
const workerRoot = join26(
|
|
18540
|
+
const workerRoot = join26(dirname19(request.stateFile), "update-worker");
|
|
17990
18541
|
const cacheRoot = join26(workerRoot, "npm-cache");
|
|
17991
18542
|
const cacheDir = join26(
|
|
17992
18543
|
cacheRoot,
|
|
@@ -18299,6 +18850,8 @@ async function startBridgeEngine(options) {
|
|
|
18299
18850
|
const isolationStore = new IsolationStore(paths.profilePath(profileName, "isolation.json"));
|
|
18300
18851
|
const permissionPolicies = new PermissionPolicyStore(paths.permissionPoliciesFile(profileName));
|
|
18301
18852
|
const notificationPreferences = new NotificationPreferenceStore(paths.notificationPreferencesFile(profileName));
|
|
18853
|
+
const notificationChannels = new NotificationChannelStore(paths.notificationChannelsFile(profileName));
|
|
18854
|
+
const notificationSinks = new OutboundSinkRegistry(notificationChannels);
|
|
18302
18855
|
const replyPolicies = new ReplyPolicyStore(paths.replyPoliciesFile(profileName));
|
|
18303
18856
|
const executionModes = new ExecutionModeStore(paths.executionModesFile(profileName));
|
|
18304
18857
|
const languagePolicies = new LanguagePolicyStore(paths.languagePoliciesFile(profileName));
|
|
@@ -18321,6 +18874,7 @@ async function startBridgeEngine(options) {
|
|
|
18321
18874
|
isolationStore.load(),
|
|
18322
18875
|
permissionPolicies.load(),
|
|
18323
18876
|
notificationPreferences.load(),
|
|
18877
|
+
notificationChannels.load(),
|
|
18324
18878
|
replyPolicies.load(),
|
|
18325
18879
|
executionModes.load(),
|
|
18326
18880
|
languagePolicies.load()
|
|
@@ -18390,7 +18944,8 @@ async function startBridgeEngine(options) {
|
|
|
18390
18944
|
send: async (chatId, markdown2, options2) => {
|
|
18391
18945
|
if (!streaming) throw new Error("bridge channel is not ready");
|
|
18392
18946
|
await streaming.sendMarkdown(chatId, markdown2, options2);
|
|
18393
|
-
}
|
|
18947
|
+
},
|
|
18948
|
+
sinks: notificationSinks
|
|
18394
18949
|
});
|
|
18395
18950
|
const replyDispatcher = new ReplyDispatcher({
|
|
18396
18951
|
policies: replyPolicies,
|
|
@@ -18681,6 +19236,9 @@ async function startBridgeEngine(options) {
|
|
|
18681
19236
|
approvals,
|
|
18682
19237
|
permissionPolicies,
|
|
18683
19238
|
notificationPreferences,
|
|
19239
|
+
notificationChannels,
|
|
19240
|
+
notificationSinks,
|
|
19241
|
+
faultNotifier: (scope, title, detail) => notificationDispatcher.notifyUrgent(scope, title, detail),
|
|
18684
19242
|
...defaultNotificationPreference ? { defaultNotificationPreference: { ...defaultNotificationPreference, events: [...defaultNotificationPreference.events] } } : {},
|
|
18685
19243
|
replyPolicies,
|
|
18686
19244
|
executionModes,
|
|
@@ -18943,6 +19501,7 @@ function notificationPreferenceFor(value) {
|
|
|
18943
19501
|
return {
|
|
18944
19502
|
events: value === "all" ? ["completed", "failed", "approval"] : ["completed", "failed"],
|
|
18945
19503
|
mentionUserIds: [],
|
|
19504
|
+
sinks: [],
|
|
18946
19505
|
approvalReminderMs: 10 * 6e4
|
|
18947
19506
|
};
|
|
18948
19507
|
}
|
|
@@ -18978,7 +19537,7 @@ function waitForShutdown() {
|
|
|
18978
19537
|
init_dsh_runtime();
|
|
18979
19538
|
init_own_package();
|
|
18980
19539
|
import { spawn as spawn8 } from "child_process";
|
|
18981
|
-
import { mkdir as mkdir23, readFile as
|
|
19540
|
+
import { mkdir as mkdir23, readFile as readFile33, writeFile as writeFile9 } from "fs/promises";
|
|
18982
19541
|
import { homedir as homedir17 } from "os";
|
|
18983
19542
|
import { join as join28 } from "path";
|
|
18984
19543
|
import { parse } from "yaml";
|
|
@@ -19059,7 +19618,7 @@ async function approveBuilds(profileDir) {
|
|
|
19059
19618
|
const workspaceFile = join28(profileDir, "pnpm-workspace.yaml");
|
|
19060
19619
|
let existing = "";
|
|
19061
19620
|
try {
|
|
19062
|
-
existing = await
|
|
19621
|
+
existing = await readFile33(workspaceFile, "utf8");
|
|
19063
19622
|
} catch {
|
|
19064
19623
|
}
|
|
19065
19624
|
const allowBuilds = [
|
|
@@ -19087,8 +19646,8 @@ async function preserveInstalledPnpmVersion(profileDir) {
|
|
|
19087
19646
|
let modulesText;
|
|
19088
19647
|
try {
|
|
19089
19648
|
[packageText, modulesText] = await Promise.all([
|
|
19090
|
-
|
|
19091
|
-
|
|
19649
|
+
readFile33(packageFile, "utf8"),
|
|
19650
|
+
readFile33(modulesFile, "utf8")
|
|
19092
19651
|
]);
|
|
19093
19652
|
} catch {
|
|
19094
19653
|
return;
|
|
@@ -21166,7 +21725,7 @@ function followLogFile(path, lines) {
|
|
|
21166
21725
|
// src/cli/commands/supervise.ts
|
|
21167
21726
|
import { spawn as spawn10 } from "child_process";
|
|
21168
21727
|
import { closeSync, mkdirSync, openSync } from "fs";
|
|
21169
|
-
import { dirname as
|
|
21728
|
+
import { dirname as dirname20 } from "path";
|
|
21170
21729
|
import { homedir as homedir22 } from "os";
|
|
21171
21730
|
init_dsh_runtime();
|
|
21172
21731
|
var BASE_BACKOFF_MS = 5e3;
|
|
@@ -21200,8 +21759,8 @@ async function runSupervise(options, deps = {}) {
|
|
|
21200
21759
|
const logFile = paths.serviceLogFile(profile);
|
|
21201
21760
|
const dshBin = deps.dshBin ?? discoverDshBin(homedir22(), childEnv);
|
|
21202
21761
|
if (!dshBin) throw new Error("\u672A\u627E\u5230 dsh CLI\uFF0Cportable supervisor \u65E0\u6CD5\u542F\u52A8 profile\u3002");
|
|
21203
|
-
mkdirSync(
|
|
21204
|
-
mkdirSync(
|
|
21762
|
+
mkdirSync(dirname20(statusFile), { recursive: true });
|
|
21763
|
+
mkdirSync(dirname20(logFile), { recursive: true });
|
|
21205
21764
|
const logFd = openSync(logFile, "a");
|
|
21206
21765
|
const writeStatus = async (state, childPid, restarts2) => {
|
|
21207
21766
|
const processIdentity = await (deps.readProcessIdentity ?? readLinuxProcessIdentity)(process.pid);
|