dsh-lark-bot 0.19.12 → 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 +687 -188
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +120 -1
- package/dist/plugin.js +706 -207
- 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
|
@@ -825,14 +825,14 @@ var init_acp_adapter = __esm({
|
|
|
825
825
|
return;
|
|
826
826
|
}
|
|
827
827
|
if (response.usage) {
|
|
828
|
-
const
|
|
828
|
+
const usage4 = {
|
|
829
829
|
type: "usage",
|
|
830
830
|
...typeof response.usage.inputTokens === "number" ? { inputTokens: response.usage.inputTokens } : {},
|
|
831
831
|
...typeof response.usage.outputTokens === "number" ? { outputTokens: response.usage.outputTokens } : {},
|
|
832
832
|
...typeof response.usage.cachedReadTokens === "number" ? { cacheReadTokens: response.usage.cachedReadTokens } : {},
|
|
833
833
|
...typeof response.usage.cachedWriteTokens === "number" ? { cacheWriteTokens: response.usage.cachedWriteTokens } : {}
|
|
834
834
|
};
|
|
835
|
-
channel.push(
|
|
835
|
+
channel.push(usage4);
|
|
836
836
|
}
|
|
837
837
|
})().catch((error) => {
|
|
838
838
|
if (!stopRequested.value) {
|
|
@@ -2502,11 +2502,11 @@ function translateToolResult(data) {
|
|
|
2502
2502
|
}
|
|
2503
2503
|
function translateAssistantMessage(data) {
|
|
2504
2504
|
if (!isRecord3(data) || !isRecord3(data.usage)) return [];
|
|
2505
|
-
const
|
|
2506
|
-
const inputTokens = typeof
|
|
2507
|
-
const outputTokens = typeof
|
|
2508
|
-
const cacheReadTokens = typeof
|
|
2509
|
-
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;
|
|
2510
2510
|
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadTokens === void 0 && cacheWriteTokens === void 0) return [];
|
|
2511
2511
|
return [
|
|
2512
2512
|
{
|
|
@@ -3682,6 +3682,7 @@ function resolveAppPaths(root = defaultHome()) {
|
|
|
3682
3682
|
jobsFile: (profile) => profilePath(profile, "jobs.json"),
|
|
3683
3683
|
permissionPoliciesFile: (profile) => profilePath(profile, "permission-policies.json"),
|
|
3684
3684
|
notificationPreferencesFile: (profile) => profilePath(profile, "notification-preferences.json"),
|
|
3685
|
+
notificationChannelsFile: (profile) => profilePath(profile, "notification-channels.json"),
|
|
3685
3686
|
replyPoliciesFile: (profile) => profilePath(profile, "reply-policies.json"),
|
|
3686
3687
|
executionModesFile: (profile) => profilePath(profile, "execution-modes.json"),
|
|
3687
3688
|
languagePoliciesFile: (profile) => profilePath(profile, "language-policy.json"),
|
|
@@ -6910,7 +6911,7 @@ var PermissionPolicyStore = class {
|
|
|
6910
6911
|
|
|
6911
6912
|
// src/bot/notification-preference-store.ts
|
|
6912
6913
|
import { readFile as readFile19 } from "fs/promises";
|
|
6913
|
-
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval"]);
|
|
6914
|
+
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval", "urgent"]);
|
|
6914
6915
|
var NotificationPreferenceStore = class {
|
|
6915
6916
|
constructor(path) {
|
|
6916
6917
|
this.path = path;
|
|
@@ -6939,7 +6940,7 @@ var NotificationPreferenceStore = class {
|
|
|
6939
6940
|
get(scope) {
|
|
6940
6941
|
const value = this.data.scopes[scope];
|
|
6941
6942
|
if (value === void 0 || value === false) return void 0;
|
|
6942
|
-
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds] };
|
|
6943
|
+
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds], sinks: [...value.sinks] };
|
|
6943
6944
|
}
|
|
6944
6945
|
resolve(scope, fallback) {
|
|
6945
6946
|
const value = this.data.scopes[scope];
|
|
@@ -6984,6 +6985,7 @@ function normalizePreference(value) {
|
|
|
6984
6985
|
...typeof input.target === "string" && input.target.trim() ? { target: input.target.trim() } : {},
|
|
6985
6986
|
events,
|
|
6986
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()))],
|
|
6987
6989
|
approvalReminderMs
|
|
6988
6990
|
};
|
|
6989
6991
|
}
|
|
@@ -7460,6 +7462,7 @@ function statusCardMarkdownFor(input, locale) {
|
|
|
7460
7462
|
zh ? `\u{1F39A}\uFE0F **\u6267\u884C\u6A21\u5F0F**\uFF1A\`${input.executionMode ?? "balanced"}\`` : `\u{1F39A}\uFE0F **Execution mode**: \`${input.executionMode ?? "balanced"}\``,
|
|
7461
7463
|
zh ? `\u{1F6E1}\uFE0F **\u5DE5\u5177\u6743\u9650**\uFF1A\`${input.permissionPolicy ?? "ask"}\`` : `\u{1F6E1}\uFE0F **Tool permission**: \`${input.permissionPolicy ?? "ask"}\``,
|
|
7462
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(",")}\``] : [],
|
|
7463
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`] : [],
|
|
7464
7467
|
...input.role ? [`\u{1F3AD} **role**\uFF1A${input.role}`] : [],
|
|
7465
7468
|
`\u{1F516} **version**\uFF1A\`${input.version}\``,
|
|
@@ -7876,8 +7879,8 @@ function renderStandard(state, now, locale, maxTools) {
|
|
|
7876
7879
|
} else if (state.terminal === "done" && !hasAnswer(state)) {
|
|
7877
7880
|
elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
7878
7881
|
} else if (state.terminal === "done") {
|
|
7879
|
-
const
|
|
7880
|
-
if (
|
|
7882
|
+
const usage4 = usageLine(state);
|
|
7883
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
7881
7884
|
}
|
|
7882
7885
|
if (state.finalDeliveryError) {
|
|
7883
7886
|
elements.push(finalDeliveryFailureLine(locale));
|
|
@@ -7943,8 +7946,8 @@ function renderDetailed(state, now, locale, maxTools) {
|
|
|
7943
7946
|
} else if (state.terminal === "error") {
|
|
7944
7947
|
elements.push(runFailureLine(locale));
|
|
7945
7948
|
} else if (state.terminal === "done") {
|
|
7946
|
-
const
|
|
7947
|
-
if (
|
|
7949
|
+
const usage4 = usageLine(state);
|
|
7950
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
7948
7951
|
if (!hasAnswer(state)) elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
7949
7952
|
}
|
|
7950
7953
|
if (state.finalDeliveryError) {
|
|
@@ -10967,7 +10970,7 @@ async function handleNotify(args, ctx) {
|
|
|
10967
10970
|
}
|
|
10968
10971
|
|
|
10969
10972
|
// src/commands/notifications.ts
|
|
10970
|
-
var ALL_EVENTS = ["completed", "failed", "approval"];
|
|
10973
|
+
var ALL_EVENTS = ["completed", "failed", "approval", "urgent"];
|
|
10971
10974
|
async function reply5(ctx, zh, en) {
|
|
10972
10975
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
10973
10976
|
}
|
|
@@ -11005,6 +11008,7 @@ async function handleNotifications(args, ctx) {
|
|
|
11005
11008
|
let events = [...ALL_EVENTS];
|
|
11006
11009
|
let mentionUserIds = ctx.senderId ? [ctx.senderId] : [];
|
|
11007
11010
|
let reminderMinutes = 10;
|
|
11011
|
+
let sinks = [];
|
|
11008
11012
|
for (const token of tokens.slice(1)) {
|
|
11009
11013
|
if (token.startsWith("events=")) {
|
|
11010
11014
|
const requested = token.slice(7).split(",").filter(Boolean);
|
|
@@ -11016,6 +11020,8 @@ async function handleNotifications(args, ctx) {
|
|
|
11016
11020
|
} else if (token.startsWith("remind=")) {
|
|
11017
11021
|
reminderMinutes = Number(token.slice(7));
|
|
11018
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);
|
|
11019
11025
|
} else if (!target) {
|
|
11020
11026
|
target = token === "current" ? void 0 : token;
|
|
11021
11027
|
} else return usage(ctx);
|
|
@@ -11034,50 +11040,297 @@ async function handleNotifications(args, ctx) {
|
|
|
11034
11040
|
...target ? { target } : {},
|
|
11035
11041
|
events,
|
|
11036
11042
|
mentionUserIds: [...new Set(mentionUserIds)],
|
|
11037
|
-
approvalReminderMs: reminderMinutes * 6e4
|
|
11043
|
+
approvalReminderMs: reminderMinutes * 6e4,
|
|
11044
|
+
sinks: [...new Set(sinks)]
|
|
11038
11045
|
};
|
|
11039
11046
|
await store.set(ctx.scope, value);
|
|
11040
11047
|
await reply5(ctx, `\u5DF2\u5F00\u542F\u3002${describe(value, true)}`, `Enabled. ${describe(value, false)}`);
|
|
11041
11048
|
}
|
|
11042
11049
|
function describe(value, zh) {
|
|
11043
11050
|
const minutes = value.approvalReminderMs / 6e4;
|
|
11044
|
-
|
|
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.`;
|
|
11045
11053
|
}
|
|
11046
11054
|
async function usage(ctx) {
|
|
11047
11055
|
await reply5(
|
|
11048
11056
|
ctx,
|
|
11049
|
-
"\u7528\u6CD5\uFF1A`/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`",
|
|
11050
|
-
"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]]`"
|
|
11051
11059
|
);
|
|
11052
11060
|
}
|
|
11053
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>`"
|
|
11286
|
+
);
|
|
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
|
+
}
|
|
11306
|
+
|
|
11054
11307
|
// src/commands/replies.ts
|
|
11055
11308
|
async function handleReplies(args, ctx) {
|
|
11056
11309
|
const store = ctx.replyPolicies;
|
|
11057
|
-
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.");
|
|
11058
11311
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
11059
|
-
if (tokens.length === 0 || tokens[0] === "show") return
|
|
11060
|
-
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.");
|
|
11061
11314
|
if (tokens[0] === "default") {
|
|
11062
11315
|
await store.set(ctx.scope, void 0);
|
|
11063
|
-
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.");
|
|
11064
11317
|
}
|
|
11065
|
-
if (tokens[0] !== "set") return
|
|
11318
|
+
if (tokens[0] !== "set") return usage3(ctx);
|
|
11066
11319
|
const policy = { ...store.get(ctx.scope) };
|
|
11067
11320
|
for (const token of tokens.slice(1)) {
|
|
11068
11321
|
const [key, raw, extra] = token.split("=");
|
|
11069
|
-
if (!raw || extra !== void 0) return
|
|
11322
|
+
if (!raw || extra !== void 0) return usage3(ctx);
|
|
11070
11323
|
const seconds = Number(raw);
|
|
11071
|
-
if (!Number.isInteger(seconds)) return
|
|
11324
|
+
if (!Number.isInteger(seconds)) return usage3(ctx);
|
|
11072
11325
|
if (key === "merge" && seconds >= 0 && seconds <= 300) policy.mergeWindowMs = seconds * 1e3;
|
|
11073
11326
|
else if (key === "batch" && seconds >= 1 && seconds <= 20) policy.maxBatchSize = seconds;
|
|
11074
11327
|
else if (key === "interval" && seconds >= 0 && seconds <= 3600) policy.minIntervalMs = seconds * 1e3;
|
|
11075
11328
|
else if (key === "dedupe" && seconds >= 0 && seconds <= 3600) policy.dedupeWindowMs = seconds * 1e3;
|
|
11076
|
-
else return
|
|
11329
|
+
else return usage3(ctx);
|
|
11077
11330
|
}
|
|
11078
|
-
if (tokens.length === 1) return
|
|
11331
|
+
if (tokens.length === 1) return usage3(ctx);
|
|
11079
11332
|
await store.set(ctx.scope, policy);
|
|
11080
|
-
await
|
|
11333
|
+
await reply7(ctx, `\u5DF2\u8BBE\u7F6E\u3002${describe2(policy, true, true)}`, `Updated. ${describe2(policy, true, false)}`);
|
|
11081
11334
|
}
|
|
11082
11335
|
async function canManageReplies(ctx) {
|
|
11083
11336
|
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
@@ -11091,10 +11344,10 @@ async function canManageReplies(ctx) {
|
|
|
11091
11344
|
function describe2(policy, configured, zh) {
|
|
11092
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.`;
|
|
11093
11346
|
}
|
|
11094
|
-
async function
|
|
11095
|
-
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]`");
|
|
11096
11349
|
}
|
|
11097
|
-
async function
|
|
11350
|
+
async function reply7(ctx, zh, en) {
|
|
11098
11351
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
11099
11352
|
}
|
|
11100
11353
|
|
|
@@ -11281,7 +11534,8 @@ var COMMAND_CATALOG = [
|
|
|
11281
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)" },
|
|
11282
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" },
|
|
11283
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)" },
|
|
11284
|
-
{ 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)" },
|
|
11285
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" },
|
|
11286
11540
|
{ names: ["/retention"], area: "session", zh: "`[N|default]` \u2014 \u4F1A\u8BDD\u4FDD\u7559\u6761\u6570", en: "`[N|default]` \u2014 retained live-message count" },
|
|
11287
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" },
|
|
@@ -11359,6 +11613,7 @@ var HELP = [
|
|
|
11359
11613
|
"- `/notify <scope|chatId> <text>` \u2014 \u8DE8\u4F1A\u8BDD\u53D1\u9001\u901A\u77E5\uFF08\u7BA1\u7406\u5458\uFF09",
|
|
11360
11614
|
"- `/notify list` \u2014 \u67E5\u770B\u5DF2\u6CE8\u518C scope",
|
|
11361
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",
|
|
11362
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",
|
|
11363
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",
|
|
11364
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",
|
|
@@ -11420,7 +11675,7 @@ var HELP_EN = [
|
|
|
11420
11675
|
"- `/invite user|admin|group <id>` \u2014 manage access allowlists",
|
|
11421
11676
|
"- `/help` \u2014 show this help"
|
|
11422
11677
|
].join("\n");
|
|
11423
|
-
async function
|
|
11678
|
+
async function reply8(ctx, zhCn, enUs) {
|
|
11424
11679
|
await ctx.channel.sendMarkdown(ctx.chatId, enUs === void 0 ? zhCn : bilingualMarkdown(zhCn, enUs, ctx.languagePolicies?.get().plain), {
|
|
11425
11680
|
replyTo: ctx.messageId
|
|
11426
11681
|
});
|
|
@@ -11429,7 +11684,7 @@ async function handleNew(_args, ctx) {
|
|
|
11429
11684
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11430
11685
|
const interrupted = await ctx.activeRuns.interruptWorkspace(ctx.scope, cwd);
|
|
11431
11686
|
ctx.sessions.clear(ctx.scope, cwd);
|
|
11432
|
-
await
|
|
11687
|
+
await reply8(
|
|
11433
11688
|
ctx,
|
|
11434
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",
|
|
11435
11690
|
interrupted > 0 ? `Interrupted ${String(interrupted)} task(s) and started a new session.` : "Started a new session."
|
|
@@ -11458,11 +11713,11 @@ function groupAppLink(chatId) {
|
|
|
11458
11713
|
async function handleNewGroup(args, ctx) {
|
|
11459
11714
|
const name = args.trim();
|
|
11460
11715
|
if (!name) {
|
|
11461
|
-
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");
|
|
11462
11717
|
return;
|
|
11463
11718
|
}
|
|
11464
11719
|
if (name.length > MAX_GROUP_NAME_LENGTH) {
|
|
11465
|
-
await
|
|
11720
|
+
await reply8(
|
|
11466
11721
|
ctx,
|
|
11467
11722
|
`\u7FA4\u540D\u8FC7\u957F\uFF08\u4E0A\u9650 ${String(MAX_GROUP_NAME_LENGTH)} \u5B57\u7B26\uFF0C\u5F53\u524D ${String(name.length)}\uFF09\u3002`,
|
|
11468
11723
|
`The group name is too long (limit ${String(MAX_GROUP_NAME_LENGTH)} characters; received ${String(name.length)}).`
|
|
@@ -11470,11 +11725,11 @@ async function handleNewGroup(args, ctx) {
|
|
|
11470
11725
|
return;
|
|
11471
11726
|
}
|
|
11472
11727
|
if (!ctx.channel.createChat) {
|
|
11473
|
-
await
|
|
11728
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u81EA\u52A8\u5EFA\u7FA4\u3002", "This channel cannot create groups automatically.");
|
|
11474
11729
|
return;
|
|
11475
11730
|
}
|
|
11476
11731
|
if (!ctx.senderId) {
|
|
11477
|
-
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.");
|
|
11478
11733
|
return;
|
|
11479
11734
|
}
|
|
11480
11735
|
try {
|
|
@@ -11485,7 +11740,7 @@ async function handleNewGroup(args, ctx) {
|
|
|
11485
11740
|
inviteUserIds: [ctx.senderId],
|
|
11486
11741
|
userIdType: "open_id"
|
|
11487
11742
|
});
|
|
11488
|
-
await
|
|
11743
|
+
await reply8(
|
|
11489
11744
|
ctx,
|
|
11490
11745
|
[
|
|
11491
11746
|
`\u2705 \u5DF2\u521B\u5EFA\u7FA4\u804A\uFF1A**${name}**`,
|
|
@@ -11504,13 +11759,13 @@ async function handleNewGroup(args, ctx) {
|
|
|
11504
11759
|
);
|
|
11505
11760
|
} catch (error) {
|
|
11506
11761
|
const message = error instanceof Error ? error.message : String(error);
|
|
11507
|
-
await
|
|
11762
|
+
await reply8(ctx, `\u274C \u5EFA\u7FA4\u5931\u8D25\uFF1A\`${message}\``, `\u274C Failed to create the group: \`${message}\``);
|
|
11508
11763
|
}
|
|
11509
11764
|
}
|
|
11510
11765
|
async function handleCd(args, ctx) {
|
|
11511
11766
|
const path = args.trim();
|
|
11512
11767
|
if (!path) {
|
|
11513
|
-
await
|
|
11768
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`", "Usage: `/cd <path>`");
|
|
11514
11769
|
return;
|
|
11515
11770
|
}
|
|
11516
11771
|
const normalized = path.replace(/^~/u, "~");
|
|
@@ -11518,13 +11773,13 @@ async function handleCd(args, ctx) {
|
|
|
11518
11773
|
const cwd = resolve5(expanded);
|
|
11519
11774
|
const target = await stat8(cwd).catch(() => void 0);
|
|
11520
11775
|
if (!target?.isDirectory()) {
|
|
11521
|
-
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}\`.`);
|
|
11522
11777
|
return;
|
|
11523
11778
|
}
|
|
11524
11779
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11525
11780
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
11526
11781
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
11527
|
-
await
|
|
11782
|
+
await reply8(
|
|
11528
11783
|
ctx,
|
|
11529
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` : ""}`,
|
|
11530
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).` : ""}`
|
|
@@ -11544,7 +11799,7 @@ async function handleWs(args, ctx) {
|
|
|
11544
11799
|
const lines = Object.entries(named).map(
|
|
11545
11800
|
([key, value]) => `- **${key}** \u2192 \`${value}\`${value === current ? " \u2190 \u5F53\u524D" : ""}`
|
|
11546
11801
|
);
|
|
11547
|
-
await
|
|
11802
|
+
await reply8(
|
|
11548
11803
|
ctx,
|
|
11549
11804
|
[
|
|
11550
11805
|
`\u5F53\u524D cwd\uFF1A\`${current}\``,
|
|
@@ -11561,29 +11816,29 @@ async function handleWs(args, ctx) {
|
|
|
11561
11816
|
}
|
|
11562
11817
|
if (sub === "save") {
|
|
11563
11818
|
if (!name) {
|
|
11564
|
-
await
|
|
11819
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`", "Usage: `/ws save <name>`");
|
|
11565
11820
|
return;
|
|
11566
11821
|
}
|
|
11567
11822
|
const current = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11568
11823
|
ctx.workspaces.saveNamed(name, current);
|
|
11569
|
-
await
|
|
11824
|
+
await reply8(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${current}\``, `Saved workspace: **${name}** \u2192 \`${current}\``);
|
|
11570
11825
|
return;
|
|
11571
11826
|
}
|
|
11572
11827
|
if (sub === "use") {
|
|
11573
11828
|
if (!name) {
|
|
11574
|
-
await
|
|
11829
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`", "Usage: `/ws use <name>`");
|
|
11575
11830
|
return;
|
|
11576
11831
|
}
|
|
11577
11832
|
const cwd = ctx.workspaces.getNamed(name);
|
|
11578
11833
|
if (!cwd) {
|
|
11579
|
-
await
|
|
11834
|
+
await reply8(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`, `Workspace not found: **${name}**`);
|
|
11580
11835
|
return;
|
|
11581
11836
|
}
|
|
11582
11837
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11583
11838
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
11584
11839
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
11585
11840
|
ctx.workspaces.touchNamed(name);
|
|
11586
|
-
await
|
|
11841
|
+
await reply8(
|
|
11587
11842
|
ctx,
|
|
11588
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` : ""}`,
|
|
11589
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).` : ""}`
|
|
@@ -11592,14 +11847,14 @@ async function handleWs(args, ctx) {
|
|
|
11592
11847
|
}
|
|
11593
11848
|
if (sub === "remove") {
|
|
11594
11849
|
if (!name) {
|
|
11595
|
-
await
|
|
11850
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`", "Usage: `/ws remove <name>`");
|
|
11596
11851
|
return;
|
|
11597
11852
|
}
|
|
11598
11853
|
const removed = ctx.workspaces.removeNamed(name);
|
|
11599
|
-
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}**`);
|
|
11600
11855
|
return;
|
|
11601
11856
|
}
|
|
11602
|
-
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.");
|
|
11603
11858
|
}
|
|
11604
11859
|
async function statusCardInputFor(ctx) {
|
|
11605
11860
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -11646,6 +11901,7 @@ async function statusCardInputFor(ctx) {
|
|
|
11646
11901
|
permissionPolicy: ctx.permissionPolicies?.get(ctx.scope) ?? "ask",
|
|
11647
11902
|
executionMode: ctx.executionModes?.get(ctx.scope) ?? "balanced",
|
|
11648
11903
|
...notificationPreference ? { notificationPreference } : {},
|
|
11904
|
+
...ctx.notificationSinks ? { notificationChannels: ctx.notificationSinks.enabledChannels().map((channel) => channel.id) } : {},
|
|
11649
11905
|
...replyPolicy ? { replyPolicy, replyPolicyConfigured: ctx.replyPolicies?.isConfigured(ctx.scope) ?? false } : {},
|
|
11650
11906
|
role: role ? `\`${role.id}\` (${role.name})` : void 0,
|
|
11651
11907
|
metrics,
|
|
@@ -11688,16 +11944,16 @@ async function handleStatus(_args, ctx) {
|
|
|
11688
11944
|
} catch {
|
|
11689
11945
|
}
|
|
11690
11946
|
}
|
|
11691
|
-
await
|
|
11947
|
+
await reply8(ctx, bilingualMarkdown(statusCardMarkdown(input), statusCardMarkdownEnglish(input)));
|
|
11692
11948
|
}
|
|
11693
11949
|
async function handleIsolation(args, ctx) {
|
|
11694
11950
|
if (ctx.chatMode === "p2p") {
|
|
11695
|
-
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.");
|
|
11696
11952
|
return;
|
|
11697
11953
|
}
|
|
11698
11954
|
const mode = args.trim().toLowerCase();
|
|
11699
11955
|
if (!mode) {
|
|
11700
|
-
await
|
|
11956
|
+
await reply8(
|
|
11701
11957
|
ctx,
|
|
11702
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`,
|
|
11703
11959
|
`Current group isolation mode: **${ctx.isolationStore?.get(ctx.chatId) ?? "topic"}**. Use \`/isolation group|topic|member\` to switch.`
|
|
@@ -11705,16 +11961,16 @@ async function handleIsolation(args, ctx) {
|
|
|
11705
11961
|
return;
|
|
11706
11962
|
}
|
|
11707
11963
|
if (mode !== "group" && mode !== "topic" && mode !== "member") {
|
|
11708
|
-
await
|
|
11964
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/isolation group|topic|member`", "Usage: `/isolation group|topic|member`");
|
|
11709
11965
|
return;
|
|
11710
11966
|
}
|
|
11711
|
-
if (!
|
|
11967
|
+
if (!requireAdmin4(ctx)) return;
|
|
11712
11968
|
if (!ctx.isolationStore) {
|
|
11713
|
-
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.");
|
|
11714
11970
|
return;
|
|
11715
11971
|
}
|
|
11716
11972
|
ctx.isolationStore.set(ctx.chatId, mode);
|
|
11717
|
-
await
|
|
11973
|
+
await reply8(
|
|
11718
11974
|
ctx,
|
|
11719
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`,
|
|
11720
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.`
|
|
@@ -11741,25 +11997,25 @@ async function handleVersion(_args, ctx) {
|
|
|
11741
11997
|
lines.push("\u6700\u65B0\u7248\u672C\u67E5\u8BE2\u6682\u4E0D\u53EF\u7528\uFF08\u7F51\u7EDC / registry \u5F02\u5E38\uFF09\u3002");
|
|
11742
11998
|
english.push("Latest-version lookup is unavailable (network or registry error).");
|
|
11743
11999
|
}
|
|
11744
|
-
await
|
|
12000
|
+
await reply8(ctx, lines.join("\n"), english.join("\n"));
|
|
11745
12001
|
}
|
|
11746
12002
|
async function handleUpgrade(_args, ctx) {
|
|
11747
|
-
if (!
|
|
12003
|
+
if (!requireAdmin4(ctx)) return;
|
|
11748
12004
|
if (!ctx.senderId || !ctx.channelUpdates) {
|
|
11749
|
-
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.");
|
|
11750
12006
|
return;
|
|
11751
12007
|
}
|
|
11752
12008
|
const result = await ctx.channelUpdates.check({ scope: ctx.scope, actorId: ctx.senderId });
|
|
11753
12009
|
if (result.kind === "unavailable") {
|
|
11754
|
-
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.");
|
|
11755
12011
|
return;
|
|
11756
12012
|
}
|
|
11757
12013
|
if (result.kind === "current") {
|
|
11758
|
-
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}\`.`);
|
|
11759
12015
|
return;
|
|
11760
12016
|
}
|
|
11761
12017
|
if (!ctx.channel.sendCard) {
|
|
11762
|
-
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.");
|
|
11763
12019
|
return;
|
|
11764
12020
|
}
|
|
11765
12021
|
await ctx.channel.sendCard(ctx.chatId, renderUpdateCard({
|
|
@@ -11774,9 +12030,9 @@ async function handleUpgrade(_args, ctx) {
|
|
|
11774
12030
|
});
|
|
11775
12031
|
}
|
|
11776
12032
|
async function handleDoctor(_args, ctx) {
|
|
11777
|
-
if (!
|
|
12033
|
+
if (!requireAdmin4(ctx)) return;
|
|
11778
12034
|
if (!ctx.createDiagnosticBundle || !ctx.channel.sendFile) {
|
|
11779
|
-
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.");
|
|
11780
12036
|
return;
|
|
11781
12037
|
}
|
|
11782
12038
|
let file;
|
|
@@ -11793,7 +12049,7 @@ async function handleDoctor(_args, ctx) {
|
|
|
11793
12049
|
...status.jobs ? { jobs: status.jobs } : {}
|
|
11794
12050
|
}), ctx.diagnosticTimeoutMs?.generate ?? DOCTOR_GENERATE_TIMEOUT_MS);
|
|
11795
12051
|
} catch {
|
|
11796
|
-
await
|
|
12052
|
+
await reply8(
|
|
11797
12053
|
ctx,
|
|
11798
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",
|
|
11799
12055
|
"\u26A0\uFE0F Failed to generate the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
@@ -11807,21 +12063,21 @@ async function handleDoctor(_args, ctx) {
|
|
|
11807
12063
|
}), ctx.diagnosticTimeoutMs?.upload ?? DOCTOR_UPLOAD_TIMEOUT_MS);
|
|
11808
12064
|
} catch (error) {
|
|
11809
12065
|
if (error instanceof OperationTimeoutError) {
|
|
11810
|
-
await
|
|
12066
|
+
await reply8(
|
|
11811
12067
|
ctx,
|
|
11812
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",
|
|
11813
12069
|
"\u26A0\uFE0F Diagnostic upload timed out with an unknown result: the file may still arrive later. Check this chat before retrying."
|
|
11814
12070
|
);
|
|
11815
12071
|
return;
|
|
11816
12072
|
}
|
|
11817
|
-
await
|
|
12073
|
+
await reply8(
|
|
11818
12074
|
ctx,
|
|
11819
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",
|
|
11820
12076
|
"\u26A0\uFE0F Failed to send the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
11821
12077
|
);
|
|
11822
12078
|
return;
|
|
11823
12079
|
}
|
|
11824
|
-
await
|
|
12080
|
+
await reply8(
|
|
11825
12081
|
ctx,
|
|
11826
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",
|
|
11827
12083
|
"\u2705 The redacted diagnostic bundle was generated and sent. You can download and forward it to a maintainer."
|
|
@@ -11831,18 +12087,18 @@ async function handleResume(_args, ctx) {
|
|
|
11831
12087
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
11832
12088
|
const history = ctx.sessions.historyFor(ctx.scope, cwd);
|
|
11833
12089
|
if (history.length === 0) {
|
|
11834
|
-
await
|
|
12090
|
+
await reply8(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002", "This session has no conversation history.");
|
|
11835
12091
|
return;
|
|
11836
12092
|
}
|
|
11837
12093
|
const recent = history.slice(-6).map((message) => {
|
|
11838
12094
|
const speaker = message.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
11839
12095
|
return `${speaker} ${message.content.slice(0, 300)}`;
|
|
11840
12096
|
});
|
|
11841
|
-
await
|
|
12097
|
+
await reply8(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"), [`Current scope: \`${ctx.scope}\``, "", ...recent].join("\n"));
|
|
11842
12098
|
}
|
|
11843
12099
|
async function handleJobs(args, ctx) {
|
|
11844
12100
|
if (!ctx.jobs) {
|
|
11845
|
-
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.");
|
|
11846
12102
|
return;
|
|
11847
12103
|
}
|
|
11848
12104
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -11850,11 +12106,11 @@ async function handleJobs(args, ctx) {
|
|
|
11850
12106
|
const records = ctx.jobs.list(ctx.scope, cwd, 20);
|
|
11851
12107
|
if (sub === "retry") {
|
|
11852
12108
|
if (!id) {
|
|
11853
|
-
await
|
|
12109
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs retry <\u6D88\u606FID>`", "Usage: `/jobs retry <message-id>`");
|
|
11854
12110
|
return;
|
|
11855
12111
|
}
|
|
11856
12112
|
const queued = await ctx.requeueJob?.(id, ctx.scope, cwd);
|
|
11857
|
-
await
|
|
12113
|
+
await reply8(
|
|
11858
12114
|
ctx,
|
|
11859
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`,
|
|
11860
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.`
|
|
@@ -11864,19 +12120,19 @@ async function handleJobs(args, ctx) {
|
|
|
11864
12120
|
if (sub === "show") {
|
|
11865
12121
|
const record = id ? ctx.jobs.get(id, ctx.scope, cwd) : void 0;
|
|
11866
12122
|
if (!record) {
|
|
11867
|
-
await
|
|
12123
|
+
await reply8(ctx, `\u5F53\u524D workspace \u672A\u627E\u5230\u4EFB\u52A1\uFF1A**${id ?? ""}**`, `Job not found in this workspace: **${id ?? ""}**`);
|
|
11868
12124
|
return;
|
|
11869
12125
|
}
|
|
11870
|
-
await
|
|
12126
|
+
await reply8(ctx, jobDetail(record, "zh"), jobDetail(record, "en"));
|
|
11871
12127
|
return;
|
|
11872
12128
|
}
|
|
11873
12129
|
if (sub && sub !== "list") {
|
|
11874
|
-
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>]`");
|
|
11875
12131
|
return;
|
|
11876
12132
|
}
|
|
11877
12133
|
const counts = ctx.jobs.counts(ctx.scope, cwd);
|
|
11878
12134
|
const rows = records.slice(0, 10);
|
|
11879
|
-
await
|
|
12135
|
+
await reply8(
|
|
11880
12136
|
ctx,
|
|
11881
12137
|
[
|
|
11882
12138
|
"**\u4EFB\u52A1\u5BF9\u8D26**",
|
|
@@ -11923,7 +12179,7 @@ async function handleStop(_args, ctx) {
|
|
|
11923
12179
|
});
|
|
11924
12180
|
let stopped = 0;
|
|
11925
12181
|
for (const scope of scopes) stopped += await ctx.activeRuns.interrupt(scope);
|
|
11926
|
-
await
|
|
12182
|
+
await reply8(
|
|
11927
12183
|
ctx,
|
|
11928
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",
|
|
11929
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."
|
|
@@ -11934,7 +12190,7 @@ async function handleTimeout(args, ctx) {
|
|
|
11934
12190
|
const effectiveMs = ctx.runPolicies.get(ctx.scope) ?? ctx.defaultRunTimeoutMs;
|
|
11935
12191
|
if (!input) {
|
|
11936
12192
|
const minutes2 = effectiveMs > 0 ? Math.round(effectiveMs / 6e4) : 0;
|
|
11937
|
-
await
|
|
12193
|
+
await reply8(
|
|
11938
12194
|
ctx,
|
|
11939
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",
|
|
11940
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."
|
|
@@ -11943,27 +12199,27 @@ async function handleTimeout(args, ctx) {
|
|
|
11943
12199
|
}
|
|
11944
12200
|
if (input === "off") {
|
|
11945
12201
|
ctx.runPolicies.set(ctx.scope, 0);
|
|
11946
|
-
await
|
|
12202
|
+
await reply8(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\u3002", "Disabled the session idle timeout.");
|
|
11947
12203
|
return;
|
|
11948
12204
|
}
|
|
11949
12205
|
if (input === "default") {
|
|
11950
12206
|
ctx.runPolicies.clear(ctx.scope);
|
|
11951
|
-
await
|
|
12207
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u7A7A\u95F2\u8D85\u65F6\u3002", "Restored the default idle timeout.");
|
|
11952
12208
|
return;
|
|
11953
12209
|
}
|
|
11954
12210
|
const minutes = Number(input);
|
|
11955
12211
|
if (!Number.isInteger(minutes) || minutes <= 0) {
|
|
11956
|
-
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.");
|
|
11957
12213
|
return;
|
|
11958
12214
|
}
|
|
11959
12215
|
ctx.runPolicies.set(ctx.scope, minutes * 6e4);
|
|
11960
|
-
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.`);
|
|
11961
12217
|
}
|
|
11962
12218
|
async function handleConcurrency(args, ctx) {
|
|
11963
12219
|
const input = args.trim();
|
|
11964
12220
|
const effective = ctx.concurrencyStore.get(ctx.scope) ?? ctx.defaultScopeConcurrency;
|
|
11965
12221
|
if (!input) {
|
|
11966
|
-
await
|
|
12222
|
+
await reply8(
|
|
11967
12223
|
ctx,
|
|
11968
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`,
|
|
11969
12225
|
`Parallel tasks for this scope: **${String(effective)}**. Use \`/concurrency <N|default>\` to change it (N \u2265 1).`
|
|
@@ -11972,16 +12228,16 @@ async function handleConcurrency(args, ctx) {
|
|
|
11972
12228
|
}
|
|
11973
12229
|
if (input === "default") {
|
|
11974
12230
|
ctx.concurrencyStore.clear(ctx.scope);
|
|
11975
|
-
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)}).`);
|
|
11976
12232
|
return;
|
|
11977
12233
|
}
|
|
11978
12234
|
const n = Number(input);
|
|
11979
12235
|
if (!Number.isInteger(n) || n < 1) {
|
|
11980
|
-
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.");
|
|
11981
12237
|
return;
|
|
11982
12238
|
}
|
|
11983
12239
|
ctx.concurrencyStore.set(ctx.scope, n);
|
|
11984
|
-
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)}**.`);
|
|
11985
12241
|
}
|
|
11986
12242
|
async function handlePermission(args, ctx) {
|
|
11987
12243
|
const [rawPolicy = "", rawTarget = "", ...extra] = args.trim().split(/\s+/);
|
|
@@ -11993,7 +12249,7 @@ async function handlePermission(args, ctx) {
|
|
|
11993
12249
|
};
|
|
11994
12250
|
if (!input) {
|
|
11995
12251
|
const current = ctx.permissionPolicies?.get(ctx.scope) ?? "ask";
|
|
11996
|
-
await
|
|
12252
|
+
await reply8(
|
|
11997
12253
|
ctx,
|
|
11998
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`,
|
|
11999
12255
|
`Tool permission policy for this scope: **${labels[current].en}** (\`${current}\`). Use \`/permission ask|allow|deny\` to change it (admin only).`
|
|
@@ -12001,21 +12257,21 @@ async function handlePermission(args, ctx) {
|
|
|
12001
12257
|
return;
|
|
12002
12258
|
}
|
|
12003
12259
|
if (input !== "ask" && input !== "allow" && input !== "deny" || extra.length > 0) {
|
|
12004
|
-
await
|
|
12260
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/permission [ask|allow|deny] [scope]`", "Usage: `/permission [ask|allow|deny] [scope]`");
|
|
12005
12261
|
return;
|
|
12006
12262
|
}
|
|
12007
|
-
if (!
|
|
12263
|
+
if (!requireAdmin4(ctx)) return;
|
|
12008
12264
|
if (!ctx.permissionPolicies) {
|
|
12009
|
-
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.");
|
|
12010
12266
|
return;
|
|
12011
12267
|
}
|
|
12012
12268
|
const targetScope = rawTarget || ctx.scope;
|
|
12013
12269
|
if (targetScope !== ctx.chatId && !targetScope.startsWith(`${ctx.chatId}:`)) {
|
|
12014
|
-
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`.");
|
|
12015
12271
|
return;
|
|
12016
12272
|
}
|
|
12017
12273
|
await ctx.permissionPolicies.set(targetScope, input);
|
|
12018
|
-
await
|
|
12274
|
+
await reply8(
|
|
12019
12275
|
ctx,
|
|
12020
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`,
|
|
12021
12277
|
`Set scope \`${targetScope}\`'s tool permission policy to **${labels[input].en}** (\`${input}\`). It applies to the next tool approval; plan approval remains enforced.`
|
|
@@ -12025,7 +12281,7 @@ async function handleDensity(args, ctx) {
|
|
|
12025
12281
|
const input = args.trim().toLowerCase();
|
|
12026
12282
|
if (!input) {
|
|
12027
12283
|
const current = ctx.densityStore?.get(ctx.scope) ?? "standard";
|
|
12028
|
-
await
|
|
12284
|
+
await reply8(
|
|
12029
12285
|
ctx,
|
|
12030
12286
|
`\u5F53\u524D\u5361\u7247\u5BC6\u5EA6\uFF1A**${current}**\u3002\u53EF\u7528 \`/density compact|standard|detailed\` \u8C03\u6574\u3002`,
|
|
12031
12287
|
`Current card density: **${current}**. Use \`/density compact|standard|detailed\` to change it.`
|
|
@@ -12034,21 +12290,21 @@ async function handleDensity(args, ctx) {
|
|
|
12034
12290
|
}
|
|
12035
12291
|
if (input === "default") {
|
|
12036
12292
|
ctx.densityStore?.clear(ctx.scope);
|
|
12037
|
-
await
|
|
12293
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002", "Restored the default card density.");
|
|
12038
12294
|
return;
|
|
12039
12295
|
}
|
|
12040
12296
|
const density = parseCardDensity(input);
|
|
12041
12297
|
if (!density) {
|
|
12042
|
-
await
|
|
12298
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`", "Usage: `/density [compact|standard|detailed|default]`");
|
|
12043
12299
|
return;
|
|
12044
12300
|
}
|
|
12045
12301
|
ctx.densityStore?.set(ctx.scope, density);
|
|
12046
|
-
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}**.`);
|
|
12047
12303
|
}
|
|
12048
12304
|
async function handleExecutionMode(args, ctx) {
|
|
12049
12305
|
const input = args.trim().toLowerCase();
|
|
12050
12306
|
if (!ctx.executionModes) {
|
|
12051
|
-
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.");
|
|
12052
12308
|
return;
|
|
12053
12309
|
}
|
|
12054
12310
|
if (!input) {
|
|
@@ -12065,7 +12321,7 @@ async function handleExecutionMode(args, ctx) {
|
|
|
12065
12321
|
} catch {
|
|
12066
12322
|
}
|
|
12067
12323
|
}
|
|
12068
|
-
await
|
|
12324
|
+
await reply8(
|
|
12069
12325
|
ctx,
|
|
12070
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`,
|
|
12071
12327
|
`Current execution mode: **${current}**. Use \`/mode quick|balanced|deep\`; changes apply next turn.`
|
|
@@ -12073,18 +12329,18 @@ async function handleExecutionMode(args, ctx) {
|
|
|
12073
12329
|
return;
|
|
12074
12330
|
}
|
|
12075
12331
|
if (input !== "quick" && input !== "balanced" && input !== "deep") {
|
|
12076
|
-
await
|
|
12332
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/mode [quick|balanced|deep]`", "Usage: `/mode [quick|balanced|deep]`");
|
|
12077
12333
|
return;
|
|
12078
12334
|
}
|
|
12079
12335
|
await ctx.executionModes.set(ctx.scope, input);
|
|
12080
|
-
await
|
|
12336
|
+
await reply8(
|
|
12081
12337
|
ctx,
|
|
12082
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`,
|
|
12083
12339
|
`Set this session\u2019s execution mode to **${input}** for the next turn. Active work and existing context are unchanged.`
|
|
12084
12340
|
);
|
|
12085
12341
|
}
|
|
12086
12342
|
async function handleEffortAlias(args, ctx) {
|
|
12087
|
-
await
|
|
12343
|
+
await reply8(
|
|
12088
12344
|
ctx,
|
|
12089
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",
|
|
12090
12346
|
"`/effort` is a compatibility alias for `/mode`; it controls task execution intensity, not the model reasoning-effort setting."
|
|
@@ -12094,11 +12350,11 @@ async function handleEffortAlias(args, ctx) {
|
|
|
12094
12350
|
async function handleAsk(args, ctx) {
|
|
12095
12351
|
const question = args.trim();
|
|
12096
12352
|
if (!question) {
|
|
12097
|
-
await
|
|
12353
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`", "Usage: `/ask <question>`");
|
|
12098
12354
|
return;
|
|
12099
12355
|
}
|
|
12100
12356
|
if (!ctx.questions) {
|
|
12101
|
-
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).");
|
|
12102
12358
|
return;
|
|
12103
12359
|
}
|
|
12104
12360
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -12120,18 +12376,18 @@ async function handleAsk(args, ctx) {
|
|
|
12120
12376
|
if (answer !== void 0) {
|
|
12121
12377
|
const text = Array.isArray(answer) ? answer.join("\u3001") : answer;
|
|
12122
12378
|
ctx.sessions.recordExchange(ctx.scope, cwd, [text], void 0);
|
|
12123
|
-
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.");
|
|
12124
12380
|
} else {
|
|
12125
|
-
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).");
|
|
12126
12382
|
}
|
|
12127
12383
|
}
|
|
12128
12384
|
async function handleInvite(args, ctx) {
|
|
12129
12385
|
const [kind, ...rest] = args.trim().split(/\s+/);
|
|
12130
12386
|
const id = rest.join(" ").trim();
|
|
12131
|
-
if (kind !== "list" && !
|
|
12387
|
+
if (kind !== "list" && !requireAdmin4(ctx)) return;
|
|
12132
12388
|
if (kind === "list") {
|
|
12133
12389
|
const snapshot = ctx.accessManager.snapshot();
|
|
12134
|
-
await
|
|
12390
|
+
await reply8(
|
|
12135
12391
|
ctx,
|
|
12136
12392
|
[
|
|
12137
12393
|
"**\u8BBF\u95EE\u767D\u540D\u5355**",
|
|
@@ -12149,7 +12405,7 @@ async function handleInvite(args, ctx) {
|
|
|
12149
12405
|
return;
|
|
12150
12406
|
}
|
|
12151
12407
|
if (!kind || !id) {
|
|
12152
|
-
await
|
|
12408
|
+
await reply8(
|
|
12153
12409
|
ctx,
|
|
12154
12410
|
"\u7528\u6CD5\uFF1A`/invite user|admin|group <id>`\u3001`/invite list`\u3001`/invite remove user|group <id>`",
|
|
12155
12411
|
"Usage: `/invite user|admin|group <id>`, `/invite list`, or `/invite remove user|group <id>`"
|
|
@@ -12158,39 +12414,39 @@ async function handleInvite(args, ctx) {
|
|
|
12158
12414
|
}
|
|
12159
12415
|
if (kind === "user") {
|
|
12160
12416
|
await ctx.accessManager.addUser(id);
|
|
12161
|
-
await
|
|
12417
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``, `Allowed user: \`${id}\``);
|
|
12162
12418
|
return;
|
|
12163
12419
|
}
|
|
12164
12420
|
if (kind === "admin") {
|
|
12165
12421
|
await ctx.accessManager.addAdmin(id);
|
|
12166
|
-
await
|
|
12422
|
+
await reply8(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``, `Granted admin access to: \`${id}\``);
|
|
12167
12423
|
return;
|
|
12168
12424
|
}
|
|
12169
12425
|
if (kind === "group") {
|
|
12170
12426
|
await ctx.accessManager.addChat(id);
|
|
12171
|
-
await
|
|
12427
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``, `Allowed group chat: \`${id}\``);
|
|
12172
12428
|
return;
|
|
12173
12429
|
}
|
|
12174
12430
|
if (kind === "remove") {
|
|
12175
12431
|
const [sub, target] = rest;
|
|
12176
12432
|
if (sub === "user" && target) {
|
|
12177
12433
|
await ctx.accessManager.removeUser(target);
|
|
12178
|
-
await
|
|
12434
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``, `Removed user: \`${target}\``);
|
|
12179
12435
|
return;
|
|
12180
12436
|
}
|
|
12181
12437
|
if (sub === "group" && target) {
|
|
12182
12438
|
await ctx.accessManager.removeChat(target);
|
|
12183
|
-
await
|
|
12439
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``, `Removed group chat: \`${target}\``);
|
|
12184
12440
|
return;
|
|
12185
12441
|
}
|
|
12186
|
-
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>`");
|
|
12187
12443
|
return;
|
|
12188
12444
|
}
|
|
12189
|
-
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.");
|
|
12190
12446
|
}
|
|
12191
|
-
function
|
|
12447
|
+
function requireAdmin4(ctx) {
|
|
12192
12448
|
if (!ctx.accessManager.isAdmin(ctx.senderId)) {
|
|
12193
|
-
void
|
|
12449
|
+
void reply8(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u8BE5\u64CD\u4F5C\u3002", "Only admins can perform this operation.");
|
|
12194
12450
|
return false;
|
|
12195
12451
|
}
|
|
12196
12452
|
return true;
|
|
@@ -12243,20 +12499,20 @@ async function handleKeyDispatch(args, ctx) {
|
|
|
12243
12499
|
await handleKey(args, ctx);
|
|
12244
12500
|
}
|
|
12245
12501
|
async function handleHelp(_args, ctx) {
|
|
12246
|
-
await
|
|
12502
|
+
await reply8(ctx, renderCommandHelp("zh"), renderCommandHelp("en"));
|
|
12247
12503
|
}
|
|
12248
12504
|
async function handleLanguage(args, ctx) {
|
|
12249
12505
|
if (!ctx.languagePolicies) {
|
|
12250
|
-
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.");
|
|
12251
12507
|
return;
|
|
12252
12508
|
}
|
|
12253
12509
|
const [action = "show", field, value] = args.trim().split(/\s+/);
|
|
12254
12510
|
if (action === "show") {
|
|
12255
12511
|
const policy = ctx.languagePolicies.get();
|
|
12256
|
-
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}`);
|
|
12257
12513
|
return;
|
|
12258
12514
|
}
|
|
12259
|
-
if (!
|
|
12515
|
+
if (!requireAdmin4(ctx)) return;
|
|
12260
12516
|
if (action === "set" && field === "plain" && (value === "bilingual" || value === "zh" || value === "en")) {
|
|
12261
12517
|
await ctx.languagePolicies.set({ plain: value });
|
|
12262
12518
|
} else if (action === "set" && field === "agent" && (value === "auto" || value === "zh" || value === "en")) {
|
|
@@ -12264,7 +12520,7 @@ async function handleLanguage(args, ctx) {
|
|
|
12264
12520
|
} else if (action === "reset" && (field === void 0 || field === "all" || field === "plain" || field === "agent")) {
|
|
12265
12521
|
await ctx.languagePolicies.reset(field ?? "all");
|
|
12266
12522
|
} else {
|
|
12267
|
-
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]`");
|
|
12268
12524
|
return;
|
|
12269
12525
|
}
|
|
12270
12526
|
process.env.DSH_LARK_REPLY_LANG = ctx.languagePolicies.get().plain;
|
|
@@ -12275,37 +12531,37 @@ function parseSecretTarget(value) {
|
|
|
12275
12531
|
}
|
|
12276
12532
|
async function handleSecret(args, ctx) {
|
|
12277
12533
|
if (!ctx.secretService) {
|
|
12278
|
-
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.");
|
|
12279
12535
|
return;
|
|
12280
12536
|
}
|
|
12281
12537
|
const [action, targetRaw, referenceRaw, ...purposeParts] = args.trim().split(/\s+/);
|
|
12282
12538
|
const target = parseSecretTarget(targetRaw);
|
|
12283
12539
|
const reference = referenceRaw?.trim();
|
|
12284
12540
|
if (!target || !reference || !["status", "set", "remove"].includes(action ?? "")) {
|
|
12285
|
-
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>`");
|
|
12286
12542
|
return;
|
|
12287
12543
|
}
|
|
12288
|
-
if (action !== "status" && !
|
|
12544
|
+
if (action !== "status" && !requireAdmin4(ctx)) return;
|
|
12289
12545
|
if (action === "status") {
|
|
12290
12546
|
const configured = await ctx.secretService.configured(target, reference);
|
|
12291
|
-
await
|
|
12547
|
+
await reply8(ctx, `${target} \`${reference}\`\uFF1A${configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`, `${target} \`${reference}\`: ${configured ? "configured" : "not configured"}`);
|
|
12292
12548
|
return;
|
|
12293
12549
|
}
|
|
12294
12550
|
if (action === "remove") {
|
|
12295
12551
|
const removed = await ctx.secretService.remove(target, reference);
|
|
12296
|
-
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.");
|
|
12297
12553
|
return;
|
|
12298
12554
|
}
|
|
12299
12555
|
if (!ctx.senderId) {
|
|
12300
|
-
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.");
|
|
12301
12557
|
return;
|
|
12302
12558
|
}
|
|
12303
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" });
|
|
12304
|
-
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.");
|
|
12305
12561
|
}
|
|
12306
12562
|
async function handleSessionProjection(args, ctx) {
|
|
12307
12563
|
if (!ctx.sessionProjection) {
|
|
12308
|
-
await
|
|
12564
|
+
await reply8(
|
|
12309
12565
|
ctx,
|
|
12310
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",
|
|
12311
12567
|
"The current adapter does not provide DSH session projection; use the `web` adapter and ensure the Web host is reachable."
|
|
@@ -12335,6 +12591,7 @@ var handlers = {
|
|
|
12335
12591
|
"/role": handleRole,
|
|
12336
12592
|
"/notify": handleNotify,
|
|
12337
12593
|
"/notifications": handleNotifications,
|
|
12594
|
+
"/channels": handleChannels,
|
|
12338
12595
|
"/replies": handleReplies,
|
|
12339
12596
|
"/retention": handleRetention,
|
|
12340
12597
|
"/archive": handleArchive,
|
|
@@ -12814,24 +13071,26 @@ var GroupMessagePoller = class {
|
|
|
12814
13071
|
|
|
12815
13072
|
// src/bridge/reconnect-notifier.ts
|
|
12816
13073
|
var ReconnectNotifier = class {
|
|
12817
|
-
constructor(channel, directory, now = Date.now, reconciliation) {
|
|
13074
|
+
constructor(channel, directory, now = Date.now, reconciliation, onFault) {
|
|
12818
13075
|
this.channel = channel;
|
|
12819
13076
|
this.directory = directory;
|
|
12820
13077
|
this.now = now;
|
|
12821
13078
|
this.reconciliation = reconciliation;
|
|
13079
|
+
this.onFault = onFault;
|
|
12822
13080
|
}
|
|
12823
13081
|
channel;
|
|
12824
13082
|
directory;
|
|
12825
13083
|
now;
|
|
12826
13084
|
reconciliation;
|
|
13085
|
+
onFault;
|
|
12827
13086
|
startedAt;
|
|
12828
13087
|
async reconnecting() {
|
|
12829
13088
|
if (this.startedAt !== void 0) return;
|
|
12830
13089
|
this.startedAt = this.now();
|
|
12831
|
-
|
|
12832
|
-
|
|
12833
|
-
|
|
12834
|
-
);
|
|
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 });
|
|
12835
13094
|
}
|
|
12836
13095
|
async reconnected() {
|
|
12837
13096
|
if (this.startedAt === void 0) return;
|
|
@@ -12839,12 +13098,12 @@ var ReconnectNotifier = class {
|
|
|
12839
13098
|
this.startedAt = void 0;
|
|
12840
13099
|
const target = this.directory?.recentDestination();
|
|
12841
13100
|
const summary = target ? this.reconciliation?.(target.scope) : void 0;
|
|
12842
|
-
|
|
12843
|
-
|
|
12844
|
-
${summary
|
|
12845
|
-
|
|
12846
|
-
|
|
12847
|
-
);
|
|
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 });
|
|
12848
13107
|
}
|
|
12849
13108
|
async send(zhCn, enUs) {
|
|
12850
13109
|
const target = this.directory?.recentDestination();
|
|
@@ -12854,6 +13113,13 @@ ${summary.enUs}` : ""}`
|
|
|
12854
13113
|
...target.threadId ? { threadId: target.threadId } : {}
|
|
12855
13114
|
});
|
|
12856
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
|
+
}
|
|
12857
13123
|
};
|
|
12858
13124
|
function formatDurationEnglish(ms) {
|
|
12859
13125
|
if (ms < 1e3) return "less than 1 second";
|
|
@@ -14056,7 +14322,8 @@ async function startChannel(deps) {
|
|
|
14056
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`,
|
|
14057
14323
|
enUs: `Ledger reconciliation: queued ${counts.queued} \xB7 running ${counts.running} \xB7 interrupted ${counts.interrupted} \xB7 failed ${counts.failed}; use \`/jobs\` for details.`
|
|
14058
14324
|
};
|
|
14059
|
-
} : void 0
|
|
14325
|
+
} : void 0,
|
|
14326
|
+
deps.faultNotifier
|
|
14060
14327
|
);
|
|
14061
14328
|
const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
|
|
14062
14329
|
let groupPoller;
|
|
@@ -14191,6 +14458,8 @@ async function startChannel(deps) {
|
|
|
14191
14458
|
...deps.permissionPolicies ? { permissionPolicies: deps.permissionPolicies } : {},
|
|
14192
14459
|
...deps.notificationPreferences ? { notificationPreferences: deps.notificationPreferences } : {},
|
|
14193
14460
|
...deps.defaultNotificationPreference ? { defaultNotificationPreference: deps.defaultNotificationPreference } : {},
|
|
14461
|
+
...deps.notificationChannels ? { notificationChannels: deps.notificationChannels } : {},
|
|
14462
|
+
...deps.notificationSinks ? { notificationSinks: deps.notificationSinks } : {},
|
|
14194
14463
|
...deps.replyPolicies ? { replyPolicies: deps.replyPolicies } : {},
|
|
14195
14464
|
...deps.executionModes ? { executionModes: deps.executionModes } : {},
|
|
14196
14465
|
...deps.languagePolicies ? { languagePolicies: deps.languagePolicies } : {},
|
|
@@ -15555,6 +15824,11 @@ var NotificationDispatcher = class {
|
|
|
15555
15824
|
setDefaultPreference(preference) {
|
|
15556
15825
|
this.defaultPreference = clonePreference(preference);
|
|
15557
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
|
+
*/
|
|
15558
15832
|
async notify(scope, event, detail) {
|
|
15559
15833
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
15560
15834
|
if (!preference?.events.includes(event)) return false;
|
|
@@ -15563,21 +15837,52 @@ var NotificationDispatcher = class {
|
|
|
15563
15837
|
log.warn("notification", "target-unavailable", { scope, target: preference.target });
|
|
15564
15838
|
return false;
|
|
15565
15839
|
}
|
|
15566
|
-
const title = event
|
|
15840
|
+
const title = titleForEvent(event);
|
|
15567
15841
|
try {
|
|
15568
|
-
await this.deps.send(destination.chatId,
|
|
15569
|
-
`${title[0]}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
15570
|
-
`${title[1]} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
15571
|
-
), {
|
|
15842
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
15572
15843
|
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
15573
15844
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
15574
15845
|
...preference.mentionUserIds.length > 0 ? { mentions: preference.mentionUserIds.map((userId) => ({ userId })) } : {}
|
|
15575
15846
|
});
|
|
15576
|
-
return true;
|
|
15577
15847
|
} catch (error) {
|
|
15578
15848
|
log.warn("notification", "send-failed", { scope, event, error });
|
|
15579
15849
|
return false;
|
|
15580
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
|
+
}
|
|
15581
15886
|
}
|
|
15582
15887
|
scheduleApprovalReminder(scope, toolName) {
|
|
15583
15888
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
@@ -15588,10 +15893,196 @@ var NotificationDispatcher = class {
|
|
|
15588
15893
|
timer.unref?.();
|
|
15589
15894
|
return () => clearTimeout(timer);
|
|
15590
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
|
+
}
|
|
15591
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
|
+
}
|
|
15592
15931
|
function clonePreference(preference) {
|
|
15593
|
-
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;
|
|
15933
|
+
}
|
|
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("'", "'");
|
|
15594
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
|
+
};
|
|
15595
16086
|
|
|
15596
16087
|
// src/bridge/reply-dispatcher.ts
|
|
15597
16088
|
var ReplyDispatcher = class {
|
|
@@ -15651,8 +16142,8 @@ var ReplyDispatcher = class {
|
|
|
15651
16142
|
}
|
|
15652
16143
|
}
|
|
15653
16144
|
};
|
|
15654
|
-
function replyRoute(
|
|
15655
|
-
return `${
|
|
16145
|
+
function replyRoute(reply9) {
|
|
16146
|
+
return `${reply9.chatId}\0${reply9.options?.threadId ?? ""}`;
|
|
15656
16147
|
}
|
|
15657
16148
|
function mergedMarkdown(batch) {
|
|
15658
16149
|
if (batch.length === 1) return batch[0].markdown;
|
|
@@ -15787,7 +16278,7 @@ async function onboardPersonalAgent(deps = {}) {
|
|
|
15787
16278
|
}
|
|
15788
16279
|
|
|
15789
16280
|
// src/media/attachments.ts
|
|
15790
|
-
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";
|
|
15791
16282
|
import { join as join23 } from "path";
|
|
15792
16283
|
var MAX_TEXT_FILE_BYTES = 256e3;
|
|
15793
16284
|
function assertSafeMediaName(mediaDir, destination) {
|
|
@@ -15828,7 +16319,7 @@ async function prepareAttachments(channel, message, mediaDir) {
|
|
|
15828
16319
|
result.textFileNotes.push(`[attachment: ${resource.fileName ?? resource.fileKey}] ${destination}`);
|
|
15829
16320
|
continue;
|
|
15830
16321
|
}
|
|
15831
|
-
const content = await
|
|
16322
|
+
const content = await readFile24(destination, "utf8");
|
|
15832
16323
|
const safeContent = truncateUtf8Safe(content, MAX_TEXT_FILE_BYTES);
|
|
15833
16324
|
result.textFileNotes.push(
|
|
15834
16325
|
`[attachment: ${resource.fileName ?? resource.fileKey}]
|
|
@@ -15839,7 +16330,7 @@ ${safeContent}`
|
|
|
15839
16330
|
}
|
|
15840
16331
|
|
|
15841
16332
|
// src/session/store.ts
|
|
15842
|
-
import { readFile as
|
|
16333
|
+
import { readFile as readFile25 } from "fs/promises";
|
|
15843
16334
|
var MAX_CONTEXT_SNAPSHOTS_PER_WORKSPACE = 32;
|
|
15844
16335
|
var SessionStore = class {
|
|
15845
16336
|
constructor(path) {
|
|
@@ -15854,7 +16345,7 @@ var SessionStore = class {
|
|
|
15854
16345
|
pendingArchive = Promise.resolve();
|
|
15855
16346
|
async load() {
|
|
15856
16347
|
try {
|
|
15857
|
-
const raw = JSON.parse(await
|
|
16348
|
+
const raw = JSON.parse(await readFile25(this.path, "utf8"));
|
|
15858
16349
|
const legacy = raw.schema !== 2 || !isObject(raw.workspaces);
|
|
15859
16350
|
this.data = legacy ? migrateLegacy(raw) : normalizeV2(raw.workspaces);
|
|
15860
16351
|
this.legacyScopes = legacy ? new Set(Object.keys(this.data.workspaces)) : /* @__PURE__ */ new Set();
|
|
@@ -15951,13 +16442,13 @@ var SessionStore = class {
|
|
|
15951
16442
|
fullHistoryFor(scopeId, cwd) {
|
|
15952
16443
|
return this.historyFor(scopeId, cwd);
|
|
15953
16444
|
}
|
|
15954
|
-
recordUsage(scopeId, cwd,
|
|
16445
|
+
recordUsage(scopeId, cwd, usage4) {
|
|
15955
16446
|
const state = this.ensureState(scopeId, cwd);
|
|
15956
16447
|
const next = { ...state.metrics };
|
|
15957
|
-
addMetric(next, "inputTokens",
|
|
15958
|
-
addMetric(next, "outputTokens",
|
|
15959
|
-
addMetric(next, "cacheReadTokens",
|
|
15960
|
-
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);
|
|
15961
16452
|
state.metrics = next;
|
|
15962
16453
|
this.schedulePersist();
|
|
15963
16454
|
}
|
|
@@ -16173,7 +16664,7 @@ function normalizeContextSnapshot(value) {
|
|
|
16173
16664
|
}
|
|
16174
16665
|
|
|
16175
16666
|
// src/session/projection-store.ts
|
|
16176
|
-
import { readFile as
|
|
16667
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
16177
16668
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
16178
16669
|
var MAX_RECENT_MESSAGES = 64;
|
|
16179
16670
|
var MAX_PROMPT_CORRELATIONS = 64;
|
|
@@ -16187,7 +16678,7 @@ var SessionProjectionStore = class {
|
|
|
16187
16678
|
saving = Promise.resolve();
|
|
16188
16679
|
async load() {
|
|
16189
16680
|
try {
|
|
16190
|
-
const parsed = JSON.parse(await
|
|
16681
|
+
const parsed = JSON.parse(await readFile26(this.path, "utf8"));
|
|
16191
16682
|
const bindings = Object.fromEntries(
|
|
16192
16683
|
Object.values(parsed.bindings ?? {}).flatMap((value) => {
|
|
16193
16684
|
const normalized = normalizeBinding(value);
|
|
@@ -16399,7 +16890,7 @@ function normalizeCorrelation(value) {
|
|
|
16399
16890
|
// src/session/archive.ts
|
|
16400
16891
|
import { execFile as execFile2 } from "child_process";
|
|
16401
16892
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
16402
|
-
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";
|
|
16403
16894
|
import { join as join24 } from "path";
|
|
16404
16895
|
import { promisify } from "util";
|
|
16405
16896
|
var execFileAsync = promisify(execFile2);
|
|
@@ -16581,8 +17072,8 @@ var SessionArchive = class {
|
|
|
16581
17072
|
const jsonlPath = join24(scopeDir, `${id}.jsonl`);
|
|
16582
17073
|
const markdownPath = join24(scopeDir, `${id}.md`);
|
|
16583
17074
|
const [jsonl, markdown2] = await Promise.all([
|
|
16584
|
-
|
|
16585
|
-
|
|
17075
|
+
readFile27(jsonlPath, "utf8"),
|
|
17076
|
+
readFile27(markdownPath, "utf8")
|
|
16586
17077
|
]);
|
|
16587
17078
|
const [first = "", ...rest] = jsonl.split("\n");
|
|
16588
17079
|
const header = JSON.parse(first);
|
|
@@ -16697,7 +17188,7 @@ async function defaultRunGit(args, cwd) {
|
|
|
16697
17188
|
}
|
|
16698
17189
|
async function readArchiveHeader(jsonlPath) {
|
|
16699
17190
|
try {
|
|
16700
|
-
const raw = await
|
|
17191
|
+
const raw = await readFile27(jsonlPath, "utf8");
|
|
16701
17192
|
const firstLine = raw.split("\n", 1)[0];
|
|
16702
17193
|
if (!firstLine) return void 0;
|
|
16703
17194
|
const header = JSON.parse(firstLine);
|
|
@@ -16747,7 +17238,7 @@ async function defaultCopyRulesFile(source, target) {
|
|
|
16747
17238
|
await mkdir18(dirname16(target), { recursive: true });
|
|
16748
17239
|
await copyFile(source, target);
|
|
16749
17240
|
}
|
|
16750
|
-
function
|
|
17241
|
+
function slugify3(scope) {
|
|
16751
17242
|
const slug = scope.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.]+/, "").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
16752
17243
|
return slug || "session";
|
|
16753
17244
|
}
|
|
@@ -16763,7 +17254,7 @@ var GitWorktreeManager = class {
|
|
|
16763
17254
|
async ensure(scope, base) {
|
|
16764
17255
|
const isGit = await this.isGitRepository(base);
|
|
16765
17256
|
if (!isGit) return { cwd: base, created: false };
|
|
16766
|
-
const scopeSlug =
|
|
17257
|
+
const scopeSlug = slugify3(scope);
|
|
16767
17258
|
const project = createHash2("sha256").update(base).digest("hex").slice(0, 10);
|
|
16768
17259
|
const slug = `${scopeSlug}-${project}`;
|
|
16769
17260
|
const target = join25(this.options.worktreesRoot, slug);
|
|
@@ -16800,7 +17291,7 @@ var GitWorktreeManager = class {
|
|
|
16800
17291
|
}
|
|
16801
17292
|
/** Owning main worktree for the legacy scope-only linked worktree. */
|
|
16802
17293
|
async legacyWorkspaceBase(scope) {
|
|
16803
|
-
const legacyTarget = join25(this.options.worktreesRoot,
|
|
17294
|
+
const legacyTarget = join25(this.options.worktreesRoot, slugify3(scope));
|
|
16804
17295
|
if (!await exists2(legacyTarget)) return void 0;
|
|
16805
17296
|
try {
|
|
16806
17297
|
const output = await this.runGit(["worktree", "list", "--porcelain"], legacyTarget);
|
|
@@ -16832,7 +17323,7 @@ async function samePath(left, right) {
|
|
|
16832
17323
|
}
|
|
16833
17324
|
|
|
16834
17325
|
// src/workspace/store.ts
|
|
16835
|
-
import { readFile as
|
|
17326
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
16836
17327
|
var WorkspaceStore = class {
|
|
16837
17328
|
data = { chats: {}, named: {}, lastUsed: {} };
|
|
16838
17329
|
saving = Promise.resolve();
|
|
@@ -16842,7 +17333,7 @@ var WorkspaceStore = class {
|
|
|
16842
17333
|
}
|
|
16843
17334
|
async load() {
|
|
16844
17335
|
try {
|
|
16845
|
-
const raw = await
|
|
17336
|
+
const raw = await readFile28(this.path, "utf8");
|
|
16846
17337
|
const parsed = JSON.parse(raw);
|
|
16847
17338
|
this.data = {
|
|
16848
17339
|
chats: parsed.chats ?? {},
|
|
@@ -16961,7 +17452,7 @@ var UpdateNotifier = class {
|
|
|
16961
17452
|
};
|
|
16962
17453
|
|
|
16963
17454
|
// src/bot/fleet-store.ts
|
|
16964
|
-
import { mkdir as mkdir19, readFile as
|
|
17455
|
+
import { mkdir as mkdir19, readFile as readFile29 } from "fs/promises";
|
|
16965
17456
|
import { dirname as dirname17 } from "path";
|
|
16966
17457
|
function validBotInstanceName(name) {
|
|
16967
17458
|
return /^[a-z][a-z0-9-]{0,31}$/u.test(name);
|
|
@@ -16974,7 +17465,7 @@ var BotFleetStore = class {
|
|
|
16974
17465
|
data = { schemaVersion: 1, bots: {} };
|
|
16975
17466
|
async load() {
|
|
16976
17467
|
try {
|
|
16977
|
-
const parsed = JSON.parse(await
|
|
17468
|
+
const parsed = JSON.parse(await readFile29(this.path, "utf8"));
|
|
16978
17469
|
this.data = {
|
|
16979
17470
|
schemaVersion: 1,
|
|
16980
17471
|
bots: Object.fromEntries(
|
|
@@ -17102,7 +17593,7 @@ function isFleetEntry(value) {
|
|
|
17102
17593
|
}
|
|
17103
17594
|
|
|
17104
17595
|
// src/bot/handoff-guard.ts
|
|
17105
|
-
import { mkdir as mkdir20, readFile as
|
|
17596
|
+
import { mkdir as mkdir20, readFile as readFile30 } from "fs/promises";
|
|
17106
17597
|
import { dirname as dirname18 } from "path";
|
|
17107
17598
|
var BotHandoffGuard = class {
|
|
17108
17599
|
constructor(path) {
|
|
@@ -17143,7 +17634,7 @@ var BotHandoffGuard = class {
|
|
|
17143
17634
|
}
|
|
17144
17635
|
async read() {
|
|
17145
17636
|
try {
|
|
17146
|
-
const parsed = JSON.parse(await
|
|
17637
|
+
const parsed = JSON.parse(await readFile30(this.path, "utf8"));
|
|
17147
17638
|
return { schemaVersion: 1, chats: parsed.chats ?? {} };
|
|
17148
17639
|
} catch (error) {
|
|
17149
17640
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -17169,7 +17660,7 @@ init_dsh_runtime();
|
|
|
17169
17660
|
import { homedir as homedir16 } from "os";
|
|
17170
17661
|
|
|
17171
17662
|
// src/bot/job-ledger.ts
|
|
17172
|
-
import { readFile as
|
|
17663
|
+
import { readFile as readFile31, rename as rename4 } from "fs/promises";
|
|
17173
17664
|
var EMPTY_COUNTS = {
|
|
17174
17665
|
queued: 0,
|
|
17175
17666
|
running: 0,
|
|
@@ -17192,7 +17683,7 @@ var JobLedger = class {
|
|
|
17192
17683
|
if (this.path === ":memory:") return;
|
|
17193
17684
|
let raw;
|
|
17194
17685
|
try {
|
|
17195
|
-
raw = await
|
|
17686
|
+
raw = await readFile31(this.path, "utf8");
|
|
17196
17687
|
} catch (error) {
|
|
17197
17688
|
if (error.code === "ENOENT") return;
|
|
17198
17689
|
throw error;
|
|
@@ -17933,7 +18424,7 @@ init_own_package();
|
|
|
17933
18424
|
// src/guardian/update-handoff.ts
|
|
17934
18425
|
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
17935
18426
|
import { spawn as spawn7 } from "child_process";
|
|
17936
|
-
import { chmod, mkdir as mkdir21, readFile as
|
|
18427
|
+
import { chmod, mkdir as mkdir21, readFile as readFile32, rm as rm10 } from "fs/promises";
|
|
17937
18428
|
import { dirname as dirname19, join as join26 } from "path";
|
|
17938
18429
|
init_own_package();
|
|
17939
18430
|
init_process();
|
|
@@ -17963,7 +18454,7 @@ function guardianUpdateFailureHint(code) {
|
|
|
17963
18454
|
}
|
|
17964
18455
|
async function loadState(file) {
|
|
17965
18456
|
try {
|
|
17966
|
-
const parsed = JSON.parse(await
|
|
18457
|
+
const parsed = JSON.parse(await readFile32(file, "utf8"));
|
|
17967
18458
|
return parsed.schemaVersion === 1 && typeof parsed.id === "string" ? parsed : void 0;
|
|
17968
18459
|
} catch (error) {
|
|
17969
18460
|
if (error.code === "ENOENT") return void 0;
|
|
@@ -18359,6 +18850,8 @@ async function startBridgeEngine(options) {
|
|
|
18359
18850
|
const isolationStore = new IsolationStore(paths.profilePath(profileName, "isolation.json"));
|
|
18360
18851
|
const permissionPolicies = new PermissionPolicyStore(paths.permissionPoliciesFile(profileName));
|
|
18361
18852
|
const notificationPreferences = new NotificationPreferenceStore(paths.notificationPreferencesFile(profileName));
|
|
18853
|
+
const notificationChannels = new NotificationChannelStore(paths.notificationChannelsFile(profileName));
|
|
18854
|
+
const notificationSinks = new OutboundSinkRegistry(notificationChannels);
|
|
18362
18855
|
const replyPolicies = new ReplyPolicyStore(paths.replyPoliciesFile(profileName));
|
|
18363
18856
|
const executionModes = new ExecutionModeStore(paths.executionModesFile(profileName));
|
|
18364
18857
|
const languagePolicies = new LanguagePolicyStore(paths.languagePoliciesFile(profileName));
|
|
@@ -18381,6 +18874,7 @@ async function startBridgeEngine(options) {
|
|
|
18381
18874
|
isolationStore.load(),
|
|
18382
18875
|
permissionPolicies.load(),
|
|
18383
18876
|
notificationPreferences.load(),
|
|
18877
|
+
notificationChannels.load(),
|
|
18384
18878
|
replyPolicies.load(),
|
|
18385
18879
|
executionModes.load(),
|
|
18386
18880
|
languagePolicies.load()
|
|
@@ -18450,7 +18944,8 @@ async function startBridgeEngine(options) {
|
|
|
18450
18944
|
send: async (chatId, markdown2, options2) => {
|
|
18451
18945
|
if (!streaming) throw new Error("bridge channel is not ready");
|
|
18452
18946
|
await streaming.sendMarkdown(chatId, markdown2, options2);
|
|
18453
|
-
}
|
|
18947
|
+
},
|
|
18948
|
+
sinks: notificationSinks
|
|
18454
18949
|
});
|
|
18455
18950
|
const replyDispatcher = new ReplyDispatcher({
|
|
18456
18951
|
policies: replyPolicies,
|
|
@@ -18741,6 +19236,9 @@ async function startBridgeEngine(options) {
|
|
|
18741
19236
|
approvals,
|
|
18742
19237
|
permissionPolicies,
|
|
18743
19238
|
notificationPreferences,
|
|
19239
|
+
notificationChannels,
|
|
19240
|
+
notificationSinks,
|
|
19241
|
+
faultNotifier: (scope, title, detail) => notificationDispatcher.notifyUrgent(scope, title, detail),
|
|
18744
19242
|
...defaultNotificationPreference ? { defaultNotificationPreference: { ...defaultNotificationPreference, events: [...defaultNotificationPreference.events] } } : {},
|
|
18745
19243
|
replyPolicies,
|
|
18746
19244
|
executionModes,
|
|
@@ -19003,6 +19501,7 @@ function notificationPreferenceFor(value) {
|
|
|
19003
19501
|
return {
|
|
19004
19502
|
events: value === "all" ? ["completed", "failed", "approval"] : ["completed", "failed"],
|
|
19005
19503
|
mentionUserIds: [],
|
|
19504
|
+
sinks: [],
|
|
19006
19505
|
approvalReminderMs: 10 * 6e4
|
|
19007
19506
|
};
|
|
19008
19507
|
}
|
|
@@ -19038,7 +19537,7 @@ function waitForShutdown() {
|
|
|
19038
19537
|
init_dsh_runtime();
|
|
19039
19538
|
init_own_package();
|
|
19040
19539
|
import { spawn as spawn8 } from "child_process";
|
|
19041
|
-
import { mkdir as mkdir23, readFile as
|
|
19540
|
+
import { mkdir as mkdir23, readFile as readFile33, writeFile as writeFile9 } from "fs/promises";
|
|
19042
19541
|
import { homedir as homedir17 } from "os";
|
|
19043
19542
|
import { join as join28 } from "path";
|
|
19044
19543
|
import { parse } from "yaml";
|
|
@@ -19119,7 +19618,7 @@ async function approveBuilds(profileDir) {
|
|
|
19119
19618
|
const workspaceFile = join28(profileDir, "pnpm-workspace.yaml");
|
|
19120
19619
|
let existing = "";
|
|
19121
19620
|
try {
|
|
19122
|
-
existing = await
|
|
19621
|
+
existing = await readFile33(workspaceFile, "utf8");
|
|
19123
19622
|
} catch {
|
|
19124
19623
|
}
|
|
19125
19624
|
const allowBuilds = [
|
|
@@ -19147,8 +19646,8 @@ async function preserveInstalledPnpmVersion(profileDir) {
|
|
|
19147
19646
|
let modulesText;
|
|
19148
19647
|
try {
|
|
19149
19648
|
[packageText, modulesText] = await Promise.all([
|
|
19150
|
-
|
|
19151
|
-
|
|
19649
|
+
readFile33(packageFile, "utf8"),
|
|
19650
|
+
readFile33(modulesFile, "utf8")
|
|
19152
19651
|
]);
|
|
19153
19652
|
} catch {
|
|
19154
19653
|
return;
|