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/plugin.js
CHANGED
|
@@ -817,14 +817,14 @@ var init_acp_adapter = __esm({
|
|
|
817
817
|
return;
|
|
818
818
|
}
|
|
819
819
|
if (response.usage) {
|
|
820
|
-
const
|
|
820
|
+
const usage4 = {
|
|
821
821
|
type: "usage",
|
|
822
822
|
...typeof response.usage.inputTokens === "number" ? { inputTokens: response.usage.inputTokens } : {},
|
|
823
823
|
...typeof response.usage.outputTokens === "number" ? { outputTokens: response.usage.outputTokens } : {},
|
|
824
824
|
...typeof response.usage.cachedReadTokens === "number" ? { cacheReadTokens: response.usage.cachedReadTokens } : {},
|
|
825
825
|
...typeof response.usage.cachedWriteTokens === "number" ? { cacheWriteTokens: response.usage.cachedWriteTokens } : {}
|
|
826
826
|
};
|
|
827
|
-
channel.push(
|
|
827
|
+
channel.push(usage4);
|
|
828
828
|
}
|
|
829
829
|
})().catch((error) => {
|
|
830
830
|
if (!stopRequested.value) {
|
|
@@ -2253,11 +2253,11 @@ function translateToolResult(data) {
|
|
|
2253
2253
|
}
|
|
2254
2254
|
function translateAssistantMessage(data) {
|
|
2255
2255
|
if (!isRecord3(data) || !isRecord3(data.usage)) return [];
|
|
2256
|
-
const
|
|
2257
|
-
const inputTokens = typeof
|
|
2258
|
-
const outputTokens = typeof
|
|
2259
|
-
const cacheReadTokens = typeof
|
|
2260
|
-
const cacheWriteTokens = typeof
|
|
2256
|
+
const usage4 = data.usage;
|
|
2257
|
+
const inputTokens = typeof usage4.inputTokens === "number" ? usage4.inputTokens : void 0;
|
|
2258
|
+
const outputTokens = typeof usage4.outputTokens === "number" ? usage4.outputTokens : void 0;
|
|
2259
|
+
const cacheReadTokens = typeof usage4.cacheReadTokens === "number" ? usage4.cacheReadTokens : void 0;
|
|
2260
|
+
const cacheWriteTokens = typeof usage4.cacheWriteTokens === "number" ? usage4.cacheWriteTokens : void 0;
|
|
2261
2261
|
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadTokens === void 0 && cacheWriteTokens === void 0) return [];
|
|
2262
2262
|
return [
|
|
2263
2263
|
{
|
|
@@ -4009,7 +4009,7 @@ var PermissionPolicyStore = class {
|
|
|
4009
4009
|
|
|
4010
4010
|
// src/bot/notification-preference-store.ts
|
|
4011
4011
|
import { readFile as readFile7 } from "fs/promises";
|
|
4012
|
-
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval"]);
|
|
4012
|
+
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval", "urgent"]);
|
|
4013
4013
|
var NotificationPreferenceStore = class {
|
|
4014
4014
|
constructor(path) {
|
|
4015
4015
|
this.path = path;
|
|
@@ -4038,7 +4038,7 @@ var NotificationPreferenceStore = class {
|
|
|
4038
4038
|
get(scope) {
|
|
4039
4039
|
const value = this.data.scopes[scope];
|
|
4040
4040
|
if (value === void 0 || value === false) return void 0;
|
|
4041
|
-
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds] };
|
|
4041
|
+
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds], sinks: [...value.sinks] };
|
|
4042
4042
|
}
|
|
4043
4043
|
resolve(scope, fallback) {
|
|
4044
4044
|
const value = this.data.scopes[scope];
|
|
@@ -4083,6 +4083,7 @@ function normalizePreference(value) {
|
|
|
4083
4083
|
...typeof input.target === "string" && input.target.trim() ? { target: input.target.trim() } : {},
|
|
4084
4084
|
events,
|
|
4085
4085
|
mentionUserIds: [...new Set((input.mentionUserIds ?? []).filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()))],
|
|
4086
|
+
sinks: [...new Set((input.sinks ?? []).filter((id) => typeof id === "string" && id.trim().length > 0).map((id) => id.trim()))],
|
|
4086
4087
|
approvalReminderMs
|
|
4087
4088
|
};
|
|
4088
4089
|
}
|
|
@@ -4559,6 +4560,7 @@ function statusCardMarkdownFor(input, locale) {
|
|
|
4559
4560
|
zh ? `\u{1F39A}\uFE0F **\u6267\u884C\u6A21\u5F0F**\uFF1A\`${input.executionMode ?? "balanced"}\`` : `\u{1F39A}\uFE0F **Execution mode**: \`${input.executionMode ?? "balanced"}\``,
|
|
4560
4561
|
zh ? `\u{1F6E1}\uFE0F **\u5DE5\u5177\u6743\u9650**\uFF1A\`${input.permissionPolicy ?? "ask"}\`` : `\u{1F6E1}\uFE0F **Tool permission**: \`${input.permissionPolicy ?? "ask"}\``,
|
|
4561
4562
|
...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"],
|
|
4563
|
+
...input.notificationChannels && input.notificationChannels.length > 0 ? [zh ? `\u{1F4E4} **\u51FA\u7AD9\u6E20\u9053**\uFF1A\`${input.notificationChannels.join(",")}\`` : `\u{1F4E4} **Outbound channels**: \`${input.notificationChannels.join(",")}\``] : [],
|
|
4562
4564
|
...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`] : [],
|
|
4563
4565
|
...input.role ? [`\u{1F3AD} **role**\uFF1A${input.role}`] : [],
|
|
4564
4566
|
`\u{1F516} **version**\uFF1A\`${input.version}\``,
|
|
@@ -4975,8 +4977,8 @@ function renderStandard(state, now, locale, maxTools) {
|
|
|
4975
4977
|
} else if (state.terminal === "done" && !hasAnswer(state)) {
|
|
4976
4978
|
elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
4977
4979
|
} else if (state.terminal === "done") {
|
|
4978
|
-
const
|
|
4979
|
-
if (
|
|
4980
|
+
const usage4 = usageLine(state);
|
|
4981
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
4980
4982
|
}
|
|
4981
4983
|
if (state.finalDeliveryError) {
|
|
4982
4984
|
elements.push(finalDeliveryFailureLine(locale));
|
|
@@ -5042,8 +5044,8 @@ function renderDetailed(state, now, locale, maxTools) {
|
|
|
5042
5044
|
} else if (state.terminal === "error") {
|
|
5043
5045
|
elements.push(runFailureLine(locale));
|
|
5044
5046
|
} else if (state.terminal === "done") {
|
|
5045
|
-
const
|
|
5046
|
-
if (
|
|
5047
|
+
const usage4 = usageLine(state);
|
|
5048
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
5047
5049
|
if (!hasAnswer(state)) elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
5048
5050
|
}
|
|
5049
5051
|
if (state.finalDeliveryError) {
|
|
@@ -8066,7 +8068,7 @@ async function handleNotify(args, ctx) {
|
|
|
8066
8068
|
}
|
|
8067
8069
|
|
|
8068
8070
|
// src/commands/notifications.ts
|
|
8069
|
-
var ALL_EVENTS = ["completed", "failed", "approval"];
|
|
8071
|
+
var ALL_EVENTS = ["completed", "failed", "approval", "urgent"];
|
|
8070
8072
|
async function reply5(ctx, zh, en) {
|
|
8071
8073
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
8072
8074
|
}
|
|
@@ -8104,6 +8106,7 @@ async function handleNotifications(args, ctx) {
|
|
|
8104
8106
|
let events = [...ALL_EVENTS];
|
|
8105
8107
|
let mentionUserIds = ctx.senderId ? [ctx.senderId] : [];
|
|
8106
8108
|
let reminderMinutes = 10;
|
|
8109
|
+
let sinks = [];
|
|
8107
8110
|
for (const token of tokens.slice(1)) {
|
|
8108
8111
|
if (token.startsWith("events=")) {
|
|
8109
8112
|
const requested = token.slice(7).split(",").filter(Boolean);
|
|
@@ -8115,6 +8118,8 @@ async function handleNotifications(args, ctx) {
|
|
|
8115
8118
|
} else if (token.startsWith("remind=")) {
|
|
8116
8119
|
reminderMinutes = Number(token.slice(7));
|
|
8117
8120
|
if (!Number.isInteger(reminderMinutes) || reminderMinutes < 1 || reminderMinutes > 1440) return usage(ctx);
|
|
8121
|
+
} else if (token.startsWith("sinks=")) {
|
|
8122
|
+
sinks = token.slice(6).split(",").map((id) => id.trim()).filter(Boolean);
|
|
8118
8123
|
} else if (!target) {
|
|
8119
8124
|
target = token === "current" ? void 0 : token;
|
|
8120
8125
|
} else return usage(ctx);
|
|
@@ -8133,50 +8138,297 @@ async function handleNotifications(args, ctx) {
|
|
|
8133
8138
|
...target ? { target } : {},
|
|
8134
8139
|
events,
|
|
8135
8140
|
mentionUserIds: [...new Set(mentionUserIds)],
|
|
8136
|
-
approvalReminderMs: reminderMinutes * 6e4
|
|
8141
|
+
approvalReminderMs: reminderMinutes * 6e4,
|
|
8142
|
+
sinks: [...new Set(sinks)]
|
|
8137
8143
|
};
|
|
8138
8144
|
await store.set(ctx.scope, value);
|
|
8139
8145
|
await reply5(ctx, `\u5DF2\u5F00\u542F\u3002${describe(value, true)}`, `Enabled. ${describe(value, false)}`);
|
|
8140
8146
|
}
|
|
8141
8147
|
function describe(value, zh) {
|
|
8142
8148
|
const minutes = value.approvalReminderMs / 6e4;
|
|
8143
|
-
|
|
8149
|
+
const sinks = value.sinks.join(",") || "none";
|
|
8150
|
+
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.`;
|
|
8144
8151
|
}
|
|
8145
8152
|
async function usage(ctx) {
|
|
8146
8153
|
await reply5(
|
|
8147
8154
|
ctx,
|
|
8148
|
-
"\u7528\u6CD5\uFF1A`/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`",
|
|
8149
|
-
"Usage: `/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`"
|
|
8155
|
+
"\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]]`",
|
|
8156
|
+
"Usage: `/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval,urgent] [mentions=self,ou_x|none] [sinks=channelId1,channelId2] [remind=10]]`"
|
|
8150
8157
|
);
|
|
8151
8158
|
}
|
|
8152
8159
|
|
|
8160
|
+
// src/notify/sinks/channel-store.ts
|
|
8161
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
8162
|
+
var SINK_TYPES = ["telegram", "wecom"];
|
|
8163
|
+
var NotificationChannelStore = class {
|
|
8164
|
+
constructor(path) {
|
|
8165
|
+
this.path = path;
|
|
8166
|
+
}
|
|
8167
|
+
path;
|
|
8168
|
+
data = { schemaVersion: 1, channels: {} };
|
|
8169
|
+
saving = Promise.resolve();
|
|
8170
|
+
async load() {
|
|
8171
|
+
try {
|
|
8172
|
+
const parsed = JSON.parse(await readFile11(this.path, "utf8"));
|
|
8173
|
+
const channels = {};
|
|
8174
|
+
for (const [id, value] of Object.entries(parsed.channels ?? {})) {
|
|
8175
|
+
const normalized = normalizeChannel(id, value);
|
|
8176
|
+
if (normalized) channels[id] = normalized;
|
|
8177
|
+
}
|
|
8178
|
+
this.data = { schemaVersion: 1, channels };
|
|
8179
|
+
} catch (error) {
|
|
8180
|
+
if (error.code !== "ENOENT") throw error;
|
|
8181
|
+
this.data = { schemaVersion: 1, channels: {} };
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
list() {
|
|
8185
|
+
return Object.values(this.data.channels).map((channel) => ({ ...channel }));
|
|
8186
|
+
}
|
|
8187
|
+
get(id) {
|
|
8188
|
+
const channel = this.data.channels[id];
|
|
8189
|
+
return channel ? { ...channel } : void 0;
|
|
8190
|
+
}
|
|
8191
|
+
async add(channel) {
|
|
8192
|
+
const normalized = normalizeChannel(channel.id, channel);
|
|
8193
|
+
if (!normalized) throw new Error("invalid notification channel");
|
|
8194
|
+
await this.mutate(() => {
|
|
8195
|
+
this.data.channels[normalized.id] = normalized;
|
|
8196
|
+
});
|
|
8197
|
+
}
|
|
8198
|
+
async update(id, patch) {
|
|
8199
|
+
const existing = this.data.channels[id];
|
|
8200
|
+
if (!existing) return false;
|
|
8201
|
+
const next = normalizeChannel(id, { ...existing, ...patch });
|
|
8202
|
+
if (!next) return false;
|
|
8203
|
+
await this.mutate(() => {
|
|
8204
|
+
this.data.channels[id] = next;
|
|
8205
|
+
});
|
|
8206
|
+
return true;
|
|
8207
|
+
}
|
|
8208
|
+
async setEnabled(id, enabled) {
|
|
8209
|
+
return this.update(id, { enabled });
|
|
8210
|
+
}
|
|
8211
|
+
async remove(id) {
|
|
8212
|
+
const existing = this.data.channels[id];
|
|
8213
|
+
if (!existing) return false;
|
|
8214
|
+
await this.mutate(() => {
|
|
8215
|
+
delete this.data.channels[id];
|
|
8216
|
+
});
|
|
8217
|
+
return true;
|
|
8218
|
+
}
|
|
8219
|
+
async flush() {
|
|
8220
|
+
await this.saving;
|
|
8221
|
+
}
|
|
8222
|
+
async mutate(fn) {
|
|
8223
|
+
const snapshot = JSON.stringify(this.data, null, 2) + "\n";
|
|
8224
|
+
const previous = JSON.stringify(this.data);
|
|
8225
|
+
fn();
|
|
8226
|
+
this.saving = this.saving.then(async () => {
|
|
8227
|
+
await writeFileAtomic(this.path, snapshot, { mode: 384 });
|
|
8228
|
+
}).catch((error) => {
|
|
8229
|
+
try {
|
|
8230
|
+
this.data = JSON.parse(previous);
|
|
8231
|
+
} catch {
|
|
8232
|
+
}
|
|
8233
|
+
log.fail("notification-channels", error, { step: "persist" });
|
|
8234
|
+
throw error;
|
|
8235
|
+
});
|
|
8236
|
+
await this.saving;
|
|
8237
|
+
}
|
|
8238
|
+
};
|
|
8239
|
+
function normalizeChannel(id, value) {
|
|
8240
|
+
if (!value || typeof value !== "object" || !id.trim()) return void 0;
|
|
8241
|
+
const input = value;
|
|
8242
|
+
const type = input.type;
|
|
8243
|
+
if (type !== "telegram" && type !== "wecom") return void 0;
|
|
8244
|
+
const label2 = typeof input.label === "string" ? input.label.trim() : "";
|
|
8245
|
+
const destination = typeof input.destination === "string" ? input.destination.trim() : "";
|
|
8246
|
+
const secret = typeof input.secret === "string" ? input.secret.trim() : "";
|
|
8247
|
+
if (!label2 || !destination || !secret) return void 0;
|
|
8248
|
+
const mentionMap = {};
|
|
8249
|
+
if (input.mentionMap && typeof input.mentionMap === "object") {
|
|
8250
|
+
for (const [from, to] of Object.entries(input.mentionMap)) {
|
|
8251
|
+
if (typeof to === "string" && to.trim()) mentionMap[from] = to.trim();
|
|
8252
|
+
}
|
|
8253
|
+
}
|
|
8254
|
+
return {
|
|
8255
|
+
id: id.trim(),
|
|
8256
|
+
type,
|
|
8257
|
+
label: label2,
|
|
8258
|
+
destination,
|
|
8259
|
+
secret,
|
|
8260
|
+
enabled: input.enabled !== false,
|
|
8261
|
+
...Object.keys(mentionMap).length > 0 ? { mentionMap } : {}
|
|
8262
|
+
};
|
|
8263
|
+
}
|
|
8264
|
+
|
|
8265
|
+
// src/notify/sinks/types.ts
|
|
8266
|
+
var SINK_HTTP_TIMEOUT_MS = 1e4;
|
|
8267
|
+
function maskChannel(channel, includeId = true) {
|
|
8268
|
+
const id = includeId ? `${channel.id} \xB7 ` : "";
|
|
8269
|
+
const dest = maskSecret(channel.destination);
|
|
8270
|
+
return `${id}${channel.type} \xB7 ${channel.label} \u2192 ${dest}`;
|
|
8271
|
+
}
|
|
8272
|
+
function maskSecret(value) {
|
|
8273
|
+
if (!value) return "(unset)";
|
|
8274
|
+
if (value.length <= 6) return `${value[0]}{redacted}`;
|
|
8275
|
+
return `${value.slice(0, 3)}\u2026${value.slice(-3)}`;
|
|
8276
|
+
}
|
|
8277
|
+
|
|
8278
|
+
// src/commands/channels.ts
|
|
8279
|
+
async function reply6(ctx, zh, en) {
|
|
8280
|
+
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
8281
|
+
}
|
|
8282
|
+
function requireAdmin3(ctx) {
|
|
8283
|
+
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
8284
|
+
void reply6(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u7BA1\u7406\u901A\u77E5\u6E20\u9053\u3002", "Only admins can manage notification channels.");
|
|
8285
|
+
return false;
|
|
8286
|
+
}
|
|
8287
|
+
async function handleChannels(args, ctx) {
|
|
8288
|
+
if (!requireAdmin3(ctx)) return;
|
|
8289
|
+
const store = ctx.notificationChannels;
|
|
8290
|
+
if (!store) {
|
|
8291
|
+
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.");
|
|
8292
|
+
return;
|
|
8293
|
+
}
|
|
8294
|
+
const [sub, ...rest] = args.trim().split(/\s+/);
|
|
8295
|
+
switch (sub) {
|
|
8296
|
+
case "list":
|
|
8297
|
+
return listChannels(ctx, store);
|
|
8298
|
+
case "show": {
|
|
8299
|
+
const id = rest[0];
|
|
8300
|
+
if (!id) return usage2(ctx);
|
|
8301
|
+
const channel = store.get(id);
|
|
8302
|
+
if (!channel) return notFound(ctx, id);
|
|
8303
|
+
return showChannel(ctx, channel);
|
|
8304
|
+
}
|
|
8305
|
+
case "add":
|
|
8306
|
+
return addChannel(ctx, store, rest.join(" "));
|
|
8307
|
+
case "remove": {
|
|
8308
|
+
const id = rest[0];
|
|
8309
|
+
if (!id) return usage2(ctx);
|
|
8310
|
+
const ok = await store.remove(id);
|
|
8311
|
+
return ok ? reply6(ctx, `\u5DF2\u79FB\u9664\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002`, `Removed notification channel \`${id}\`.`) : notFound(ctx, id);
|
|
8312
|
+
}
|
|
8313
|
+
case "enable":
|
|
8314
|
+
case "disable": {
|
|
8315
|
+
const id = rest[0];
|
|
8316
|
+
if (!id) return usage2(ctx);
|
|
8317
|
+
const enabled = sub === "enable";
|
|
8318
|
+
const ok = await store.setEnabled(id, enabled);
|
|
8319
|
+
if (!ok) return notFound(ctx, id);
|
|
8320
|
+
return reply6(ctx, `\u5DF2${enabled ? "\u542F\u7528" : "\u505C\u7528"}\u901A\u77E5\u6E20\u9053 \`${id}\`\u3002`, `${enabled ? "Enabled" : "Disabled"} notification channel \`${id}\`.`);
|
|
8321
|
+
}
|
|
8322
|
+
default:
|
|
8323
|
+
return usage2(ctx);
|
|
8324
|
+
}
|
|
8325
|
+
}
|
|
8326
|
+
async function listChannels(ctx, store) {
|
|
8327
|
+
const channels = store.list();
|
|
8328
|
+
if (channels.length === 0) {
|
|
8329
|
+
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.");
|
|
8330
|
+
return;
|
|
8331
|
+
}
|
|
8332
|
+
const lines = channels.map((channel) => `- ${channel.enabled ? "\u{1F7E2}" : "\u26AA\uFE0F"} \`${channel.id}\` \u2014 ${channel.type} \xB7 ${channel.label} \xB7 ${maskSecret(channel.destination)}`);
|
|
8333
|
+
await reply6(ctx, ["**\u5DF2\u914D\u7F6E\u901A\u77E5\u6E20\u9053**", "", ...lines].join("\n"), ["**Configured notification channels**", "", ...lines].join("\n"));
|
|
8334
|
+
}
|
|
8335
|
+
async function showChannel(ctx, channel) {
|
|
8336
|
+
const zh = `**${channel.id}**
|
|
8337
|
+
- \u7C7B\u578B\uFF1A\`${channel.type}\`
|
|
8338
|
+
- \u540D\u79F0\uFF1A${channel.label}
|
|
8339
|
+
- \u76EE\u6807\uFF1A\`${maskSecret(channel.destination)}\`
|
|
8340
|
+
- \u72B6\u6001\uFF1A${channel.enabled ? "\u542F\u7528" : "\u505C\u7528"}
|
|
8341
|
+
- \u5BC6\u94A5\uFF1A\`${maskSecret(channel.secret)}\``;
|
|
8342
|
+
const en = `**${channel.id}**
|
|
8343
|
+
- Type: \`${channel.type}\`
|
|
8344
|
+
- Label: ${channel.label}
|
|
8345
|
+
- Target: \`${maskSecret(channel.destination)}\`
|
|
8346
|
+
- Status: ${channel.enabled ? "enabled" : "disabled"}
|
|
8347
|
+
- Secret: \`${maskSecret(channel.secret)}\``;
|
|
8348
|
+
await reply6(ctx, zh, en);
|
|
8349
|
+
}
|
|
8350
|
+
async function addChannel(ctx, store, raw) {
|
|
8351
|
+
const parsed = parseAddArgs(raw);
|
|
8352
|
+
if (!parsed || !parsed.type || !parsed.label || !parsed.destination || !parsed.secret) {
|
|
8353
|
+
await usage2(ctx);
|
|
8354
|
+
return;
|
|
8355
|
+
}
|
|
8356
|
+
if (!SINK_TYPES.includes(parsed.type)) {
|
|
8357
|
+
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("|")}\`.`);
|
|
8358
|
+
return;
|
|
8359
|
+
}
|
|
8360
|
+
const id = parsed.id ?? `${parsed.type}-${slugify(parsed.label)}`;
|
|
8361
|
+
if (store.get(id)) {
|
|
8362
|
+
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}\`.`);
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
8365
|
+
const channel = {
|
|
8366
|
+
id,
|
|
8367
|
+
type: parsed.type,
|
|
8368
|
+
label: parsed.label,
|
|
8369
|
+
destination: parsed.destination,
|
|
8370
|
+
secret: parsed.secret,
|
|
8371
|
+
enabled: true
|
|
8372
|
+
};
|
|
8373
|
+
await store.add(channel);
|
|
8374
|
+
await reply6(ctx, `\u5DF2\u6DFB\u52A0\u901A\u77E5\u6E20\u9053 ${maskChannel(channel)}\u3002`, `Added notification channel ${maskChannel(channel)}.`);
|
|
8375
|
+
}
|
|
8376
|
+
async function notFound(ctx, id) {
|
|
8377
|
+
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\`.`);
|
|
8378
|
+
}
|
|
8379
|
+
async function usage2(ctx) {
|
|
8380
|
+
await reply6(
|
|
8381
|
+
ctx,
|
|
8382
|
+
"\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>`",
|
|
8383
|
+
"Usage: `/channels list|show <id>|add <telegram|wecom> <label> [--id <id>] --destination <target> --secret <token>|remove <id>|enable <id>|disable <id>`"
|
|
8384
|
+
);
|
|
8385
|
+
}
|
|
8386
|
+
function parseAddArgs(raw) {
|
|
8387
|
+
if (!raw.trim()) return void 0;
|
|
8388
|
+
const idMatch = raw.match(/--id\s+(\S+)/);
|
|
8389
|
+
const destMatch = raw.match(/--destination\s+(\S+)/);
|
|
8390
|
+
const secretMatch = raw.match(/--secret\s+(\S+)/);
|
|
8391
|
+
const positionals = raw.replace(/\s*--id\s+\S+/, " ").replace(/\s*--destination\s+\S+/, " ").replace(/\s*--secret\s+\S+/, " ").trim().split(/\s+/).filter(Boolean);
|
|
8392
|
+
const [type, label2] = positionals;
|
|
8393
|
+
return {
|
|
8394
|
+
...idMatch ? { id: idMatch[1] } : {},
|
|
8395
|
+
...type ? { type } : {},
|
|
8396
|
+
...label2 ? { label: label2 } : {},
|
|
8397
|
+
...destMatch ? { destination: destMatch[1] } : {},
|
|
8398
|
+
...secretMatch ? { secret: secretMatch[1] } : {}
|
|
8399
|
+
};
|
|
8400
|
+
}
|
|
8401
|
+
function slugify(value) {
|
|
8402
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "channel";
|
|
8403
|
+
}
|
|
8404
|
+
|
|
8153
8405
|
// src/commands/replies.ts
|
|
8154
8406
|
async function handleReplies(args, ctx) {
|
|
8155
8407
|
const store = ctx.replyPolicies;
|
|
8156
|
-
if (!store) return
|
|
8408
|
+
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.");
|
|
8157
8409
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
8158
|
-
if (tokens.length === 0 || tokens[0] === "show") return
|
|
8159
|
-
if (!await canManageReplies(ctx)) return
|
|
8410
|
+
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));
|
|
8411
|
+
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.");
|
|
8160
8412
|
if (tokens[0] === "default") {
|
|
8161
8413
|
await store.set(ctx.scope, void 0);
|
|
8162
|
-
return
|
|
8414
|
+
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.");
|
|
8163
8415
|
}
|
|
8164
|
-
if (tokens[0] !== "set") return
|
|
8416
|
+
if (tokens[0] !== "set") return usage3(ctx);
|
|
8165
8417
|
const policy = { ...store.get(ctx.scope) };
|
|
8166
8418
|
for (const token of tokens.slice(1)) {
|
|
8167
8419
|
const [key, raw, extra] = token.split("=");
|
|
8168
|
-
if (!raw || extra !== void 0) return
|
|
8420
|
+
if (!raw || extra !== void 0) return usage3(ctx);
|
|
8169
8421
|
const seconds = Number(raw);
|
|
8170
|
-
if (!Number.isInteger(seconds)) return
|
|
8422
|
+
if (!Number.isInteger(seconds)) return usage3(ctx);
|
|
8171
8423
|
if (key === "merge" && seconds >= 0 && seconds <= 300) policy.mergeWindowMs = seconds * 1e3;
|
|
8172
8424
|
else if (key === "batch" && seconds >= 1 && seconds <= 20) policy.maxBatchSize = seconds;
|
|
8173
8425
|
else if (key === "interval" && seconds >= 0 && seconds <= 3600) policy.minIntervalMs = seconds * 1e3;
|
|
8174
8426
|
else if (key === "dedupe" && seconds >= 0 && seconds <= 3600) policy.dedupeWindowMs = seconds * 1e3;
|
|
8175
|
-
else return
|
|
8427
|
+
else return usage3(ctx);
|
|
8176
8428
|
}
|
|
8177
|
-
if (tokens.length === 1) return
|
|
8429
|
+
if (tokens.length === 1) return usage3(ctx);
|
|
8178
8430
|
await store.set(ctx.scope, policy);
|
|
8179
|
-
await
|
|
8431
|
+
await reply7(ctx, `\u5DF2\u8BBE\u7F6E\u3002${describe2(policy, true, true)}`, `Updated. ${describe2(policy, true, false)}`);
|
|
8180
8432
|
}
|
|
8181
8433
|
async function canManageReplies(ctx) {
|
|
8182
8434
|
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
@@ -8190,10 +8442,10 @@ async function canManageReplies(ctx) {
|
|
|
8190
8442
|
function describe2(policy, configured, zh) {
|
|
8191
8443
|
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.`;
|
|
8192
8444
|
}
|
|
8193
|
-
async function
|
|
8194
|
-
await
|
|
8445
|
+
async function usage3(ctx) {
|
|
8446
|
+
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]`");
|
|
8195
8447
|
}
|
|
8196
|
-
async function
|
|
8448
|
+
async function reply7(ctx, zh, en) {
|
|
8197
8449
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
8198
8450
|
}
|
|
8199
8451
|
|
|
@@ -8428,7 +8680,8 @@ var COMMAND_CATALOG = [
|
|
|
8428
8680
|
{ 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)" },
|
|
8429
8681
|
{ 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" },
|
|
8430
8682
|
{ 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)" },
|
|
8431
|
-
{ names: ["/notifications"], area: "communication", zh: "`[show|off|on \u2026]` \u2014 scope \u63D0\u9192\u7B56\u7565", en: "`[show|off|on \u2026]` \u2014 scope notification policy" },
|
|
8683
|
+
{ 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=`)" },
|
|
8684
|
+
{ 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)" },
|
|
8432
8685
|
{ 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" },
|
|
8433
8686
|
{ names: ["/retention"], area: "session", zh: "`[N|default]` \u2014 \u4F1A\u8BDD\u4FDD\u7559\u6761\u6570", en: "`[N|default]` \u2014 retained live-message count" },
|
|
8434
8687
|
{ names: ["/archive"], area: "session", zh: "`[note]|send|list|clean \u2026` \u2014 \u7BA1\u7406\u5F52\u6863", en: "`[note]|send|list|clean \u2026` \u2014 manage archives" },
|
|
@@ -8511,6 +8764,7 @@ var HELP = [
|
|
|
8511
8764
|
"- `/notify <scope|chatId> <text>` \u2014 \u8DE8\u4F1A\u8BDD\u53D1\u9001\u901A\u77E5\uFF08\u7BA1\u7406\u5458\uFF09",
|
|
8512
8765
|
"- `/notify list` \u2014 \u67E5\u770B\u5DF2\u6CE8\u518C scope",
|
|
8513
8766
|
"- `/notifications [show|off|on \u2026]` \u2014 \u914D\u7F6E\u5F53\u524D scope \u7684\u5B8C\u6210 / \u5931\u8D25 / \u5BA1\u6279\u63D0\u9192",
|
|
8767
|
+
"- `/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",
|
|
8514
8768
|
"- `/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",
|
|
8515
8769
|
"- `/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",
|
|
8516
8770
|
"- `/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",
|
|
@@ -8572,7 +8826,7 @@ var HELP_EN = [
|
|
|
8572
8826
|
"- `/invite user|admin|group <id>` \u2014 manage access allowlists",
|
|
8573
8827
|
"- `/help` \u2014 show this help"
|
|
8574
8828
|
].join("\n");
|
|
8575
|
-
async function
|
|
8829
|
+
async function reply8(ctx, zhCn, enUs) {
|
|
8576
8830
|
await ctx.channel.sendMarkdown(ctx.chatId, enUs === void 0 ? zhCn : bilingualMarkdown(zhCn, enUs, ctx.languagePolicies?.get().plain), {
|
|
8577
8831
|
replyTo: ctx.messageId
|
|
8578
8832
|
});
|
|
@@ -8581,7 +8835,7 @@ async function handleNew(_args, ctx) {
|
|
|
8581
8835
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8582
8836
|
const interrupted = await ctx.activeRuns.interruptWorkspace(ctx.scope, cwd);
|
|
8583
8837
|
ctx.sessions.clear(ctx.scope, cwd);
|
|
8584
|
-
await
|
|
8838
|
+
await reply8(
|
|
8585
8839
|
ctx,
|
|
8586
8840
|
interrupted > 0 ? `\u5DF2\u4E2D\u65AD ${String(interrupted)} \u4E2A\u4EFB\u52A1\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002` : "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002",
|
|
8587
8841
|
interrupted > 0 ? `Interrupted ${String(interrupted)} task(s) and started a new session.` : "Started a new session."
|
|
@@ -8610,11 +8864,11 @@ function groupAppLink(chatId) {
|
|
|
8610
8864
|
async function handleNewGroup(args, ctx) {
|
|
8611
8865
|
const name2 = args.trim();
|
|
8612
8866
|
if (!name2) {
|
|
8613
|
-
await
|
|
8867
|
+
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");
|
|
8614
8868
|
return;
|
|
8615
8869
|
}
|
|
8616
8870
|
if (name2.length > MAX_GROUP_NAME_LENGTH) {
|
|
8617
|
-
await
|
|
8871
|
+
await reply8(
|
|
8618
8872
|
ctx,
|
|
8619
8873
|
`\u7FA4\u540D\u8FC7\u957F\uFF08\u4E0A\u9650 ${String(MAX_GROUP_NAME_LENGTH)} \u5B57\u7B26\uFF0C\u5F53\u524D ${String(name2.length)}\uFF09\u3002`,
|
|
8620
8874
|
`The group name is too long (limit ${String(MAX_GROUP_NAME_LENGTH)} characters; received ${String(name2.length)}).`
|
|
@@ -8622,11 +8876,11 @@ async function handleNewGroup(args, ctx) {
|
|
|
8622
8876
|
return;
|
|
8623
8877
|
}
|
|
8624
8878
|
if (!ctx.channel.createChat) {
|
|
8625
|
-
await
|
|
8879
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u81EA\u52A8\u5EFA\u7FA4\u3002", "This channel cannot create groups automatically.");
|
|
8626
8880
|
return;
|
|
8627
8881
|
}
|
|
8628
8882
|
if (!ctx.senderId) {
|
|
8629
|
-
await
|
|
8883
|
+
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.");
|
|
8630
8884
|
return;
|
|
8631
8885
|
}
|
|
8632
8886
|
try {
|
|
@@ -8637,7 +8891,7 @@ async function handleNewGroup(args, ctx) {
|
|
|
8637
8891
|
inviteUserIds: [ctx.senderId],
|
|
8638
8892
|
userIdType: "open_id"
|
|
8639
8893
|
});
|
|
8640
|
-
await
|
|
8894
|
+
await reply8(
|
|
8641
8895
|
ctx,
|
|
8642
8896
|
[
|
|
8643
8897
|
`\u2705 \u5DF2\u521B\u5EFA\u7FA4\u804A\uFF1A**${name2}**`,
|
|
@@ -8656,13 +8910,13 @@ async function handleNewGroup(args, ctx) {
|
|
|
8656
8910
|
);
|
|
8657
8911
|
} catch (error) {
|
|
8658
8912
|
const message = error instanceof Error ? error.message : String(error);
|
|
8659
|
-
await
|
|
8913
|
+
await reply8(ctx, `\u274C \u5EFA\u7FA4\u5931\u8D25\uFF1A\`${message}\``, `\u274C Failed to create the group: \`${message}\``);
|
|
8660
8914
|
}
|
|
8661
8915
|
}
|
|
8662
8916
|
async function handleCd(args, ctx) {
|
|
8663
8917
|
const path = args.trim();
|
|
8664
8918
|
if (!path) {
|
|
8665
|
-
await
|
|
8919
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`", "Usage: `/cd <path>`");
|
|
8666
8920
|
return;
|
|
8667
8921
|
}
|
|
8668
8922
|
const normalized = path.replace(/^~/u, "~");
|
|
@@ -8670,13 +8924,13 @@ async function handleCd(args, ctx) {
|
|
|
8670
8924
|
const cwd = resolve3(expanded);
|
|
8671
8925
|
const target = await stat2(cwd).catch(() => void 0);
|
|
8672
8926
|
if (!target?.isDirectory()) {
|
|
8673
|
-
await
|
|
8927
|
+
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}\`.`);
|
|
8674
8928
|
return;
|
|
8675
8929
|
}
|
|
8676
8930
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8677
8931
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
8678
8932
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
8679
|
-
await
|
|
8933
|
+
await reply8(
|
|
8680
8934
|
ctx,
|
|
8681
8935
|
`\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` : ""}`,
|
|
8682
8936
|
`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).` : ""}`
|
|
@@ -8696,7 +8950,7 @@ async function handleWs(args, ctx) {
|
|
|
8696
8950
|
const lines = Object.entries(named).map(
|
|
8697
8951
|
([key, value]) => `- **${key}** \u2192 \`${value}\`${value === current ? " \u2190 \u5F53\u524D" : ""}`
|
|
8698
8952
|
);
|
|
8699
|
-
await
|
|
8953
|
+
await reply8(
|
|
8700
8954
|
ctx,
|
|
8701
8955
|
[
|
|
8702
8956
|
`\u5F53\u524D cwd\uFF1A\`${current}\``,
|
|
@@ -8713,29 +8967,29 @@ async function handleWs(args, ctx) {
|
|
|
8713
8967
|
}
|
|
8714
8968
|
if (sub === "save") {
|
|
8715
8969
|
if (!name2) {
|
|
8716
|
-
await
|
|
8970
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`", "Usage: `/ws save <name>`");
|
|
8717
8971
|
return;
|
|
8718
8972
|
}
|
|
8719
8973
|
const current = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8720
8974
|
ctx.workspaces.saveNamed(name2, current);
|
|
8721
|
-
await
|
|
8975
|
+
await reply8(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}** \u2192 \`${current}\``, `Saved workspace: **${name2}** \u2192 \`${current}\``);
|
|
8722
8976
|
return;
|
|
8723
8977
|
}
|
|
8724
8978
|
if (sub === "use") {
|
|
8725
8979
|
if (!name2) {
|
|
8726
|
-
await
|
|
8980
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`", "Usage: `/ws use <name>`");
|
|
8727
8981
|
return;
|
|
8728
8982
|
}
|
|
8729
8983
|
const cwd = ctx.workspaces.getNamed(name2);
|
|
8730
8984
|
if (!cwd) {
|
|
8731
|
-
await
|
|
8985
|
+
await reply8(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}**`, `Workspace not found: **${name2}**`);
|
|
8732
8986
|
return;
|
|
8733
8987
|
}
|
|
8734
8988
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8735
8989
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
8736
8990
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
8737
8991
|
ctx.workspaces.touchNamed(name2);
|
|
8738
|
-
await
|
|
8992
|
+
await reply8(
|
|
8739
8993
|
ctx,
|
|
8740
8994
|
`\u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}** \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` : ""}`,
|
|
8741
8995
|
`Switched to workspace **${name2}** \u2192 \`${cwd}\`; its independent session will resume.${interrupted > 0 ? ` Interrupted ${String(interrupted)} running task(s) in the previous workspace (session data was preserved).` : ""}`
|
|
@@ -8744,14 +8998,14 @@ async function handleWs(args, ctx) {
|
|
|
8744
8998
|
}
|
|
8745
8999
|
if (sub === "remove") {
|
|
8746
9000
|
if (!name2) {
|
|
8747
|
-
await
|
|
9001
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`", "Usage: `/ws remove <name>`");
|
|
8748
9002
|
return;
|
|
8749
9003
|
}
|
|
8750
9004
|
const removed = ctx.workspaces.removeNamed(name2);
|
|
8751
|
-
await
|
|
9005
|
+
await reply8(ctx, removed ? `\u5DF2\u5220\u9664\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}**` : `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}**`, removed ? `Removed workspace: **${name2}**` : `Workspace not found: **${name2}**`);
|
|
8752
9006
|
return;
|
|
8753
9007
|
}
|
|
8754
|
-
await
|
|
9008
|
+
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.");
|
|
8755
9009
|
}
|
|
8756
9010
|
async function statusCardInputFor(ctx) {
|
|
8757
9011
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -8798,6 +9052,7 @@ async function statusCardInputFor(ctx) {
|
|
|
8798
9052
|
permissionPolicy: ctx.permissionPolicies?.get(ctx.scope) ?? "ask",
|
|
8799
9053
|
executionMode: ctx.executionModes?.get(ctx.scope) ?? "balanced",
|
|
8800
9054
|
...notificationPreference ? { notificationPreference } : {},
|
|
9055
|
+
...ctx.notificationSinks ? { notificationChannels: ctx.notificationSinks.enabledChannels().map((channel) => channel.id) } : {},
|
|
8801
9056
|
...replyPolicy ? { replyPolicy, replyPolicyConfigured: ctx.replyPolicies?.isConfigured(ctx.scope) ?? false } : {},
|
|
8802
9057
|
role: role ? `\`${role.id}\` (${role.name})` : void 0,
|
|
8803
9058
|
metrics,
|
|
@@ -8840,16 +9095,16 @@ async function handleStatus(_args, ctx) {
|
|
|
8840
9095
|
} catch {
|
|
8841
9096
|
}
|
|
8842
9097
|
}
|
|
8843
|
-
await
|
|
9098
|
+
await reply8(ctx, bilingualMarkdown(statusCardMarkdown(input), statusCardMarkdownEnglish(input)));
|
|
8844
9099
|
}
|
|
8845
9100
|
async function handleIsolation(args, ctx) {
|
|
8846
9101
|
if (ctx.chatMode === "p2p") {
|
|
8847
|
-
await
|
|
9102
|
+
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.");
|
|
8848
9103
|
return;
|
|
8849
9104
|
}
|
|
8850
9105
|
const mode = args.trim().toLowerCase();
|
|
8851
9106
|
if (!mode) {
|
|
8852
|
-
await
|
|
9107
|
+
await reply8(
|
|
8853
9108
|
ctx,
|
|
8854
9109
|
`\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`,
|
|
8855
9110
|
`Current group isolation mode: **${ctx.isolationStore?.get(ctx.chatId) ?? "topic"}**. Use \`/isolation group|topic|member\` to switch.`
|
|
@@ -8857,16 +9112,16 @@ async function handleIsolation(args, ctx) {
|
|
|
8857
9112
|
return;
|
|
8858
9113
|
}
|
|
8859
9114
|
if (mode !== "group" && mode !== "topic" && mode !== "member") {
|
|
8860
|
-
await
|
|
9115
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/isolation group|topic|member`", "Usage: `/isolation group|topic|member`");
|
|
8861
9116
|
return;
|
|
8862
9117
|
}
|
|
8863
|
-
if (!
|
|
9118
|
+
if (!requireAdmin4(ctx)) return;
|
|
8864
9119
|
if (!ctx.isolationStore) {
|
|
8865
|
-
await
|
|
9120
|
+
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.");
|
|
8866
9121
|
return;
|
|
8867
9122
|
}
|
|
8868
9123
|
ctx.isolationStore.set(ctx.chatId, mode);
|
|
8869
|
-
await
|
|
9124
|
+
await reply8(
|
|
8870
9125
|
ctx,
|
|
8871
9126
|
`\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`,
|
|
8872
9127
|
`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.`
|
|
@@ -8893,25 +9148,25 @@ async function handleVersion(_args, ctx) {
|
|
|
8893
9148
|
lines.push("\u6700\u65B0\u7248\u672C\u67E5\u8BE2\u6682\u4E0D\u53EF\u7528\uFF08\u7F51\u7EDC / registry \u5F02\u5E38\uFF09\u3002");
|
|
8894
9149
|
english.push("Latest-version lookup is unavailable (network or registry error).");
|
|
8895
9150
|
}
|
|
8896
|
-
await
|
|
9151
|
+
await reply8(ctx, lines.join("\n"), english.join("\n"));
|
|
8897
9152
|
}
|
|
8898
9153
|
async function handleUpgrade(_args, ctx) {
|
|
8899
|
-
if (!
|
|
9154
|
+
if (!requireAdmin4(ctx)) return;
|
|
8900
9155
|
if (!ctx.senderId || !ctx.channelUpdates) {
|
|
8901
|
-
await
|
|
9156
|
+
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.");
|
|
8902
9157
|
return;
|
|
8903
9158
|
}
|
|
8904
9159
|
const result = await ctx.channelUpdates.check({ scope: ctx.scope, actorId: ctx.senderId });
|
|
8905
9160
|
if (result.kind === "unavailable") {
|
|
8906
|
-
await
|
|
9161
|
+
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.");
|
|
8907
9162
|
return;
|
|
8908
9163
|
}
|
|
8909
9164
|
if (result.kind === "current") {
|
|
8910
|
-
await
|
|
9165
|
+
await reply8(ctx, `\u2705 \u5F53\u524D\u5DF2\u662F\u6700\u65B0\u7248\u672C\uFF1A\`${result.current}\`\u3002`, `\u2705 Already up to date: \`${result.current}\`.`);
|
|
8911
9166
|
return;
|
|
8912
9167
|
}
|
|
8913
9168
|
if (!ctx.channel.sendCard) {
|
|
8914
|
-
await
|
|
9169
|
+
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.");
|
|
8915
9170
|
return;
|
|
8916
9171
|
}
|
|
8917
9172
|
await ctx.channel.sendCard(ctx.chatId, renderUpdateCard({
|
|
@@ -8926,9 +9181,9 @@ async function handleUpgrade(_args, ctx) {
|
|
|
8926
9181
|
});
|
|
8927
9182
|
}
|
|
8928
9183
|
async function handleDoctor(_args, ctx) {
|
|
8929
|
-
if (!
|
|
9184
|
+
if (!requireAdmin4(ctx)) return;
|
|
8930
9185
|
if (!ctx.createDiagnosticBundle || !ctx.channel.sendFile) {
|
|
8931
|
-
await
|
|
9186
|
+
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.");
|
|
8932
9187
|
return;
|
|
8933
9188
|
}
|
|
8934
9189
|
let file;
|
|
@@ -8945,7 +9200,7 @@ async function handleDoctor(_args, ctx) {
|
|
|
8945
9200
|
...status.jobs ? { jobs: status.jobs } : {}
|
|
8946
9201
|
}), ctx.diagnosticTimeoutMs?.generate ?? DOCTOR_GENERATE_TIMEOUT_MS);
|
|
8947
9202
|
} catch {
|
|
8948
|
-
await
|
|
9203
|
+
await reply8(
|
|
8949
9204
|
ctx,
|
|
8950
9205
|
"\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",
|
|
8951
9206
|
"\u26A0\uFE0F Failed to generate the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
@@ -8959,21 +9214,21 @@ async function handleDoctor(_args, ctx) {
|
|
|
8959
9214
|
}), ctx.diagnosticTimeoutMs?.upload ?? DOCTOR_UPLOAD_TIMEOUT_MS);
|
|
8960
9215
|
} catch (error) {
|
|
8961
9216
|
if (error instanceof OperationTimeoutError) {
|
|
8962
|
-
await
|
|
9217
|
+
await reply8(
|
|
8963
9218
|
ctx,
|
|
8964
9219
|
"\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",
|
|
8965
9220
|
"\u26A0\uFE0F Diagnostic upload timed out with an unknown result: the file may still arrive later. Check this chat before retrying."
|
|
8966
9221
|
);
|
|
8967
9222
|
return;
|
|
8968
9223
|
}
|
|
8969
|
-
await
|
|
9224
|
+
await reply8(
|
|
8970
9225
|
ctx,
|
|
8971
9226
|
"\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",
|
|
8972
9227
|
"\u26A0\uFE0F Failed to send the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
8973
9228
|
);
|
|
8974
9229
|
return;
|
|
8975
9230
|
}
|
|
8976
|
-
await
|
|
9231
|
+
await reply8(
|
|
8977
9232
|
ctx,
|
|
8978
9233
|
"\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",
|
|
8979
9234
|
"\u2705 The redacted diagnostic bundle was generated and sent. You can download and forward it to a maintainer."
|
|
@@ -8983,18 +9238,18 @@ async function handleResume(_args, ctx) {
|
|
|
8983
9238
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8984
9239
|
const history = ctx.sessions.historyFor(ctx.scope, cwd);
|
|
8985
9240
|
if (history.length === 0) {
|
|
8986
|
-
await
|
|
9241
|
+
await reply8(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002", "This session has no conversation history.");
|
|
8987
9242
|
return;
|
|
8988
9243
|
}
|
|
8989
9244
|
const recent = history.slice(-6).map((message) => {
|
|
8990
9245
|
const speaker = message.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
8991
9246
|
return `${speaker} ${message.content.slice(0, 300)}`;
|
|
8992
9247
|
});
|
|
8993
|
-
await
|
|
9248
|
+
await reply8(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"), [`Current scope: \`${ctx.scope}\``, "", ...recent].join("\n"));
|
|
8994
9249
|
}
|
|
8995
9250
|
async function handleJobs(args, ctx) {
|
|
8996
9251
|
if (!ctx.jobs) {
|
|
8997
|
-
await
|
|
9252
|
+
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.");
|
|
8998
9253
|
return;
|
|
8999
9254
|
}
|
|
9000
9255
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -9002,11 +9257,11 @@ async function handleJobs(args, ctx) {
|
|
|
9002
9257
|
const records = ctx.jobs.list(ctx.scope, cwd, 20);
|
|
9003
9258
|
if (sub === "retry") {
|
|
9004
9259
|
if (!id) {
|
|
9005
|
-
await
|
|
9260
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs retry <\u6D88\u606FID>`", "Usage: `/jobs retry <message-id>`");
|
|
9006
9261
|
return;
|
|
9007
9262
|
}
|
|
9008
9263
|
const queued = await ctx.requeueJob?.(id, ctx.scope, cwd);
|
|
9009
|
-
await
|
|
9264
|
+
await reply8(
|
|
9010
9265
|
ctx,
|
|
9011
9266
|
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`,
|
|
9012
9267
|
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.`
|
|
@@ -9016,19 +9271,19 @@ async function handleJobs(args, ctx) {
|
|
|
9016
9271
|
if (sub === "show") {
|
|
9017
9272
|
const record = id ? ctx.jobs.get(id, ctx.scope, cwd) : void 0;
|
|
9018
9273
|
if (!record) {
|
|
9019
|
-
await
|
|
9274
|
+
await reply8(ctx, `\u5F53\u524D workspace \u672A\u627E\u5230\u4EFB\u52A1\uFF1A**${id ?? ""}**`, `Job not found in this workspace: **${id ?? ""}**`);
|
|
9020
9275
|
return;
|
|
9021
9276
|
}
|
|
9022
|
-
await
|
|
9277
|
+
await reply8(ctx, jobDetail(record, "zh"), jobDetail(record, "en"));
|
|
9023
9278
|
return;
|
|
9024
9279
|
}
|
|
9025
9280
|
if (sub && sub !== "list") {
|
|
9026
|
-
await
|
|
9281
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs [list|show <\u6D88\u606FID>|retry <\u6D88\u606FID>]`", "Usage: `/jobs [list|show <message-id>|retry <message-id>]`");
|
|
9027
9282
|
return;
|
|
9028
9283
|
}
|
|
9029
9284
|
const counts = ctx.jobs.counts(ctx.scope, cwd);
|
|
9030
9285
|
const rows = records.slice(0, 10);
|
|
9031
|
-
await
|
|
9286
|
+
await reply8(
|
|
9032
9287
|
ctx,
|
|
9033
9288
|
[
|
|
9034
9289
|
"**\u4EFB\u52A1\u5BF9\u8D26**",
|
|
@@ -9075,7 +9330,7 @@ async function handleStop(_args, ctx) {
|
|
|
9075
9330
|
});
|
|
9076
9331
|
let stopped = 0;
|
|
9077
9332
|
for (const scope of scopes) stopped += await ctx.activeRuns.interrupt(scope);
|
|
9078
|
-
await
|
|
9333
|
+
await reply8(
|
|
9079
9334
|
ctx,
|
|
9080
9335
|
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",
|
|
9081
9336
|
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."
|
|
@@ -9086,7 +9341,7 @@ async function handleTimeout(args, ctx) {
|
|
|
9086
9341
|
const effectiveMs = ctx.runPolicies.get(ctx.scope) ?? ctx.defaultRunTimeoutMs;
|
|
9087
9342
|
if (!input) {
|
|
9088
9343
|
const minutes2 = effectiveMs > 0 ? Math.round(effectiveMs / 6e4) : 0;
|
|
9089
|
-
await
|
|
9344
|
+
await reply8(
|
|
9090
9345
|
ctx,
|
|
9091
9346
|
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",
|
|
9092
9347
|
minutes2 > 0 ? `Session idle timeout: stop after ${minutes2} minutes without activity events. Use \`/timeout <N|off|default>\` to change it.` : "Session idle timeout: off."
|
|
@@ -9095,27 +9350,27 @@ async function handleTimeout(args, ctx) {
|
|
|
9095
9350
|
}
|
|
9096
9351
|
if (input === "off") {
|
|
9097
9352
|
ctx.runPolicies.set(ctx.scope, 0);
|
|
9098
|
-
await
|
|
9353
|
+
await reply8(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\u3002", "Disabled the session idle timeout.");
|
|
9099
9354
|
return;
|
|
9100
9355
|
}
|
|
9101
9356
|
if (input === "default") {
|
|
9102
9357
|
ctx.runPolicies.clear(ctx.scope);
|
|
9103
|
-
await
|
|
9358
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u7A7A\u95F2\u8D85\u65F6\u3002", "Restored the default idle timeout.");
|
|
9104
9359
|
return;
|
|
9105
9360
|
}
|
|
9106
9361
|
const minutes = Number(input);
|
|
9107
9362
|
if (!Number.isInteger(minutes) || minutes <= 0) {
|
|
9108
|
-
await
|
|
9363
|
+
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.");
|
|
9109
9364
|
return;
|
|
9110
9365
|
}
|
|
9111
9366
|
ctx.runPolicies.set(ctx.scope, minutes * 6e4);
|
|
9112
|
-
await
|
|
9367
|
+
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.`);
|
|
9113
9368
|
}
|
|
9114
9369
|
async function handleConcurrency(args, ctx) {
|
|
9115
9370
|
const input = args.trim();
|
|
9116
9371
|
const effective = ctx.concurrencyStore.get(ctx.scope) ?? ctx.defaultScopeConcurrency;
|
|
9117
9372
|
if (!input) {
|
|
9118
|
-
await
|
|
9373
|
+
await reply8(
|
|
9119
9374
|
ctx,
|
|
9120
9375
|
`\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`,
|
|
9121
9376
|
`Parallel tasks for this scope: **${String(effective)}**. Use \`/concurrency <N|default>\` to change it (N \u2265 1).`
|
|
@@ -9124,16 +9379,16 @@ async function handleConcurrency(args, ctx) {
|
|
|
9124
9379
|
}
|
|
9125
9380
|
if (input === "default") {
|
|
9126
9381
|
ctx.concurrencyStore.clear(ctx.scope);
|
|
9127
|
-
await
|
|
9382
|
+
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)}).`);
|
|
9128
9383
|
return;
|
|
9129
9384
|
}
|
|
9130
9385
|
const n = Number(input);
|
|
9131
9386
|
if (!Number.isInteger(n) || n < 1) {
|
|
9132
|
-
await
|
|
9387
|
+
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.");
|
|
9133
9388
|
return;
|
|
9134
9389
|
}
|
|
9135
9390
|
ctx.concurrencyStore.set(ctx.scope, n);
|
|
9136
|
-
await
|
|
9391
|
+
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)}**.`);
|
|
9137
9392
|
}
|
|
9138
9393
|
async function handlePermission(args, ctx) {
|
|
9139
9394
|
const [rawPolicy = "", rawTarget = "", ...extra] = args.trim().split(/\s+/);
|
|
@@ -9145,7 +9400,7 @@ async function handlePermission(args, ctx) {
|
|
|
9145
9400
|
};
|
|
9146
9401
|
if (!input) {
|
|
9147
9402
|
const current = ctx.permissionPolicies?.get(ctx.scope) ?? "ask";
|
|
9148
|
-
await
|
|
9403
|
+
await reply8(
|
|
9149
9404
|
ctx,
|
|
9150
9405
|
`\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`,
|
|
9151
9406
|
`Tool permission policy for this scope: **${labels[current].en}** (\`${current}\`). Use \`/permission ask|allow|deny\` to change it (admin only).`
|
|
@@ -9153,21 +9408,21 @@ async function handlePermission(args, ctx) {
|
|
|
9153
9408
|
return;
|
|
9154
9409
|
}
|
|
9155
9410
|
if (input !== "ask" && input !== "allow" && input !== "deny" || extra.length > 0) {
|
|
9156
|
-
await
|
|
9411
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/permission [ask|allow|deny] [scope]`", "Usage: `/permission [ask|allow|deny] [scope]`");
|
|
9157
9412
|
return;
|
|
9158
9413
|
}
|
|
9159
|
-
if (!
|
|
9414
|
+
if (!requireAdmin4(ctx)) return;
|
|
9160
9415
|
if (!ctx.permissionPolicies) {
|
|
9161
|
-
await
|
|
9416
|
+
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.");
|
|
9162
9417
|
return;
|
|
9163
9418
|
}
|
|
9164
9419
|
const targetScope = rawTarget || ctx.scope;
|
|
9165
9420
|
if (targetScope !== ctx.chatId && !targetScope.startsWith(`${ctx.chatId}:`)) {
|
|
9166
|
-
await
|
|
9421
|
+
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`.");
|
|
9167
9422
|
return;
|
|
9168
9423
|
}
|
|
9169
9424
|
await ctx.permissionPolicies.set(targetScope, input);
|
|
9170
|
-
await
|
|
9425
|
+
await reply8(
|
|
9171
9426
|
ctx,
|
|
9172
9427
|
`\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`,
|
|
9173
9428
|
`Set scope \`${targetScope}\`'s tool permission policy to **${labels[input].en}** (\`${input}\`). It applies to the next tool approval; plan approval remains enforced.`
|
|
@@ -9177,7 +9432,7 @@ async function handleDensity(args, ctx) {
|
|
|
9177
9432
|
const input = args.trim().toLowerCase();
|
|
9178
9433
|
if (!input) {
|
|
9179
9434
|
const current = ctx.densityStore?.get(ctx.scope) ?? "standard";
|
|
9180
|
-
await
|
|
9435
|
+
await reply8(
|
|
9181
9436
|
ctx,
|
|
9182
9437
|
`\u5F53\u524D\u5361\u7247\u5BC6\u5EA6\uFF1A**${current}**\u3002\u53EF\u7528 \`/density compact|standard|detailed\` \u8C03\u6574\u3002`,
|
|
9183
9438
|
`Current card density: **${current}**. Use \`/density compact|standard|detailed\` to change it.`
|
|
@@ -9186,21 +9441,21 @@ async function handleDensity(args, ctx) {
|
|
|
9186
9441
|
}
|
|
9187
9442
|
if (input === "default") {
|
|
9188
9443
|
ctx.densityStore?.clear(ctx.scope);
|
|
9189
|
-
await
|
|
9444
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002", "Restored the default card density.");
|
|
9190
9445
|
return;
|
|
9191
9446
|
}
|
|
9192
9447
|
const density = parseCardDensity(input);
|
|
9193
9448
|
if (!density) {
|
|
9194
|
-
await
|
|
9449
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`", "Usage: `/density [compact|standard|detailed|default]`");
|
|
9195
9450
|
return;
|
|
9196
9451
|
}
|
|
9197
9452
|
ctx.densityStore?.set(ctx.scope, density);
|
|
9198
|
-
await
|
|
9453
|
+
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}**.`);
|
|
9199
9454
|
}
|
|
9200
9455
|
async function handleExecutionMode(args, ctx) {
|
|
9201
9456
|
const input = args.trim().toLowerCase();
|
|
9202
9457
|
if (!ctx.executionModes) {
|
|
9203
|
-
await
|
|
9458
|
+
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.");
|
|
9204
9459
|
return;
|
|
9205
9460
|
}
|
|
9206
9461
|
if (!input) {
|
|
@@ -9217,7 +9472,7 @@ async function handleExecutionMode(args, ctx) {
|
|
|
9217
9472
|
} catch {
|
|
9218
9473
|
}
|
|
9219
9474
|
}
|
|
9220
|
-
await
|
|
9475
|
+
await reply8(
|
|
9221
9476
|
ctx,
|
|
9222
9477
|
`\u5F53\u524D\u6267\u884C\u6A21\u5F0F\uFF1A**${current}**\u3002\u53EF\u7528 \`/mode quick|balanced|deep\` \u5207\u6362\uFF1B\u4E0B\u4E00\u8F6E\u751F\u6548\u3002`,
|
|
9223
9478
|
`Current execution mode: **${current}**. Use \`/mode quick|balanced|deep\`; changes apply next turn.`
|
|
@@ -9225,18 +9480,18 @@ async function handleExecutionMode(args, ctx) {
|
|
|
9225
9480
|
return;
|
|
9226
9481
|
}
|
|
9227
9482
|
if (input !== "quick" && input !== "balanced" && input !== "deep") {
|
|
9228
|
-
await
|
|
9483
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/mode [quick|balanced|deep]`", "Usage: `/mode [quick|balanced|deep]`");
|
|
9229
9484
|
return;
|
|
9230
9485
|
}
|
|
9231
9486
|
await ctx.executionModes.set(ctx.scope, input);
|
|
9232
|
-
await
|
|
9487
|
+
await reply8(
|
|
9233
9488
|
ctx,
|
|
9234
9489
|
`\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`,
|
|
9235
9490
|
`Set this session\u2019s execution mode to **${input}** for the next turn. Active work and existing context are unchanged.`
|
|
9236
9491
|
);
|
|
9237
9492
|
}
|
|
9238
9493
|
async function handleEffortAlias(args, ctx) {
|
|
9239
|
-
await
|
|
9494
|
+
await reply8(
|
|
9240
9495
|
ctx,
|
|
9241
9496
|
"`/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",
|
|
9242
9497
|
"`/effort` is a compatibility alias for `/mode`; it controls task execution intensity, not the model reasoning-effort setting."
|
|
@@ -9246,11 +9501,11 @@ async function handleEffortAlias(args, ctx) {
|
|
|
9246
9501
|
async function handleAsk(args, ctx) {
|
|
9247
9502
|
const question = args.trim();
|
|
9248
9503
|
if (!question) {
|
|
9249
|
-
await
|
|
9504
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`", "Usage: `/ask <question>`");
|
|
9250
9505
|
return;
|
|
9251
9506
|
}
|
|
9252
9507
|
if (!ctx.questions) {
|
|
9253
|
-
await
|
|
9508
|
+
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).");
|
|
9254
9509
|
return;
|
|
9255
9510
|
}
|
|
9256
9511
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -9272,18 +9527,18 @@ async function handleAsk(args, ctx) {
|
|
|
9272
9527
|
if (answer !== void 0) {
|
|
9273
9528
|
const text = Array.isArray(answer) ? answer.join("\u3001") : answer;
|
|
9274
9529
|
ctx.sessions.recordExchange(ctx.scope, cwd, [text], void 0);
|
|
9275
|
-
await
|
|
9530
|
+
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.");
|
|
9276
9531
|
} else {
|
|
9277
|
-
await
|
|
9532
|
+
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).");
|
|
9278
9533
|
}
|
|
9279
9534
|
}
|
|
9280
9535
|
async function handleInvite(args, ctx) {
|
|
9281
9536
|
const [kind, ...rest] = args.trim().split(/\s+/);
|
|
9282
9537
|
const id = rest.join(" ").trim();
|
|
9283
|
-
if (kind !== "list" && !
|
|
9538
|
+
if (kind !== "list" && !requireAdmin4(ctx)) return;
|
|
9284
9539
|
if (kind === "list") {
|
|
9285
9540
|
const snapshot = ctx.accessManager.snapshot();
|
|
9286
|
-
await
|
|
9541
|
+
await reply8(
|
|
9287
9542
|
ctx,
|
|
9288
9543
|
[
|
|
9289
9544
|
"**\u8BBF\u95EE\u767D\u540D\u5355**",
|
|
@@ -9301,7 +9556,7 @@ async function handleInvite(args, ctx) {
|
|
|
9301
9556
|
return;
|
|
9302
9557
|
}
|
|
9303
9558
|
if (!kind || !id) {
|
|
9304
|
-
await
|
|
9559
|
+
await reply8(
|
|
9305
9560
|
ctx,
|
|
9306
9561
|
"\u7528\u6CD5\uFF1A`/invite user|admin|group <id>`\u3001`/invite list`\u3001`/invite remove user|group <id>`",
|
|
9307
9562
|
"Usage: `/invite user|admin|group <id>`, `/invite list`, or `/invite remove user|group <id>`"
|
|
@@ -9310,39 +9565,39 @@ async function handleInvite(args, ctx) {
|
|
|
9310
9565
|
}
|
|
9311
9566
|
if (kind === "user") {
|
|
9312
9567
|
await ctx.accessManager.addUser(id);
|
|
9313
|
-
await
|
|
9568
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``, `Allowed user: \`${id}\``);
|
|
9314
9569
|
return;
|
|
9315
9570
|
}
|
|
9316
9571
|
if (kind === "admin") {
|
|
9317
9572
|
await ctx.accessManager.addAdmin(id);
|
|
9318
|
-
await
|
|
9573
|
+
await reply8(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``, `Granted admin access to: \`${id}\``);
|
|
9319
9574
|
return;
|
|
9320
9575
|
}
|
|
9321
9576
|
if (kind === "group") {
|
|
9322
9577
|
await ctx.accessManager.addChat(id);
|
|
9323
|
-
await
|
|
9578
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``, `Allowed group chat: \`${id}\``);
|
|
9324
9579
|
return;
|
|
9325
9580
|
}
|
|
9326
9581
|
if (kind === "remove") {
|
|
9327
9582
|
const [sub, target] = rest;
|
|
9328
9583
|
if (sub === "user" && target) {
|
|
9329
9584
|
await ctx.accessManager.removeUser(target);
|
|
9330
|
-
await
|
|
9585
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``, `Removed user: \`${target}\``);
|
|
9331
9586
|
return;
|
|
9332
9587
|
}
|
|
9333
9588
|
if (sub === "group" && target) {
|
|
9334
9589
|
await ctx.accessManager.removeChat(target);
|
|
9335
|
-
await
|
|
9590
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``, `Removed group chat: \`${target}\``);
|
|
9336
9591
|
return;
|
|
9337
9592
|
}
|
|
9338
|
-
await
|
|
9593
|
+
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>`");
|
|
9339
9594
|
return;
|
|
9340
9595
|
}
|
|
9341
|
-
await
|
|
9596
|
+
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.");
|
|
9342
9597
|
}
|
|
9343
|
-
function
|
|
9598
|
+
function requireAdmin4(ctx) {
|
|
9344
9599
|
if (!ctx.accessManager.isAdmin(ctx.senderId)) {
|
|
9345
|
-
void
|
|
9600
|
+
void reply8(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u8BE5\u64CD\u4F5C\u3002", "Only admins can perform this operation.");
|
|
9346
9601
|
return false;
|
|
9347
9602
|
}
|
|
9348
9603
|
return true;
|
|
@@ -9395,20 +9650,20 @@ async function handleKeyDispatch(args, ctx) {
|
|
|
9395
9650
|
await handleKey(args, ctx);
|
|
9396
9651
|
}
|
|
9397
9652
|
async function handleHelp(_args, ctx) {
|
|
9398
|
-
await
|
|
9653
|
+
await reply8(ctx, renderCommandHelp("zh"), renderCommandHelp("en"));
|
|
9399
9654
|
}
|
|
9400
9655
|
async function handleLanguage(args, ctx) {
|
|
9401
9656
|
if (!ctx.languagePolicies) {
|
|
9402
|
-
await
|
|
9657
|
+
await reply8(ctx, "\u5F53\u524D\u8FD0\u884C\u65F6\u672A\u542F\u7528\u8BED\u8A00\u7B56\u7565\u3002", "Language policy is unavailable in this runtime.");
|
|
9403
9658
|
return;
|
|
9404
9659
|
}
|
|
9405
9660
|
const [action = "show", field, value] = args.trim().split(/\s+/);
|
|
9406
9661
|
if (action === "show") {
|
|
9407
9662
|
const policy = ctx.languagePolicies.get();
|
|
9408
|
-
await
|
|
9663
|
+
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}`);
|
|
9409
9664
|
return;
|
|
9410
9665
|
}
|
|
9411
|
-
if (!
|
|
9666
|
+
if (!requireAdmin4(ctx)) return;
|
|
9412
9667
|
if (action === "set" && field === "plain" && (value === "bilingual" || value === "zh" || value === "en")) {
|
|
9413
9668
|
await ctx.languagePolicies.set({ plain: value });
|
|
9414
9669
|
} else if (action === "set" && field === "agent" && (value === "auto" || value === "zh" || value === "en")) {
|
|
@@ -9416,7 +9671,7 @@ async function handleLanguage(args, ctx) {
|
|
|
9416
9671
|
} else if (action === "reset" && (field === void 0 || field === "all" || field === "plain" || field === "agent")) {
|
|
9417
9672
|
await ctx.languagePolicies.reset(field ?? "all");
|
|
9418
9673
|
} else {
|
|
9419
|
-
await
|
|
9674
|
+
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]`");
|
|
9420
9675
|
return;
|
|
9421
9676
|
}
|
|
9422
9677
|
process.env.DSH_LARK_REPLY_LANG = ctx.languagePolicies.get().plain;
|
|
@@ -9427,37 +9682,37 @@ function parseSecretTarget(value) {
|
|
|
9427
9682
|
}
|
|
9428
9683
|
async function handleSecret(args, ctx) {
|
|
9429
9684
|
if (!ctx.secretService) {
|
|
9430
|
-
await
|
|
9685
|
+
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.");
|
|
9431
9686
|
return;
|
|
9432
9687
|
}
|
|
9433
9688
|
const [action, targetRaw, referenceRaw, ...purposeParts] = args.trim().split(/\s+/);
|
|
9434
9689
|
const target = parseSecretTarget(targetRaw);
|
|
9435
9690
|
const reference = referenceRaw?.trim();
|
|
9436
9691
|
if (!target || !reference || !["status", "set", "remove"].includes(action ?? "")) {
|
|
9437
|
-
await
|
|
9692
|
+
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>`");
|
|
9438
9693
|
return;
|
|
9439
9694
|
}
|
|
9440
|
-
if (action !== "status" && !
|
|
9695
|
+
if (action !== "status" && !requireAdmin4(ctx)) return;
|
|
9441
9696
|
if (action === "status") {
|
|
9442
9697
|
const configured = await ctx.secretService.configured(target, reference);
|
|
9443
|
-
await
|
|
9698
|
+
await reply8(ctx, `${target} \`${reference}\`\uFF1A${configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`, `${target} \`${reference}\`: ${configured ? "configured" : "not configured"}`);
|
|
9444
9699
|
return;
|
|
9445
9700
|
}
|
|
9446
9701
|
if (action === "remove") {
|
|
9447
9702
|
const removed = await ctx.secretService.remove(target, reference);
|
|
9448
|
-
await
|
|
9703
|
+
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.");
|
|
9449
9704
|
return;
|
|
9450
9705
|
}
|
|
9451
9706
|
if (!ctx.senderId) {
|
|
9452
|
-
await
|
|
9707
|
+
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.");
|
|
9453
9708
|
return;
|
|
9454
9709
|
}
|
|
9455
9710
|
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" });
|
|
9456
|
-
await
|
|
9711
|
+
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.");
|
|
9457
9712
|
}
|
|
9458
9713
|
async function handleSessionProjection(args, ctx) {
|
|
9459
9714
|
if (!ctx.sessionProjection) {
|
|
9460
|
-
await
|
|
9715
|
+
await reply8(
|
|
9461
9716
|
ctx,
|
|
9462
9717
|
"\u5F53\u524D adapter \u672A\u63D0\u4F9B DSH session \u6295\u5F71\uFF1B\u8BF7\u4F7F\u7528 `web` adapter \u5E76\u786E\u8BA4 Web host \u53EF\u7528\u3002",
|
|
9463
9718
|
"The current adapter does not provide DSH session projection; use the `web` adapter and ensure the Web host is reachable."
|
|
@@ -9487,6 +9742,7 @@ var handlers = {
|
|
|
9487
9742
|
"/role": handleRole,
|
|
9488
9743
|
"/notify": handleNotify,
|
|
9489
9744
|
"/notifications": handleNotifications,
|
|
9745
|
+
"/channels": handleChannels,
|
|
9490
9746
|
"/replies": handleReplies,
|
|
9491
9747
|
"/retention": handleRetention,
|
|
9492
9748
|
"/archive": handleArchive,
|
|
@@ -9966,24 +10222,26 @@ var GroupMessagePoller = class {
|
|
|
9966
10222
|
|
|
9967
10223
|
// src/bridge/reconnect-notifier.ts
|
|
9968
10224
|
var ReconnectNotifier = class {
|
|
9969
|
-
constructor(channel, directory, now = Date.now, reconciliation) {
|
|
10225
|
+
constructor(channel, directory, now = Date.now, reconciliation, onFault) {
|
|
9970
10226
|
this.channel = channel;
|
|
9971
10227
|
this.directory = directory;
|
|
9972
10228
|
this.now = now;
|
|
9973
10229
|
this.reconciliation = reconciliation;
|
|
10230
|
+
this.onFault = onFault;
|
|
9974
10231
|
}
|
|
9975
10232
|
channel;
|
|
9976
10233
|
directory;
|
|
9977
10234
|
now;
|
|
9978
10235
|
reconciliation;
|
|
10236
|
+
onFault;
|
|
9979
10237
|
startedAt;
|
|
9980
10238
|
async reconnecting() {
|
|
9981
10239
|
if (this.startedAt !== void 0) return;
|
|
9982
10240
|
this.startedAt = this.now();
|
|
9983
|
-
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
);
|
|
10241
|
+
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";
|
|
10242
|
+
const enUs = "\u26A0\uFE0F The bot connection is unstable and reconnecting automatically. New messages may be delayed.";
|
|
10243
|
+
await this.send(zhCn, enUs);
|
|
10244
|
+
await this.fault(this.directory?.recentDestination()?.scope, { zh: zhCn, en: enUs });
|
|
9987
10245
|
}
|
|
9988
10246
|
async reconnected() {
|
|
9989
10247
|
if (this.startedAt === void 0) return;
|
|
@@ -9991,12 +10249,12 @@ var ReconnectNotifier = class {
|
|
|
9991
10249
|
this.startedAt = void 0;
|
|
9992
10250
|
const target = this.directory?.recentDestination();
|
|
9993
10251
|
const summary = target ? this.reconciliation?.(target.scope) : void 0;
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
${summary
|
|
9997
|
-
|
|
9998
|
-
|
|
9999
|
-
);
|
|
10252
|
+
const zhCn = `\u2705 \u673A\u5668\u4EBA\u8FDE\u63A5\u5DF2\u6062\u590D\uFF08\u4E2D\u65AD\u7EA6 ${formatDuration(elapsed)}\uFF09\u3002${summary ? `
|
|
10253
|
+
${summary.zhCn}` : ""}`;
|
|
10254
|
+
const enUs = `\u2705 The bot connection recovered after about ${formatDurationEnglish(elapsed)}.${summary ? `
|
|
10255
|
+
${summary.enUs}` : ""}`;
|
|
10256
|
+
await this.send(zhCn, enUs);
|
|
10257
|
+
await this.fault(target?.scope, { zh: zhCn, en: enUs });
|
|
10000
10258
|
}
|
|
10001
10259
|
async send(zhCn, enUs) {
|
|
10002
10260
|
const target = this.directory?.recentDestination();
|
|
@@ -10006,6 +10264,13 @@ ${summary.enUs}` : ""}`
|
|
|
10006
10264
|
...target.threadId ? { threadId: target.threadId } : {}
|
|
10007
10265
|
});
|
|
10008
10266
|
}
|
|
10267
|
+
async fault(scope, title) {
|
|
10268
|
+
if (!scope || !this.onFault) return;
|
|
10269
|
+
try {
|
|
10270
|
+
await this.onFault(scope, title);
|
|
10271
|
+
} catch (error) {
|
|
10272
|
+
}
|
|
10273
|
+
}
|
|
10009
10274
|
};
|
|
10010
10275
|
function formatDurationEnglish(ms) {
|
|
10011
10276
|
if (ms < 1e3) return "less than 1 second";
|
|
@@ -11208,7 +11473,8 @@ async function startChannel(deps) {
|
|
|
11208
11473
|
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`,
|
|
11209
11474
|
enUs: `Ledger reconciliation: queued ${counts.queued} \xB7 running ${counts.running} \xB7 interrupted ${counts.interrupted} \xB7 failed ${counts.failed}; use \`/jobs\` for details.`
|
|
11210
11475
|
};
|
|
11211
|
-
} : void 0
|
|
11476
|
+
} : void 0,
|
|
11477
|
+
deps.faultNotifier
|
|
11212
11478
|
);
|
|
11213
11479
|
const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
|
|
11214
11480
|
let groupPoller;
|
|
@@ -11343,6 +11609,8 @@ async function startChannel(deps) {
|
|
|
11343
11609
|
...deps.permissionPolicies ? { permissionPolicies: deps.permissionPolicies } : {},
|
|
11344
11610
|
...deps.notificationPreferences ? { notificationPreferences: deps.notificationPreferences } : {},
|
|
11345
11611
|
...deps.defaultNotificationPreference ? { defaultNotificationPreference: deps.defaultNotificationPreference } : {},
|
|
11612
|
+
...deps.notificationChannels ? { notificationChannels: deps.notificationChannels } : {},
|
|
11613
|
+
...deps.notificationSinks ? { notificationSinks: deps.notificationSinks } : {},
|
|
11346
11614
|
...deps.replyPolicies ? { replyPolicies: deps.replyPolicies } : {},
|
|
11347
11615
|
...deps.executionModes ? { executionModes: deps.executionModes } : {},
|
|
11348
11616
|
...deps.languagePolicies ? { languagePolicies: deps.languagePolicies } : {},
|
|
@@ -12065,7 +12333,7 @@ function attachRunCardAnchors(channel, anchors) {
|
|
|
12065
12333
|
}
|
|
12066
12334
|
|
|
12067
12335
|
// src/bridge/scope-directory.ts
|
|
12068
|
-
import { readFile as
|
|
12336
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
12069
12337
|
var ScopeDirectory = class {
|
|
12070
12338
|
data = { entries: {} };
|
|
12071
12339
|
saving = Promise.resolve();
|
|
@@ -12075,7 +12343,7 @@ var ScopeDirectory = class {
|
|
|
12075
12343
|
}
|
|
12076
12344
|
async load() {
|
|
12077
12345
|
try {
|
|
12078
|
-
const raw = await
|
|
12346
|
+
const raw = await readFile12(this.path, "utf8");
|
|
12079
12347
|
const parsed = JSON.parse(raw);
|
|
12080
12348
|
this.data = { entries: parsed.entries ?? {} };
|
|
12081
12349
|
} catch (error) {
|
|
@@ -12816,6 +13084,11 @@ var NotificationDispatcher = class {
|
|
|
12816
13084
|
setDefaultPreference(preference) {
|
|
12817
13085
|
this.defaultPreference = clonePreference(preference);
|
|
12818
13086
|
}
|
|
13087
|
+
/**
|
|
13088
|
+
* Primary notification path: send to Feishu (default first-class route) and,
|
|
13089
|
+
* when the scope preference lists extra channels, fan out to those sinks.
|
|
13090
|
+
* Returns `false` when the event is disabled or no Feishu destination exists.
|
|
13091
|
+
*/
|
|
12819
13092
|
async notify(scope, event, detail) {
|
|
12820
13093
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
12821
13094
|
if (!preference?.events.includes(event)) return false;
|
|
@@ -12824,21 +13097,52 @@ var NotificationDispatcher = class {
|
|
|
12824
13097
|
log.warn("notification", "target-unavailable", { scope, target: preference.target });
|
|
12825
13098
|
return false;
|
|
12826
13099
|
}
|
|
12827
|
-
const title = event
|
|
13100
|
+
const title = titleForEvent(event);
|
|
12828
13101
|
try {
|
|
12829
|
-
await this.deps.send(destination.chatId,
|
|
12830
|
-
`${title[0]}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
12831
|
-
`${title[1]} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
12832
|
-
), {
|
|
13102
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
12833
13103
|
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
12834
13104
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
12835
13105
|
...preference.mentionUserIds.length > 0 ? { mentions: preference.mentionUserIds.map((userId) => ({ userId })) } : {}
|
|
12836
13106
|
});
|
|
12837
|
-
return true;
|
|
12838
13107
|
} catch (error) {
|
|
12839
13108
|
log.warn("notification", "send-failed", { scope, event, error });
|
|
12840
13109
|
return false;
|
|
12841
13110
|
}
|
|
13111
|
+
if (preference.sinks.length > 0) {
|
|
13112
|
+
await this.broadcastSinks(preference.sinks, scope, event, title, detail);
|
|
13113
|
+
}
|
|
13114
|
+
return true;
|
|
13115
|
+
}
|
|
13116
|
+
/**
|
|
13117
|
+
* Safety-net / fault class notification (issue #113): broadcast to every
|
|
13118
|
+
* enabled outbound sink regardless of the per-scope opt-in, so a crash /
|
|
13119
|
+
* reconnect / heartbeat anomaly is seen even when a scope has not enabled
|
|
13120
|
+
* proactive Feishu reminders. When the scope preference explicitly enables
|
|
13121
|
+
* the `urgent` event it is also sent to Feishu.
|
|
13122
|
+
*/
|
|
13123
|
+
async notifyUrgent(scope, title, detail) {
|
|
13124
|
+
const channels = this.deps.sinks?.enabledChannels() ?? [];
|
|
13125
|
+
if (channels.length > 0) {
|
|
13126
|
+
await this.broadcastSinks(
|
|
13127
|
+
channels.map((channel) => channel.id),
|
|
13128
|
+
scope,
|
|
13129
|
+
"urgent",
|
|
13130
|
+
title,
|
|
13131
|
+
detail
|
|
13132
|
+
);
|
|
13133
|
+
}
|
|
13134
|
+
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
13135
|
+
if (!preference?.events.includes("urgent")) return;
|
|
13136
|
+
const destination = preference.target ? this.deps.scopeDirectory.resolve(preference.target) ?? this.deps.scopeDirectory.resolveChat(preference.target) : this.deps.scopeDirectory.resolve(scope);
|
|
13137
|
+
if (!destination) return;
|
|
13138
|
+
try {
|
|
13139
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
13140
|
+
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
13141
|
+
...destination.messageId ? { replyTo: destination.messageId } : {}
|
|
13142
|
+
});
|
|
13143
|
+
} catch (error) {
|
|
13144
|
+
log.warn("notification", "urgent-send-failed", { scope, error });
|
|
13145
|
+
}
|
|
12842
13146
|
}
|
|
12843
13147
|
scheduleApprovalReminder(scope, toolName) {
|
|
12844
13148
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
@@ -12849,11 +13153,197 @@ var NotificationDispatcher = class {
|
|
|
12849
13153
|
timer.unref?.();
|
|
12850
13154
|
return () => clearTimeout(timer);
|
|
12851
13155
|
}
|
|
13156
|
+
async broadcastSinks(channelIds, scope, event, title, detail) {
|
|
13157
|
+
if (!this.deps.sinks || channelIds.length === 0) return;
|
|
13158
|
+
try {
|
|
13159
|
+
const outcome = await this.deps.sinks.broadcast(channelIds, {
|
|
13160
|
+
scope,
|
|
13161
|
+
event,
|
|
13162
|
+
title,
|
|
13163
|
+
...detail === void 0 ? {} : { detail }
|
|
13164
|
+
});
|
|
13165
|
+
if (outcome.failures.length > 0) {
|
|
13166
|
+
log.warn("notification", "sink-failures", { scope, event, failures: outcome.failures });
|
|
13167
|
+
}
|
|
13168
|
+
} catch (error) {
|
|
13169
|
+
log.warn("notification", "sink-broadcast-error", { scope, event, error });
|
|
13170
|
+
}
|
|
13171
|
+
}
|
|
12852
13172
|
};
|
|
13173
|
+
function titleForEvent(event) {
|
|
13174
|
+
switch (event) {
|
|
13175
|
+
case "completed":
|
|
13176
|
+
return { zh: "\u2705 \u4EFB\u52A1\u5DF2\u5B8C\u6210", en: "\u2705 Task completed" };
|
|
13177
|
+
case "failed":
|
|
13178
|
+
return { zh: "\u26A0\uFE0F \u4EFB\u52A1\u6267\u884C\u5931\u8D25", en: "\u26A0\uFE0F Task failed" };
|
|
13179
|
+
case "approval":
|
|
13180
|
+
return { zh: "\u23F3 \u4EFB\u52A1\u4ECD\u5728\u7B49\u5F85\u64CD\u4F5C\u5BA1\u6279", en: "\u23F3 Task is still waiting for tool approval" };
|
|
13181
|
+
case "urgent":
|
|
13182
|
+
return { zh: "\u{1F534} \u7A81\u53D1 / \u6545\u969C", en: "\u{1F534} Urgent / fault" };
|
|
13183
|
+
}
|
|
13184
|
+
}
|
|
13185
|
+
function renderFeishuBody(title, scope, detail) {
|
|
13186
|
+
return bilingualMarkdown(
|
|
13187
|
+
`${title.zh}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
13188
|
+
`${title.en} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
13189
|
+
);
|
|
13190
|
+
}
|
|
12853
13191
|
function clonePreference(preference) {
|
|
12854
|
-
return preference ? { ...preference, events: [...preference.events], mentionUserIds: [...preference.mentionUserIds] } : void 0;
|
|
13192
|
+
return preference ? { ...preference, events: [...preference.events], mentionUserIds: [...preference.mentionUserIds], sinks: [...preference.sinks] } : void 0;
|
|
12855
13193
|
}
|
|
12856
13194
|
|
|
13195
|
+
// src/notify/sinks/http.ts
|
|
13196
|
+
async function postJson(url, body3) {
|
|
13197
|
+
const controller = new AbortController();
|
|
13198
|
+
const timer = setTimeout(() => controller.abort(), SINK_HTTP_TIMEOUT_MS);
|
|
13199
|
+
timer.unref?.();
|
|
13200
|
+
try {
|
|
13201
|
+
return await fetch(url, {
|
|
13202
|
+
method: "POST",
|
|
13203
|
+
headers: { "content-type": "application/json" },
|
|
13204
|
+
body: body3,
|
|
13205
|
+
signal: controller.signal
|
|
13206
|
+
});
|
|
13207
|
+
} finally {
|
|
13208
|
+
clearTimeout(timer);
|
|
13209
|
+
}
|
|
13210
|
+
}
|
|
13211
|
+
|
|
13212
|
+
// src/notify/sinks/text.ts
|
|
13213
|
+
function renderSinkText(message) {
|
|
13214
|
+
const zhLine = `${message.title.zh}\uFF08scope \`${message.scope}\`\uFF09${message.detail ? `\uFF1A${message.detail}` : ""}`;
|
|
13215
|
+
const enLine = `${message.title.en} (scope \`${message.scope}\`)${message.detail ? `: ${message.detail}` : ""}`;
|
|
13216
|
+
return `${stripMarkdown(zhLine)}
|
|
13217
|
+
|
|
13218
|
+
${stripMarkdown(enLine)}`;
|
|
13219
|
+
}
|
|
13220
|
+
function escapeHtml(value) {
|
|
13221
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
13222
|
+
}
|
|
13223
|
+
function stripMarkdown(value) {
|
|
13224
|
+
return value.replaceAll("`", "").replaceAll("**", "").replaceAll("*", "");
|
|
13225
|
+
}
|
|
13226
|
+
|
|
13227
|
+
// src/notify/sinks/telegram.ts
|
|
13228
|
+
var TelegramSink = class {
|
|
13229
|
+
constructor(baseUrl = "https://api.telegram.org") {
|
|
13230
|
+
this.baseUrl = baseUrl;
|
|
13231
|
+
}
|
|
13232
|
+
baseUrl;
|
|
13233
|
+
type = "telegram";
|
|
13234
|
+
async send(channel, message) {
|
|
13235
|
+
const token = channel.secret;
|
|
13236
|
+
if (!token) {
|
|
13237
|
+
log.warn("sink:telegram", "missing-token", { channel: channel.id });
|
|
13238
|
+
return false;
|
|
13239
|
+
}
|
|
13240
|
+
const url = `${this.baseUrl}/bot${encodeURIComponent(token)}/sendMessage`;
|
|
13241
|
+
const body3 = JSON.stringify({
|
|
13242
|
+
chat_id: channel.destination,
|
|
13243
|
+
text: escapeHtml(renderSinkText(message)),
|
|
13244
|
+
parse_mode: "HTML",
|
|
13245
|
+
disable_web_page_preview: true
|
|
13246
|
+
});
|
|
13247
|
+
try {
|
|
13248
|
+
const response = await postJson(url, body3);
|
|
13249
|
+
const payload = await response.json();
|
|
13250
|
+
if (!response.ok || payload.ok !== true) {
|
|
13251
|
+
log.warn("sink:telegram", "send-failed", {
|
|
13252
|
+
channel: channel.id,
|
|
13253
|
+
error: payload.description ?? `http ${response.status}`
|
|
13254
|
+
});
|
|
13255
|
+
return false;
|
|
13256
|
+
}
|
|
13257
|
+
return true;
|
|
13258
|
+
} catch (error) {
|
|
13259
|
+
log.warn("sink:telegram", "send-error", { channel: channel.id, error });
|
|
13260
|
+
return false;
|
|
13261
|
+
}
|
|
13262
|
+
}
|
|
13263
|
+
};
|
|
13264
|
+
|
|
13265
|
+
// src/notify/sinks/wecom.ts
|
|
13266
|
+
var WeComSink = class {
|
|
13267
|
+
constructor(baseUrl = "https://qyapi.weixin.qq.com") {
|
|
13268
|
+
this.baseUrl = baseUrl;
|
|
13269
|
+
}
|
|
13270
|
+
baseUrl;
|
|
13271
|
+
type = "wecom";
|
|
13272
|
+
async send(channel, message) {
|
|
13273
|
+
const key = channel.destination;
|
|
13274
|
+
if (!key) {
|
|
13275
|
+
log.warn("sink:wecom", "missing-key", { channel: channel.id });
|
|
13276
|
+
return false;
|
|
13277
|
+
}
|
|
13278
|
+
const url = `${this.baseUrl}/cgi-bin/webhook/send?key=${encodeURIComponent(key)}`;
|
|
13279
|
+
const body3 = JSON.stringify({
|
|
13280
|
+
msgtype: "text",
|
|
13281
|
+
text: { content: renderSinkText(message) }
|
|
13282
|
+
});
|
|
13283
|
+
try {
|
|
13284
|
+
const response = await postJson(url, body3);
|
|
13285
|
+
const payload = await response.json();
|
|
13286
|
+
if (!response.ok || payload.errcode !== 0) {
|
|
13287
|
+
log.warn("sink:wecom", "send-failed", {
|
|
13288
|
+
channel: channel.id,
|
|
13289
|
+
error: payload.errmsg ?? `http ${response.status}`
|
|
13290
|
+
});
|
|
13291
|
+
return false;
|
|
13292
|
+
}
|
|
13293
|
+
return true;
|
|
13294
|
+
} catch (error) {
|
|
13295
|
+
log.warn("sink:wecom", "send-error", { channel: channel.id, error });
|
|
13296
|
+
return false;
|
|
13297
|
+
}
|
|
13298
|
+
}
|
|
13299
|
+
};
|
|
13300
|
+
|
|
13301
|
+
// src/notify/sinks/registry.ts
|
|
13302
|
+
var OutboundSinkRegistry = class {
|
|
13303
|
+
constructor(store, sinks = [new TelegramSink(), new WeComSink()]) {
|
|
13304
|
+
this.store = store;
|
|
13305
|
+
this.sinks = sinks;
|
|
13306
|
+
}
|
|
13307
|
+
store;
|
|
13308
|
+
sinks;
|
|
13309
|
+
/** Resolve only the enabled channels referenced by `channelIds`. */
|
|
13310
|
+
channelsForIds(channelIds) {
|
|
13311
|
+
const seen = new Set(channelIds);
|
|
13312
|
+
return this.store.list().filter((channel) => channel.enabled && seen.has(channel.id));
|
|
13313
|
+
}
|
|
13314
|
+
/** Channels currently enabled (for `/status` and `/channels`). */
|
|
13315
|
+
enabledChannels() {
|
|
13316
|
+
return this.store.list().filter((channel) => channel.enabled);
|
|
13317
|
+
}
|
|
13318
|
+
/** Best-effort fan-out; a failing channel never blocks the others. */
|
|
13319
|
+
async broadcast(channelIds, message) {
|
|
13320
|
+
const channelIdsToSend = [...new Set(channelIds)];
|
|
13321
|
+
const channels = this.channelsForIds(channelIdsToSend);
|
|
13322
|
+
const failures = [];
|
|
13323
|
+
let delivered = 0;
|
|
13324
|
+
for (const channel of channels) {
|
|
13325
|
+
const sink = this.sinkFor(channel.type);
|
|
13326
|
+
if (!sink) {
|
|
13327
|
+
log.warn("sinks", "unsupported-type", { channel: channel.id, type: channel.type });
|
|
13328
|
+
failures.push(channel.id);
|
|
13329
|
+
continue;
|
|
13330
|
+
}
|
|
13331
|
+
let ok = false;
|
|
13332
|
+
try {
|
|
13333
|
+
ok = await sink.send(channel, message);
|
|
13334
|
+
} catch (error) {
|
|
13335
|
+
log.warn("sinks", "channel-send-error", { channel: channel.id, error });
|
|
13336
|
+
}
|
|
13337
|
+
if (ok) delivered += 1;
|
|
13338
|
+
else failures.push(channel.id);
|
|
13339
|
+
}
|
|
13340
|
+
return { delivered, failures, total: channels.length };
|
|
13341
|
+
}
|
|
13342
|
+
sinkFor(type) {
|
|
13343
|
+
return this.sinks.find((sink) => sink.type === type);
|
|
13344
|
+
}
|
|
13345
|
+
};
|
|
13346
|
+
|
|
12857
13347
|
// src/bridge/reply-dispatcher.ts
|
|
12858
13348
|
var ReplyDispatcher = class {
|
|
12859
13349
|
constructor(deps) {
|
|
@@ -12912,8 +13402,8 @@ var ReplyDispatcher = class {
|
|
|
12912
13402
|
}
|
|
12913
13403
|
}
|
|
12914
13404
|
};
|
|
12915
|
-
function replyRoute(
|
|
12916
|
-
return `${
|
|
13405
|
+
function replyRoute(reply9) {
|
|
13406
|
+
return `${reply9.chatId}\0${reply9.options?.threadId ?? ""}`;
|
|
12917
13407
|
}
|
|
12918
13408
|
function mergedMarkdown(batch) {
|
|
12919
13409
|
if (batch.length === 1) return batch[0].markdown;
|
|
@@ -12964,6 +13454,7 @@ function resolveAppPaths(root = defaultHome()) {
|
|
|
12964
13454
|
jobsFile: (profile) => profilePath(profile, "jobs.json"),
|
|
12965
13455
|
permissionPoliciesFile: (profile) => profilePath(profile, "permission-policies.json"),
|
|
12966
13456
|
notificationPreferencesFile: (profile) => profilePath(profile, "notification-preferences.json"),
|
|
13457
|
+
notificationChannelsFile: (profile) => profilePath(profile, "notification-channels.json"),
|
|
12967
13458
|
replyPoliciesFile: (profile) => profilePath(profile, "reply-policies.json"),
|
|
12968
13459
|
executionModesFile: (profile) => profilePath(profile, "execution-modes.json"),
|
|
12969
13460
|
languagePoliciesFile: (profile) => profilePath(profile, "language-policy.json"),
|
|
@@ -13317,11 +13808,11 @@ function loadRuntimeEnv(source = process.env) {
|
|
|
13317
13808
|
}
|
|
13318
13809
|
|
|
13319
13810
|
// src/config/profile-store.ts
|
|
13320
|
-
import { mkdir as mkdir7, readFile as
|
|
13811
|
+
import { mkdir as mkdir7, readFile as readFile14 } from "fs/promises";
|
|
13321
13812
|
import { dirname as dirname8 } from "path";
|
|
13322
13813
|
|
|
13323
13814
|
// src/platform/file-lock.ts
|
|
13324
|
-
import { open as open3, mkdir as mkdir6, readFile as
|
|
13815
|
+
import { open as open3, mkdir as mkdir6, readFile as readFile13, readdir as readdir2, rm as rm3, rmdir, stat as stat3, utimes } from "fs/promises";
|
|
13325
13816
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
13326
13817
|
import { dirname as dirname7, join as join12 } from "path";
|
|
13327
13818
|
var RETRY_MS = 10;
|
|
@@ -13405,7 +13896,7 @@ async function olderThan(path, thresholdMs) {
|
|
|
13405
13896
|
}
|
|
13406
13897
|
async function readLease(path) {
|
|
13407
13898
|
try {
|
|
13408
|
-
const value = JSON.parse(await
|
|
13899
|
+
const value = JSON.parse(await readFile13(path, "utf8"));
|
|
13409
13900
|
return typeof value.pid === "number" && typeof value.token === "string" && typeof value.createdAt === "number" ? value : void 0;
|
|
13410
13901
|
} catch {
|
|
13411
13902
|
return void 0;
|
|
@@ -13433,7 +13924,7 @@ var ConfigStore = class {
|
|
|
13433
13924
|
return this.data;
|
|
13434
13925
|
}
|
|
13435
13926
|
try {
|
|
13436
|
-
const raw = await
|
|
13927
|
+
const raw = await readFile14(this.path, "utf8");
|
|
13437
13928
|
const parsed = JSON.parse(raw);
|
|
13438
13929
|
this.data = {
|
|
13439
13930
|
schemaVersion: 1,
|
|
@@ -13601,7 +14092,7 @@ async function onboardPersonalAgent(deps = {}) {
|
|
|
13601
14092
|
}
|
|
13602
14093
|
|
|
13603
14094
|
// src/media/attachments.ts
|
|
13604
|
-
import { mkdir as mkdir8, readFile as
|
|
14095
|
+
import { mkdir as mkdir8, readFile as readFile15, rename as rename2, rm as rm4, stat as stat4 } from "fs/promises";
|
|
13605
14096
|
import { join as join13 } from "path";
|
|
13606
14097
|
var MAX_TEXT_FILE_BYTES = 256e3;
|
|
13607
14098
|
function assertSafeMediaName(mediaDir, destination) {
|
|
@@ -13642,7 +14133,7 @@ async function prepareAttachments(channel, message, mediaDir) {
|
|
|
13642
14133
|
result.textFileNotes.push(`[attachment: ${resource.fileName ?? resource.fileKey}] ${destination}`);
|
|
13643
14134
|
continue;
|
|
13644
14135
|
}
|
|
13645
|
-
const content2 = await
|
|
14136
|
+
const content2 = await readFile15(destination, "utf8");
|
|
13646
14137
|
const safeContent = truncateUtf8Safe(content2, MAX_TEXT_FILE_BYTES);
|
|
13647
14138
|
result.textFileNotes.push(
|
|
13648
14139
|
`[attachment: ${resource.fileName ?? resource.fileKey}]
|
|
@@ -13653,7 +14144,7 @@ ${safeContent}`
|
|
|
13653
14144
|
}
|
|
13654
14145
|
|
|
13655
14146
|
// src/session/store.ts
|
|
13656
|
-
import { readFile as
|
|
14147
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
13657
14148
|
var MAX_CONTEXT_SNAPSHOTS_PER_WORKSPACE = 32;
|
|
13658
14149
|
var SessionStore = class {
|
|
13659
14150
|
constructor(path) {
|
|
@@ -13668,7 +14159,7 @@ var SessionStore = class {
|
|
|
13668
14159
|
pendingArchive = Promise.resolve();
|
|
13669
14160
|
async load() {
|
|
13670
14161
|
try {
|
|
13671
|
-
const raw = JSON.parse(await
|
|
14162
|
+
const raw = JSON.parse(await readFile16(this.path, "utf8"));
|
|
13672
14163
|
const legacy = raw.schema !== 2 || !isObject(raw.workspaces);
|
|
13673
14164
|
this.data = legacy ? migrateLegacy(raw) : normalizeV2(raw.workspaces);
|
|
13674
14165
|
this.legacyScopes = legacy ? new Set(Object.keys(this.data.workspaces)) : /* @__PURE__ */ new Set();
|
|
@@ -13765,13 +14256,13 @@ var SessionStore = class {
|
|
|
13765
14256
|
fullHistoryFor(scopeId, cwd) {
|
|
13766
14257
|
return this.historyFor(scopeId, cwd);
|
|
13767
14258
|
}
|
|
13768
|
-
recordUsage(scopeId, cwd,
|
|
14259
|
+
recordUsage(scopeId, cwd, usage4) {
|
|
13769
14260
|
const state = this.ensureState(scopeId, cwd);
|
|
13770
14261
|
const next = { ...state.metrics };
|
|
13771
|
-
addMetric(next, "inputTokens",
|
|
13772
|
-
addMetric(next, "outputTokens",
|
|
13773
|
-
addMetric(next, "cacheReadTokens",
|
|
13774
|
-
addMetric(next, "cacheWriteTokens",
|
|
14262
|
+
addMetric(next, "inputTokens", usage4.inputTokens);
|
|
14263
|
+
addMetric(next, "outputTokens", usage4.outputTokens);
|
|
14264
|
+
addMetric(next, "cacheReadTokens", usage4.cacheReadTokens);
|
|
14265
|
+
addMetric(next, "cacheWriteTokens", usage4.cacheWriteTokens);
|
|
13775
14266
|
state.metrics = next;
|
|
13776
14267
|
this.schedulePersist();
|
|
13777
14268
|
}
|
|
@@ -13987,7 +14478,7 @@ function normalizeContextSnapshot(value) {
|
|
|
13987
14478
|
}
|
|
13988
14479
|
|
|
13989
14480
|
// src/session/projection-store.ts
|
|
13990
|
-
import { readFile as
|
|
14481
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
13991
14482
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
13992
14483
|
var MAX_RECENT_MESSAGES = 64;
|
|
13993
14484
|
var MAX_PROMPT_CORRELATIONS = 64;
|
|
@@ -14001,7 +14492,7 @@ var SessionProjectionStore = class {
|
|
|
14001
14492
|
saving = Promise.resolve();
|
|
14002
14493
|
async load() {
|
|
14003
14494
|
try {
|
|
14004
|
-
const parsed = JSON.parse(await
|
|
14495
|
+
const parsed = JSON.parse(await readFile17(this.path, "utf8"));
|
|
14005
14496
|
const bindings = Object.fromEntries(
|
|
14006
14497
|
Object.values(parsed.bindings ?? {}).flatMap((value) => {
|
|
14007
14498
|
const normalized = normalizeBinding(value);
|
|
@@ -14213,7 +14704,7 @@ function normalizeCorrelation(value) {
|
|
|
14213
14704
|
// src/session/archive.ts
|
|
14214
14705
|
import { execFile } from "child_process";
|
|
14215
14706
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
14216
|
-
import { access, mkdir as mkdir9, readdir as readdir3, readFile as
|
|
14707
|
+
import { access, mkdir as mkdir9, readdir as readdir3, readFile as readFile18, unlink, writeFile as writeFile5 } from "fs/promises";
|
|
14217
14708
|
import { join as join14 } from "path";
|
|
14218
14709
|
import { promisify } from "util";
|
|
14219
14710
|
var execFileAsync = promisify(execFile);
|
|
@@ -14395,8 +14886,8 @@ var SessionArchive = class {
|
|
|
14395
14886
|
const jsonlPath = join14(scopeDir, `${id}.jsonl`);
|
|
14396
14887
|
const markdownPath = join14(scopeDir, `${id}.md`);
|
|
14397
14888
|
const [jsonl, markdown2] = await Promise.all([
|
|
14398
|
-
|
|
14399
|
-
|
|
14889
|
+
readFile18(jsonlPath, "utf8"),
|
|
14890
|
+
readFile18(markdownPath, "utf8")
|
|
14400
14891
|
]);
|
|
14401
14892
|
const [first = "", ...rest] = jsonl.split("\n");
|
|
14402
14893
|
const header = JSON.parse(first);
|
|
@@ -14511,7 +15002,7 @@ async function defaultRunGit(args, cwd) {
|
|
|
14511
15002
|
}
|
|
14512
15003
|
async function readArchiveHeader(jsonlPath) {
|
|
14513
15004
|
try {
|
|
14514
|
-
const raw = await
|
|
15005
|
+
const raw = await readFile18(jsonlPath, "utf8");
|
|
14515
15006
|
const firstLine = raw.split("\n", 1)[0];
|
|
14516
15007
|
if (!firstLine) return void 0;
|
|
14517
15008
|
const header = JSON.parse(firstLine);
|
|
@@ -14561,7 +15052,7 @@ async function defaultCopyRulesFile(source, target) {
|
|
|
14561
15052
|
await mkdir10(dirname9(target), { recursive: true });
|
|
14562
15053
|
await copyFile(source, target);
|
|
14563
15054
|
}
|
|
14564
|
-
function
|
|
15055
|
+
function slugify2(scope) {
|
|
14565
15056
|
const slug = scope.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.]+/, "").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
14566
15057
|
return slug || "session";
|
|
14567
15058
|
}
|
|
@@ -14577,7 +15068,7 @@ var GitWorktreeManager = class {
|
|
|
14577
15068
|
async ensure(scope, base) {
|
|
14578
15069
|
const isGit = await this.isGitRepository(base);
|
|
14579
15070
|
if (!isGit) return { cwd: base, created: false };
|
|
14580
|
-
const scopeSlug =
|
|
15071
|
+
const scopeSlug = slugify2(scope);
|
|
14581
15072
|
const project = createHash2("sha256").update(base).digest("hex").slice(0, 10);
|
|
14582
15073
|
const slug = `${scopeSlug}-${project}`;
|
|
14583
15074
|
const target = join15(this.options.worktreesRoot, slug);
|
|
@@ -14614,7 +15105,7 @@ var GitWorktreeManager = class {
|
|
|
14614
15105
|
}
|
|
14615
15106
|
/** Owning main worktree for the legacy scope-only linked worktree. */
|
|
14616
15107
|
async legacyWorkspaceBase(scope) {
|
|
14617
|
-
const legacyTarget = join15(this.options.worktreesRoot,
|
|
15108
|
+
const legacyTarget = join15(this.options.worktreesRoot, slugify2(scope));
|
|
14618
15109
|
if (!await exists2(legacyTarget)) return void 0;
|
|
14619
15110
|
try {
|
|
14620
15111
|
const output = await this.runGit(["worktree", "list", "--porcelain"], legacyTarget);
|
|
@@ -14646,7 +15137,7 @@ async function samePath(left, right) {
|
|
|
14646
15137
|
}
|
|
14647
15138
|
|
|
14648
15139
|
// src/workspace/store.ts
|
|
14649
|
-
import { readFile as
|
|
15140
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
14650
15141
|
var WorkspaceStore = class {
|
|
14651
15142
|
data = { chats: {}, named: {}, lastUsed: {} };
|
|
14652
15143
|
saving = Promise.resolve();
|
|
@@ -14656,7 +15147,7 @@ var WorkspaceStore = class {
|
|
|
14656
15147
|
}
|
|
14657
15148
|
async load() {
|
|
14658
15149
|
try {
|
|
14659
|
-
const raw = await
|
|
15150
|
+
const raw = await readFile19(this.path, "utf8");
|
|
14660
15151
|
const parsed = JSON.parse(raw);
|
|
14661
15152
|
this.data = {
|
|
14662
15153
|
chats: parsed.chats ?? {},
|
|
@@ -14728,7 +15219,7 @@ var WorkspaceStore = class {
|
|
|
14728
15219
|
};
|
|
14729
15220
|
|
|
14730
15221
|
// src/guardian/heartbeat.ts
|
|
14731
|
-
import { readFile as
|
|
15222
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
14732
15223
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
14733
15224
|
function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, getChannelHealth) {
|
|
14734
15225
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -14809,7 +15300,7 @@ var UpdateNotifier = class {
|
|
|
14809
15300
|
};
|
|
14810
15301
|
|
|
14811
15302
|
// src/bot/fleet-store.ts
|
|
14812
|
-
import { mkdir as mkdir11, readFile as
|
|
15303
|
+
import { mkdir as mkdir11, readFile as readFile21 } from "fs/promises";
|
|
14813
15304
|
import { dirname as dirname10 } from "path";
|
|
14814
15305
|
function validBotInstanceName(name2) {
|
|
14815
15306
|
return /^[a-z][a-z0-9-]{0,31}$/u.test(name2);
|
|
@@ -14822,7 +15313,7 @@ var BotFleetStore = class {
|
|
|
14822
15313
|
data = { schemaVersion: 1, bots: {} };
|
|
14823
15314
|
async load() {
|
|
14824
15315
|
try {
|
|
14825
|
-
const parsed = JSON.parse(await
|
|
15316
|
+
const parsed = JSON.parse(await readFile21(this.path, "utf8"));
|
|
14826
15317
|
this.data = {
|
|
14827
15318
|
schemaVersion: 1,
|
|
14828
15319
|
bots: Object.fromEntries(
|
|
@@ -14950,7 +15441,7 @@ function isFleetEntry(value) {
|
|
|
14950
15441
|
}
|
|
14951
15442
|
|
|
14952
15443
|
// src/bot/handoff-guard.ts
|
|
14953
|
-
import { mkdir as mkdir12, readFile as
|
|
15444
|
+
import { mkdir as mkdir12, readFile as readFile22 } from "fs/promises";
|
|
14954
15445
|
import { dirname as dirname11 } from "path";
|
|
14955
15446
|
var BotHandoffGuard = class {
|
|
14956
15447
|
constructor(path) {
|
|
@@ -14991,7 +15482,7 @@ var BotHandoffGuard = class {
|
|
|
14991
15482
|
}
|
|
14992
15483
|
async read() {
|
|
14993
15484
|
try {
|
|
14994
|
-
const parsed = JSON.parse(await
|
|
15485
|
+
const parsed = JSON.parse(await readFile22(this.path, "utf8"));
|
|
14995
15486
|
return { schemaVersion: 1, chats: parsed.chats ?? {} };
|
|
14996
15487
|
} catch (error) {
|
|
14997
15488
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -15017,7 +15508,7 @@ init_dsh_runtime();
|
|
|
15017
15508
|
import { homedir as homedir14 } from "os";
|
|
15018
15509
|
|
|
15019
15510
|
// src/bot/job-ledger.ts
|
|
15020
|
-
import { readFile as
|
|
15511
|
+
import { readFile as readFile23, rename as rename3 } from "fs/promises";
|
|
15021
15512
|
var EMPTY_COUNTS = {
|
|
15022
15513
|
queued: 0,
|
|
15023
15514
|
running: 0,
|
|
@@ -15040,7 +15531,7 @@ var JobLedger = class {
|
|
|
15040
15531
|
if (this.path === ":memory:") return;
|
|
15041
15532
|
let raw;
|
|
15042
15533
|
try {
|
|
15043
|
-
raw = await
|
|
15534
|
+
raw = await readFile23(this.path, "utf8");
|
|
15044
15535
|
} catch (error) {
|
|
15045
15536
|
if (error.code === "ENOENT") return;
|
|
15046
15537
|
throw error;
|
|
@@ -15616,13 +16107,13 @@ function projectDiagnosticLogs(lines) {
|
|
|
15616
16107
|
}
|
|
15617
16108
|
|
|
15618
16109
|
// src/service/manager.ts
|
|
15619
|
-
import { mkdir as mkdir19, readFile as
|
|
16110
|
+
import { mkdir as mkdir19, readFile as readFile28, rename as rename4, rm as rm9, stat as stat8 } from "fs/promises";
|
|
15620
16111
|
import { dirname as dirname17, join as join22 } from "path";
|
|
15621
16112
|
import { homedir as homedir13 } from "os";
|
|
15622
16113
|
init_dsh_runtime();
|
|
15623
16114
|
|
|
15624
16115
|
// src/guardian/install.ts
|
|
15625
|
-
import { mkdir as mkdir14, readFile as
|
|
16116
|
+
import { mkdir as mkdir14, readFile as readFile25, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
15626
16117
|
import { existsSync as existsSync5 } from "fs";
|
|
15627
16118
|
import { homedir as homedir9 } from "os";
|
|
15628
16119
|
import { dirname as dirname14, join as join16 } from "path";
|
|
@@ -15813,7 +16304,7 @@ async function findProfileProcess(dshProfile) {
|
|
|
15813
16304
|
}
|
|
15814
16305
|
|
|
15815
16306
|
// src/guardian/state.ts
|
|
15816
|
-
import { readFile as
|
|
16307
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
15817
16308
|
import { dirname as dirname13 } from "path";
|
|
15818
16309
|
import { mkdir as mkdir13 } from "fs/promises";
|
|
15819
16310
|
|
|
@@ -15862,19 +16353,19 @@ function resolveCliJsPath(metaUrl = import.meta.url) {
|
|
|
15862
16353
|
if (name2 === "cli.js" || name2 === "cli.mjs" || name2 === "cli.cjs") return current;
|
|
15863
16354
|
return join17(dirname15(current), "..", "dist", "cli.js");
|
|
15864
16355
|
}
|
|
15865
|
-
function
|
|
16356
|
+
function slugify3(value) {
|
|
15866
16357
|
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15867
16358
|
return slug || "default";
|
|
15868
16359
|
}
|
|
15869
16360
|
function serviceNameFor(profile) {
|
|
15870
|
-
return `dsh-lark-engine-${
|
|
16361
|
+
return `dsh-lark-engine-${slugify3(profile)}`;
|
|
15871
16362
|
}
|
|
15872
16363
|
function launchdLabelFor(profile) {
|
|
15873
|
-
return `io.dsh-lark.engine.${
|
|
16364
|
+
return `io.dsh-lark.engine.${slugify3(profile)}`;
|
|
15874
16365
|
}
|
|
15875
16366
|
|
|
15876
16367
|
// src/service/env-snapshot.ts
|
|
15877
|
-
import { readFile as
|
|
16368
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
15878
16369
|
var EXTRA_KEYS = ["PATH", "HOME", "DEEPSEEK_API_KEY", "DSH_HOME"];
|
|
15879
16370
|
var INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
15880
16371
|
"DSH_LARK_NOTIFY_URL",
|
|
@@ -15946,7 +16437,7 @@ async function writeServiceEnv(file, env) {
|
|
|
15946
16437
|
}
|
|
15947
16438
|
async function readServiceEnv(file) {
|
|
15948
16439
|
try {
|
|
15949
|
-
return parseEnvFile(await
|
|
16440
|
+
return parseEnvFile(await readFile26(file, "utf8"));
|
|
15950
16441
|
} catch (error) {
|
|
15951
16442
|
if (error.code === "ENOENT") return {};
|
|
15952
16443
|
throw error;
|
|
@@ -16258,7 +16749,7 @@ var LaunchdServiceController = class {
|
|
|
16258
16749
|
|
|
16259
16750
|
// src/service/portable.ts
|
|
16260
16751
|
import { spawn as spawn6 } from "child_process";
|
|
16261
|
-
import { mkdir as mkdir17, readFile as
|
|
16752
|
+
import { mkdir as mkdir17, readFile as readFile27, rm as rm8, stat as stat7, writeFile as writeFile7 } from "fs/promises";
|
|
16262
16753
|
import { homedir as homedir12 } from "os";
|
|
16263
16754
|
import { join as join20 } from "path";
|
|
16264
16755
|
function defaultAutostartDir() {
|
|
@@ -16293,8 +16784,8 @@ function buildDesktopEntry(spec) {
|
|
|
16293
16784
|
async function readLinuxProcessIdentity(pid) {
|
|
16294
16785
|
try {
|
|
16295
16786
|
const [statText, cmdlineText] = await Promise.all([
|
|
16296
|
-
|
|
16297
|
-
|
|
16787
|
+
readFile27(`/proc/${pid}/stat`, "utf8"),
|
|
16788
|
+
readFile27(`/proc/${pid}/cmdline`, "utf8")
|
|
16298
16789
|
]);
|
|
16299
16790
|
const close = statText.lastIndexOf(") ");
|
|
16300
16791
|
if (close < 0) return void 0;
|
|
@@ -16325,7 +16816,7 @@ function isProcessAlive(pid) {
|
|
|
16325
16816
|
}
|
|
16326
16817
|
async function readSupervisorStatus(file) {
|
|
16327
16818
|
try {
|
|
16328
|
-
return JSON.parse(await
|
|
16819
|
+
return JSON.parse(await readFile27(file, "utf8"));
|
|
16329
16820
|
} catch {
|
|
16330
16821
|
return void 0;
|
|
16331
16822
|
}
|
|
@@ -16700,7 +17191,7 @@ var ServiceManager = class {
|
|
|
16700
17191
|
async readIntent() {
|
|
16701
17192
|
try {
|
|
16702
17193
|
const parsed = JSON.parse(
|
|
16703
|
-
await
|
|
17194
|
+
await readFile28(this.paths.serviceIntentFile(this.profile), "utf8")
|
|
16704
17195
|
);
|
|
16705
17196
|
if (parsed.schemaVersion !== 1 || !["running", "stopped"].includes(parsed.desiredState ?? "")) {
|
|
16706
17197
|
return void 0;
|
|
@@ -16727,7 +17218,7 @@ var ServiceManager = class {
|
|
|
16727
17218
|
if (error.code !== "EEXIST") throw error;
|
|
16728
17219
|
let ownerPid;
|
|
16729
17220
|
try {
|
|
16730
|
-
const owner = JSON.parse(await
|
|
17221
|
+
const owner = JSON.parse(await readFile28(join22(lockDir, "owner.json"), "utf8"));
|
|
16731
17222
|
if (typeof owner.pid === "number") ownerPid = owner.pid;
|
|
16732
17223
|
} catch {
|
|
16733
17224
|
}
|
|
@@ -16881,7 +17372,7 @@ var ServiceManager = class {
|
|
|
16881
17372
|
}
|
|
16882
17373
|
async readMetadata() {
|
|
16883
17374
|
try {
|
|
16884
|
-
const parsed = JSON.parse(await
|
|
17375
|
+
const parsed = JSON.parse(await readFile28(this.metadataFile(), "utf8"));
|
|
16885
17376
|
if (parsed.schemaVersion !== 2 || typeof parsed.profile !== "string") return void 0;
|
|
16886
17377
|
return parsed;
|
|
16887
17378
|
} catch {
|
|
@@ -16983,7 +17474,7 @@ var ServiceManager = class {
|
|
|
16983
17474
|
async logs(lines = 100) {
|
|
16984
17475
|
const path = this.paths.serviceLogFile(this.profile);
|
|
16985
17476
|
try {
|
|
16986
|
-
const content2 = await
|
|
17477
|
+
const content2 = await readFile28(path, "utf8");
|
|
16987
17478
|
return { path, text: content2.split(/\r?\n/).slice(-Math.max(1, lines)).join("\n") };
|
|
16988
17479
|
} catch (error) {
|
|
16989
17480
|
if (error.code === "ENOENT") return { path, text: "" };
|
|
@@ -17158,7 +17649,7 @@ init_own_package();
|
|
|
17158
17649
|
// src/guardian/update-handoff.ts
|
|
17159
17650
|
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
17160
17651
|
import { spawn as spawn7 } from "child_process";
|
|
17161
|
-
import { chmod, mkdir as mkdir20, readFile as
|
|
17652
|
+
import { chmod, mkdir as mkdir20, readFile as readFile29, rm as rm10 } from "fs/promises";
|
|
17162
17653
|
import { dirname as dirname18, join as join23 } from "path";
|
|
17163
17654
|
init_own_package();
|
|
17164
17655
|
function guardianUpdateFailureHint(code) {
|
|
@@ -17187,7 +17678,7 @@ function guardianUpdateFailureHint(code) {
|
|
|
17187
17678
|
}
|
|
17188
17679
|
async function loadState(file) {
|
|
17189
17680
|
try {
|
|
17190
|
-
const parsed = JSON.parse(await
|
|
17681
|
+
const parsed = JSON.parse(await readFile29(file, "utf8"));
|
|
17191
17682
|
return parsed.schemaVersion === 1 && typeof parsed.id === "string" ? parsed : void 0;
|
|
17192
17683
|
} catch (error) {
|
|
17193
17684
|
if (error.code === "ENOENT") return void 0;
|
|
@@ -17472,6 +17963,8 @@ async function startBridgeEngine(options) {
|
|
|
17472
17963
|
const isolationStore = new IsolationStore(paths.profilePath(profileName, "isolation.json"));
|
|
17473
17964
|
const permissionPolicies = new PermissionPolicyStore(paths.permissionPoliciesFile(profileName));
|
|
17474
17965
|
const notificationPreferences = new NotificationPreferenceStore(paths.notificationPreferencesFile(profileName));
|
|
17966
|
+
const notificationChannels = new NotificationChannelStore(paths.notificationChannelsFile(profileName));
|
|
17967
|
+
const notificationSinks = new OutboundSinkRegistry(notificationChannels);
|
|
17475
17968
|
const replyPolicies = new ReplyPolicyStore(paths.replyPoliciesFile(profileName));
|
|
17476
17969
|
const executionModes = new ExecutionModeStore(paths.executionModesFile(profileName));
|
|
17477
17970
|
const languagePolicies = new LanguagePolicyStore(paths.languagePoliciesFile(profileName));
|
|
@@ -17494,6 +17987,7 @@ async function startBridgeEngine(options) {
|
|
|
17494
17987
|
isolationStore.load(),
|
|
17495
17988
|
permissionPolicies.load(),
|
|
17496
17989
|
notificationPreferences.load(),
|
|
17990
|
+
notificationChannels.load(),
|
|
17497
17991
|
replyPolicies.load(),
|
|
17498
17992
|
executionModes.load(),
|
|
17499
17993
|
languagePolicies.load()
|
|
@@ -17563,7 +18057,8 @@ async function startBridgeEngine(options) {
|
|
|
17563
18057
|
send: async (chatId, markdown2, options2) => {
|
|
17564
18058
|
if (!streaming) throw new Error("bridge channel is not ready");
|
|
17565
18059
|
await streaming.sendMarkdown(chatId, markdown2, options2);
|
|
17566
|
-
}
|
|
18060
|
+
},
|
|
18061
|
+
sinks: notificationSinks
|
|
17567
18062
|
});
|
|
17568
18063
|
const replyDispatcher = new ReplyDispatcher({
|
|
17569
18064
|
policies: replyPolicies,
|
|
@@ -17854,6 +18349,9 @@ async function startBridgeEngine(options) {
|
|
|
17854
18349
|
approvals,
|
|
17855
18350
|
permissionPolicies,
|
|
17856
18351
|
notificationPreferences,
|
|
18352
|
+
notificationChannels,
|
|
18353
|
+
notificationSinks,
|
|
18354
|
+
faultNotifier: (scope, title, detail) => notificationDispatcher.notifyUrgent(scope, title, detail),
|
|
17857
18355
|
...defaultNotificationPreference ? { defaultNotificationPreference: { ...defaultNotificationPreference, events: [...defaultNotificationPreference.events] } } : {},
|
|
17858
18356
|
replyPolicies,
|
|
17859
18357
|
executionModes,
|
|
@@ -18116,6 +18614,7 @@ function notificationPreferenceFor(value) {
|
|
|
18116
18614
|
return {
|
|
18117
18615
|
events: value === "all" ? ["completed", "failed", "approval"] : ["completed", "failed"],
|
|
18118
18616
|
mentionUserIds: [],
|
|
18617
|
+
sinks: [],
|
|
18119
18618
|
approvalReminderMs: 10 * 6e4
|
|
18120
18619
|
};
|
|
18121
18620
|
}
|