dsh-lark-bot 0.19.11 → 0.19.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README_EN.md +3 -1
- package/dist/cli.js +801 -242
- package/dist/cli.js.map +1 -1
- package/dist/plugin.d.ts +120 -1
- package/dist/plugin.js +853 -232
- package/dist/plugin.js.map +1 -1
- package/dist/skill.js +2 -1
- package/dist/skill.js.map +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -296,6 +296,16 @@ var init_profile_package = __esm({
|
|
|
296
296
|
}
|
|
297
297
|
});
|
|
298
298
|
|
|
299
|
+
// src/adapters/model-choice.ts
|
|
300
|
+
function resolveModelChoice(preferenceModel, envModel) {
|
|
301
|
+
return preferenceModel?.trim() || envModel;
|
|
302
|
+
}
|
|
303
|
+
var init_model_choice = __esm({
|
|
304
|
+
"src/adapters/model-choice.ts"() {
|
|
305
|
+
"use strict";
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
299
309
|
// src/adapters/dsh/acp-runtime.ts
|
|
300
310
|
import { spawn as spawn4 } from "cross-spawn";
|
|
301
311
|
import { existsSync as existsSync4, realpathSync as realpathSync3 } from "fs";
|
|
@@ -658,7 +668,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
658
668
|
home: homedir2(),
|
|
659
669
|
env: process.env,
|
|
660
670
|
provider: env.provider,
|
|
661
|
-
model: preferences.model
|
|
671
|
+
model: resolveModelChoice(preferences.model, env.model),
|
|
662
672
|
...env.dshExplicit ? { command: env.dshCommand, args: env.dshArgs } : {}
|
|
663
673
|
};
|
|
664
674
|
const ensure = await ensureAcpProfile(runtimeOptions);
|
|
@@ -671,7 +681,7 @@ async function buildAcpAgentAdapter(env, preferences) {
|
|
|
671
681
|
return new AcpDshAdapter({
|
|
672
682
|
launch,
|
|
673
683
|
provider: env.provider,
|
|
674
|
-
model: preferences.model
|
|
684
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
675
685
|
});
|
|
676
686
|
}
|
|
677
687
|
var AcpDshAdapter;
|
|
@@ -680,6 +690,7 @@ var init_acp_adapter = __esm({
|
|
|
680
690
|
"use strict";
|
|
681
691
|
init_acp_runtime();
|
|
682
692
|
init_event_channel();
|
|
693
|
+
init_model_choice();
|
|
683
694
|
AcpDshAdapter = class {
|
|
684
695
|
id = "dsh-acp";
|
|
685
696
|
displayName = "DeepSeek Harness (ACP)";
|
|
@@ -806,14 +817,14 @@ var init_acp_adapter = __esm({
|
|
|
806
817
|
return;
|
|
807
818
|
}
|
|
808
819
|
if (response.usage) {
|
|
809
|
-
const
|
|
820
|
+
const usage4 = {
|
|
810
821
|
type: "usage",
|
|
811
822
|
...typeof response.usage.inputTokens === "number" ? { inputTokens: response.usage.inputTokens } : {},
|
|
812
823
|
...typeof response.usage.outputTokens === "number" ? { outputTokens: response.usage.outputTokens } : {},
|
|
813
824
|
...typeof response.usage.cachedReadTokens === "number" ? { cacheReadTokens: response.usage.cachedReadTokens } : {},
|
|
814
825
|
...typeof response.usage.cachedWriteTokens === "number" ? { cacheWriteTokens: response.usage.cachedWriteTokens } : {}
|
|
815
826
|
};
|
|
816
|
-
channel.push(
|
|
827
|
+
channel.push(usage4);
|
|
817
828
|
}
|
|
818
829
|
})().catch((error) => {
|
|
819
830
|
if (!stopRequested.value) {
|
|
@@ -1463,7 +1474,7 @@ var DshProviderManager = class {
|
|
|
1463
1474
|
try {
|
|
1464
1475
|
catalogProviders = await this.catalog.listProviders();
|
|
1465
1476
|
} catch (error) {
|
|
1466
|
-
log.
|
|
1477
|
+
log.info("model-catalog", "refresh-failed", {
|
|
1467
1478
|
error: error instanceof Error ? error.message : String(error)
|
|
1468
1479
|
});
|
|
1469
1480
|
}
|
|
@@ -2242,11 +2253,11 @@ function translateToolResult(data) {
|
|
|
2242
2253
|
}
|
|
2243
2254
|
function translateAssistantMessage(data) {
|
|
2244
2255
|
if (!isRecord3(data) || !isRecord3(data.usage)) return [];
|
|
2245
|
-
const
|
|
2246
|
-
const inputTokens = typeof
|
|
2247
|
-
const outputTokens = typeof
|
|
2248
|
-
const cacheReadTokens = typeof
|
|
2249
|
-
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;
|
|
2250
2261
|
if (inputTokens === void 0 && outputTokens === void 0 && cacheReadTokens === void 0 && cacheWriteTokens === void 0) return [];
|
|
2251
2262
|
return [
|
|
2252
2263
|
{
|
|
@@ -3201,6 +3212,7 @@ var WebDshAdapter = class {
|
|
|
3201
3212
|
};
|
|
3202
3213
|
|
|
3203
3214
|
// src/adapters/index.ts
|
|
3215
|
+
init_model_choice();
|
|
3204
3216
|
async function resolveAdapterRoute(input, source) {
|
|
3205
3217
|
const provider = input.provider?.trim() || void 0;
|
|
3206
3218
|
const model = input.model?.trim() || void 0;
|
|
@@ -3220,7 +3232,7 @@ async function resolveAdapterRoute(input, source) {
|
|
|
3220
3232
|
return void 0;
|
|
3221
3233
|
}
|
|
3222
3234
|
async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model: void 0 }) {
|
|
3223
|
-
const configuredModel = preferences.model
|
|
3235
|
+
const configuredModel = resolveModelChoice(preferences.model, env.model);
|
|
3224
3236
|
const managedRoute = env.adapterMode === "sdk" || env.adapterMode === "acp" ? await resolveAdapterRoute(
|
|
3225
3237
|
{ provider: env.provider, model: configuredModel },
|
|
3226
3238
|
new DshProviderManager({ env: process.env })
|
|
@@ -3252,7 +3264,7 @@ async function buildAgentAdapter(env, preferences = { stopGraceMs: void 0, model
|
|
|
3252
3264
|
return new WebDshAdapter({
|
|
3253
3265
|
baseUrl: env.webBaseUrl,
|
|
3254
3266
|
provider: env.provider,
|
|
3255
|
-
model: preferences.model
|
|
3267
|
+
model: resolveModelChoice(preferences.model, env.model)
|
|
3256
3268
|
});
|
|
3257
3269
|
}
|
|
3258
3270
|
case "sdk":
|
|
@@ -3997,7 +4009,7 @@ var PermissionPolicyStore = class {
|
|
|
3997
4009
|
|
|
3998
4010
|
// src/bot/notification-preference-store.ts
|
|
3999
4011
|
import { readFile as readFile7 } from "fs/promises";
|
|
4000
|
-
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval"]);
|
|
4012
|
+
var EVENTS = /* @__PURE__ */ new Set(["completed", "failed", "approval", "urgent"]);
|
|
4001
4013
|
var NotificationPreferenceStore = class {
|
|
4002
4014
|
constructor(path) {
|
|
4003
4015
|
this.path = path;
|
|
@@ -4026,7 +4038,7 @@ var NotificationPreferenceStore = class {
|
|
|
4026
4038
|
get(scope) {
|
|
4027
4039
|
const value = this.data.scopes[scope];
|
|
4028
4040
|
if (value === void 0 || value === false) return void 0;
|
|
4029
|
-
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds] };
|
|
4041
|
+
return { ...value, events: [...value.events], mentionUserIds: [...value.mentionUserIds], sinks: [...value.sinks] };
|
|
4030
4042
|
}
|
|
4031
4043
|
resolve(scope, fallback) {
|
|
4032
4044
|
const value = this.data.scopes[scope];
|
|
@@ -4071,6 +4083,7 @@ function normalizePreference(value) {
|
|
|
4071
4083
|
...typeof input.target === "string" && input.target.trim() ? { target: input.target.trim() } : {},
|
|
4072
4084
|
events,
|
|
4073
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()))],
|
|
4074
4087
|
approvalReminderMs
|
|
4075
4088
|
};
|
|
4076
4089
|
}
|
|
@@ -4547,6 +4560,7 @@ function statusCardMarkdownFor(input, locale) {
|
|
|
4547
4560
|
zh ? `\u{1F39A}\uFE0F **\u6267\u884C\u6A21\u5F0F**\uFF1A\`${input.executionMode ?? "balanced"}\`` : `\u{1F39A}\uFE0F **Execution mode**: \`${input.executionMode ?? "balanced"}\``,
|
|
4548
4561
|
zh ? `\u{1F6E1}\uFE0F **\u5DE5\u5177\u6743\u9650**\uFF1A\`${input.permissionPolicy ?? "ask"}\`` : `\u{1F6E1}\uFE0F **Tool permission**: \`${input.permissionPolicy ?? "ask"}\``,
|
|
4549
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(",")}\``] : [],
|
|
4550
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`] : [],
|
|
4551
4565
|
...input.role ? [`\u{1F3AD} **role**\uFF1A${input.role}`] : [],
|
|
4552
4566
|
`\u{1F516} **version**\uFF1A\`${input.version}\``,
|
|
@@ -4963,8 +4977,8 @@ function renderStandard(state, now, locale, maxTools) {
|
|
|
4963
4977
|
} else if (state.terminal === "done" && !hasAnswer(state)) {
|
|
4964
4978
|
elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
4965
4979
|
} else if (state.terminal === "done") {
|
|
4966
|
-
const
|
|
4967
|
-
if (
|
|
4980
|
+
const usage4 = usageLine(state);
|
|
4981
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
4968
4982
|
}
|
|
4969
4983
|
if (state.finalDeliveryError) {
|
|
4970
4984
|
elements.push(finalDeliveryFailureLine(locale));
|
|
@@ -5030,8 +5044,8 @@ function renderDetailed(state, now, locale, maxTools) {
|
|
|
5030
5044
|
} else if (state.terminal === "error") {
|
|
5031
5045
|
elements.push(runFailureLine(locale));
|
|
5032
5046
|
} else if (state.terminal === "done") {
|
|
5033
|
-
const
|
|
5034
|
-
if (
|
|
5047
|
+
const usage4 = usageLine(state);
|
|
5048
|
+
if (usage4) elements.push(noteMd(usage4));
|
|
5035
5049
|
if (!hasAnswer(state)) elements.push(noteMd(zh ? "_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_" : "_(No content returned)_"));
|
|
5036
5050
|
}
|
|
5037
5051
|
if (state.finalDeliveryError) {
|
|
@@ -8054,7 +8068,7 @@ async function handleNotify(args, ctx) {
|
|
|
8054
8068
|
}
|
|
8055
8069
|
|
|
8056
8070
|
// src/commands/notifications.ts
|
|
8057
|
-
var ALL_EVENTS = ["completed", "failed", "approval"];
|
|
8071
|
+
var ALL_EVENTS = ["completed", "failed", "approval", "urgent"];
|
|
8058
8072
|
async function reply5(ctx, zh, en) {
|
|
8059
8073
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
8060
8074
|
}
|
|
@@ -8092,6 +8106,7 @@ async function handleNotifications(args, ctx) {
|
|
|
8092
8106
|
let events = [...ALL_EVENTS];
|
|
8093
8107
|
let mentionUserIds = ctx.senderId ? [ctx.senderId] : [];
|
|
8094
8108
|
let reminderMinutes = 10;
|
|
8109
|
+
let sinks = [];
|
|
8095
8110
|
for (const token of tokens.slice(1)) {
|
|
8096
8111
|
if (token.startsWith("events=")) {
|
|
8097
8112
|
const requested = token.slice(7).split(",").filter(Boolean);
|
|
@@ -8103,6 +8118,8 @@ async function handleNotifications(args, ctx) {
|
|
|
8103
8118
|
} else if (token.startsWith("remind=")) {
|
|
8104
8119
|
reminderMinutes = Number(token.slice(7));
|
|
8105
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);
|
|
8106
8123
|
} else if (!target) {
|
|
8107
8124
|
target = token === "current" ? void 0 : token;
|
|
8108
8125
|
} else return usage(ctx);
|
|
@@ -8121,50 +8138,297 @@ async function handleNotifications(args, ctx) {
|
|
|
8121
8138
|
...target ? { target } : {},
|
|
8122
8139
|
events,
|
|
8123
8140
|
mentionUserIds: [...new Set(mentionUserIds)],
|
|
8124
|
-
approvalReminderMs: reminderMinutes * 6e4
|
|
8141
|
+
approvalReminderMs: reminderMinutes * 6e4,
|
|
8142
|
+
sinks: [...new Set(sinks)]
|
|
8125
8143
|
};
|
|
8126
8144
|
await store.set(ctx.scope, value);
|
|
8127
8145
|
await reply5(ctx, `\u5DF2\u5F00\u542F\u3002${describe(value, true)}`, `Enabled. ${describe(value, false)}`);
|
|
8128
8146
|
}
|
|
8129
8147
|
function describe(value, zh) {
|
|
8130
8148
|
const minutes = value.approvalReminderMs / 6e4;
|
|
8131
|
-
|
|
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.`;
|
|
8132
8151
|
}
|
|
8133
8152
|
async function usage(ctx) {
|
|
8134
8153
|
await reply5(
|
|
8135
8154
|
ctx,
|
|
8136
|
-
"\u7528\u6CD5\uFF1A`/notifications [show|off|default|on [current|scope|chatId] [events=completed,failed,approval] [mentions=self,ou_x|none] [remind=10]]`",
|
|
8137
|
-
"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]]`"
|
|
8157
|
+
);
|
|
8158
|
+
}
|
|
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>`"
|
|
8138
8384
|
);
|
|
8139
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
|
+
}
|
|
8140
8404
|
|
|
8141
8405
|
// src/commands/replies.ts
|
|
8142
8406
|
async function handleReplies(args, ctx) {
|
|
8143
8407
|
const store = ctx.replyPolicies;
|
|
8144
|
-
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.");
|
|
8145
8409
|
const tokens = args.trim().split(/\s+/).filter(Boolean);
|
|
8146
|
-
if (tokens.length === 0 || tokens[0] === "show") return
|
|
8147
|
-
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.");
|
|
8148
8412
|
if (tokens[0] === "default") {
|
|
8149
8413
|
await store.set(ctx.scope, void 0);
|
|
8150
|
-
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.");
|
|
8151
8415
|
}
|
|
8152
|
-
if (tokens[0] !== "set") return
|
|
8416
|
+
if (tokens[0] !== "set") return usage3(ctx);
|
|
8153
8417
|
const policy = { ...store.get(ctx.scope) };
|
|
8154
8418
|
for (const token of tokens.slice(1)) {
|
|
8155
8419
|
const [key, raw, extra] = token.split("=");
|
|
8156
|
-
if (!raw || extra !== void 0) return
|
|
8420
|
+
if (!raw || extra !== void 0) return usage3(ctx);
|
|
8157
8421
|
const seconds = Number(raw);
|
|
8158
|
-
if (!Number.isInteger(seconds)) return
|
|
8422
|
+
if (!Number.isInteger(seconds)) return usage3(ctx);
|
|
8159
8423
|
if (key === "merge" && seconds >= 0 && seconds <= 300) policy.mergeWindowMs = seconds * 1e3;
|
|
8160
8424
|
else if (key === "batch" && seconds >= 1 && seconds <= 20) policy.maxBatchSize = seconds;
|
|
8161
8425
|
else if (key === "interval" && seconds >= 0 && seconds <= 3600) policy.minIntervalMs = seconds * 1e3;
|
|
8162
8426
|
else if (key === "dedupe" && seconds >= 0 && seconds <= 3600) policy.dedupeWindowMs = seconds * 1e3;
|
|
8163
|
-
else return
|
|
8427
|
+
else return usage3(ctx);
|
|
8164
8428
|
}
|
|
8165
|
-
if (tokens.length === 1) return
|
|
8429
|
+
if (tokens.length === 1) return usage3(ctx);
|
|
8166
8430
|
await store.set(ctx.scope, policy);
|
|
8167
|
-
await
|
|
8431
|
+
await reply7(ctx, `\u5DF2\u8BBE\u7F6E\u3002${describe2(policy, true, true)}`, `Updated. ${describe2(policy, true, false)}`);
|
|
8168
8432
|
}
|
|
8169
8433
|
async function canManageReplies(ctx) {
|
|
8170
8434
|
if (ctx.accessManager.isAdmin(ctx.senderId)) return true;
|
|
@@ -8178,10 +8442,10 @@ async function canManageReplies(ctx) {
|
|
|
8178
8442
|
function describe2(policy, configured, zh) {
|
|
8179
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.`;
|
|
8180
8444
|
}
|
|
8181
|
-
async function
|
|
8182
|
-
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]`");
|
|
8183
8447
|
}
|
|
8184
|
-
async function
|
|
8448
|
+
async function reply7(ctx, zh, en) {
|
|
8185
8449
|
await ctx.channel.sendMarkdown(ctx.chatId, bilingualMarkdown(zh, en), { replyTo: ctx.messageId });
|
|
8186
8450
|
}
|
|
8187
8451
|
|
|
@@ -8416,7 +8680,8 @@ var COMMAND_CATALOG = [
|
|
|
8416
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)" },
|
|
8417
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" },
|
|
8418
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)" },
|
|
8419
|
-
{ 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)" },
|
|
8420
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" },
|
|
8421
8686
|
{ names: ["/retention"], area: "session", zh: "`[N|default]` \u2014 \u4F1A\u8BDD\u4FDD\u7559\u6761\u6570", en: "`[N|default]` \u2014 retained live-message count" },
|
|
8422
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" },
|
|
@@ -8499,6 +8764,7 @@ var HELP = [
|
|
|
8499
8764
|
"- `/notify <scope|chatId> <text>` \u2014 \u8DE8\u4F1A\u8BDD\u53D1\u9001\u901A\u77E5\uFF08\u7BA1\u7406\u5458\uFF09",
|
|
8500
8765
|
"- `/notify list` \u2014 \u67E5\u770B\u5DF2\u6CE8\u518C scope",
|
|
8501
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",
|
|
8502
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",
|
|
8503
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",
|
|
8504
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",
|
|
@@ -8560,7 +8826,7 @@ var HELP_EN = [
|
|
|
8560
8826
|
"- `/invite user|admin|group <id>` \u2014 manage access allowlists",
|
|
8561
8827
|
"- `/help` \u2014 show this help"
|
|
8562
8828
|
].join("\n");
|
|
8563
|
-
async function
|
|
8829
|
+
async function reply8(ctx, zhCn, enUs) {
|
|
8564
8830
|
await ctx.channel.sendMarkdown(ctx.chatId, enUs === void 0 ? zhCn : bilingualMarkdown(zhCn, enUs, ctx.languagePolicies?.get().plain), {
|
|
8565
8831
|
replyTo: ctx.messageId
|
|
8566
8832
|
});
|
|
@@ -8569,7 +8835,7 @@ async function handleNew(_args, ctx) {
|
|
|
8569
8835
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8570
8836
|
const interrupted = await ctx.activeRuns.interruptWorkspace(ctx.scope, cwd);
|
|
8571
8837
|
ctx.sessions.clear(ctx.scope, cwd);
|
|
8572
|
-
await
|
|
8838
|
+
await reply8(
|
|
8573
8839
|
ctx,
|
|
8574
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",
|
|
8575
8841
|
interrupted > 0 ? `Interrupted ${String(interrupted)} task(s) and started a new session.` : "Started a new session."
|
|
@@ -8598,11 +8864,11 @@ function groupAppLink(chatId) {
|
|
|
8598
8864
|
async function handleNewGroup(args, ctx) {
|
|
8599
8865
|
const name2 = args.trim();
|
|
8600
8866
|
if (!name2) {
|
|
8601
|
-
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");
|
|
8602
8868
|
return;
|
|
8603
8869
|
}
|
|
8604
8870
|
if (name2.length > MAX_GROUP_NAME_LENGTH) {
|
|
8605
|
-
await
|
|
8871
|
+
await reply8(
|
|
8606
8872
|
ctx,
|
|
8607
8873
|
`\u7FA4\u540D\u8FC7\u957F\uFF08\u4E0A\u9650 ${String(MAX_GROUP_NAME_LENGTH)} \u5B57\u7B26\uFF0C\u5F53\u524D ${String(name2.length)}\uFF09\u3002`,
|
|
8608
8874
|
`The group name is too long (limit ${String(MAX_GROUP_NAME_LENGTH)} characters; received ${String(name2.length)}).`
|
|
@@ -8610,11 +8876,11 @@ async function handleNewGroup(args, ctx) {
|
|
|
8610
8876
|
return;
|
|
8611
8877
|
}
|
|
8612
8878
|
if (!ctx.channel.createChat) {
|
|
8613
|
-
await
|
|
8879
|
+
await reply8(ctx, "\u5F53\u524D\u6E20\u9053\u4E0D\u652F\u6301\u81EA\u52A8\u5EFA\u7FA4\u3002", "This channel cannot create groups automatically.");
|
|
8614
8880
|
return;
|
|
8615
8881
|
}
|
|
8616
8882
|
if (!ctx.senderId) {
|
|
8617
|
-
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.");
|
|
8618
8884
|
return;
|
|
8619
8885
|
}
|
|
8620
8886
|
try {
|
|
@@ -8625,7 +8891,7 @@ async function handleNewGroup(args, ctx) {
|
|
|
8625
8891
|
inviteUserIds: [ctx.senderId],
|
|
8626
8892
|
userIdType: "open_id"
|
|
8627
8893
|
});
|
|
8628
|
-
await
|
|
8894
|
+
await reply8(
|
|
8629
8895
|
ctx,
|
|
8630
8896
|
[
|
|
8631
8897
|
`\u2705 \u5DF2\u521B\u5EFA\u7FA4\u804A\uFF1A**${name2}**`,
|
|
@@ -8644,13 +8910,13 @@ async function handleNewGroup(args, ctx) {
|
|
|
8644
8910
|
);
|
|
8645
8911
|
} catch (error) {
|
|
8646
8912
|
const message = error instanceof Error ? error.message : String(error);
|
|
8647
|
-
await
|
|
8913
|
+
await reply8(ctx, `\u274C \u5EFA\u7FA4\u5931\u8D25\uFF1A\`${message}\``, `\u274C Failed to create the group: \`${message}\``);
|
|
8648
8914
|
}
|
|
8649
8915
|
}
|
|
8650
8916
|
async function handleCd(args, ctx) {
|
|
8651
8917
|
const path = args.trim();
|
|
8652
8918
|
if (!path) {
|
|
8653
|
-
await
|
|
8919
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`", "Usage: `/cd <path>`");
|
|
8654
8920
|
return;
|
|
8655
8921
|
}
|
|
8656
8922
|
const normalized = path.replace(/^~/u, "~");
|
|
@@ -8658,13 +8924,13 @@ async function handleCd(args, ctx) {
|
|
|
8658
8924
|
const cwd = resolve3(expanded);
|
|
8659
8925
|
const target = await stat2(cwd).catch(() => void 0);
|
|
8660
8926
|
if (!target?.isDirectory()) {
|
|
8661
|
-
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}\`.`);
|
|
8662
8928
|
return;
|
|
8663
8929
|
}
|
|
8664
8930
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8665
8931
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
8666
8932
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
8667
|
-
await
|
|
8933
|
+
await reply8(
|
|
8668
8934
|
ctx,
|
|
8669
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` : ""}`,
|
|
8670
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).` : ""}`
|
|
@@ -8684,7 +8950,7 @@ async function handleWs(args, ctx) {
|
|
|
8684
8950
|
const lines = Object.entries(named).map(
|
|
8685
8951
|
([key, value]) => `- **${key}** \u2192 \`${value}\`${value === current ? " \u2190 \u5F53\u524D" : ""}`
|
|
8686
8952
|
);
|
|
8687
|
-
await
|
|
8953
|
+
await reply8(
|
|
8688
8954
|
ctx,
|
|
8689
8955
|
[
|
|
8690
8956
|
`\u5F53\u524D cwd\uFF1A\`${current}\``,
|
|
@@ -8701,29 +8967,29 @@ async function handleWs(args, ctx) {
|
|
|
8701
8967
|
}
|
|
8702
8968
|
if (sub === "save") {
|
|
8703
8969
|
if (!name2) {
|
|
8704
|
-
await
|
|
8970
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`", "Usage: `/ws save <name>`");
|
|
8705
8971
|
return;
|
|
8706
8972
|
}
|
|
8707
8973
|
const current = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8708
8974
|
ctx.workspaces.saveNamed(name2, current);
|
|
8709
|
-
await
|
|
8975
|
+
await reply8(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}** \u2192 \`${current}\``, `Saved workspace: **${name2}** \u2192 \`${current}\``);
|
|
8710
8976
|
return;
|
|
8711
8977
|
}
|
|
8712
8978
|
if (sub === "use") {
|
|
8713
8979
|
if (!name2) {
|
|
8714
|
-
await
|
|
8980
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`", "Usage: `/ws use <name>`");
|
|
8715
8981
|
return;
|
|
8716
8982
|
}
|
|
8717
8983
|
const cwd = ctx.workspaces.getNamed(name2);
|
|
8718
8984
|
if (!cwd) {
|
|
8719
|
-
await
|
|
8985
|
+
await reply8(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name2}**`, `Workspace not found: **${name2}**`);
|
|
8720
8986
|
return;
|
|
8721
8987
|
}
|
|
8722
8988
|
const previous = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8723
8989
|
const interrupted = cwd === previous ? 0 : await ctx.activeRuns.interruptWorkspace(ctx.scope, previous);
|
|
8724
8990
|
ctx.workspaces.setCwd(ctx.scope, cwd);
|
|
8725
8991
|
ctx.workspaces.touchNamed(name2);
|
|
8726
|
-
await
|
|
8992
|
+
await reply8(
|
|
8727
8993
|
ctx,
|
|
8728
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` : ""}`,
|
|
8729
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).` : ""}`
|
|
@@ -8732,14 +8998,14 @@ async function handleWs(args, ctx) {
|
|
|
8732
8998
|
}
|
|
8733
8999
|
if (sub === "remove") {
|
|
8734
9000
|
if (!name2) {
|
|
8735
|
-
await
|
|
9001
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`", "Usage: `/ws remove <name>`");
|
|
8736
9002
|
return;
|
|
8737
9003
|
}
|
|
8738
9004
|
const removed = ctx.workspaces.removeNamed(name2);
|
|
8739
|
-
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}**`);
|
|
8740
9006
|
return;
|
|
8741
9007
|
}
|
|
8742
|
-
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.");
|
|
8743
9009
|
}
|
|
8744
9010
|
async function statusCardInputFor(ctx) {
|
|
8745
9011
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -8786,6 +9052,7 @@ async function statusCardInputFor(ctx) {
|
|
|
8786
9052
|
permissionPolicy: ctx.permissionPolicies?.get(ctx.scope) ?? "ask",
|
|
8787
9053
|
executionMode: ctx.executionModes?.get(ctx.scope) ?? "balanced",
|
|
8788
9054
|
...notificationPreference ? { notificationPreference } : {},
|
|
9055
|
+
...ctx.notificationSinks ? { notificationChannels: ctx.notificationSinks.enabledChannels().map((channel) => channel.id) } : {},
|
|
8789
9056
|
...replyPolicy ? { replyPolicy, replyPolicyConfigured: ctx.replyPolicies?.isConfigured(ctx.scope) ?? false } : {},
|
|
8790
9057
|
role: role ? `\`${role.id}\` (${role.name})` : void 0,
|
|
8791
9058
|
metrics,
|
|
@@ -8828,16 +9095,16 @@ async function handleStatus(_args, ctx) {
|
|
|
8828
9095
|
} catch {
|
|
8829
9096
|
}
|
|
8830
9097
|
}
|
|
8831
|
-
await
|
|
9098
|
+
await reply8(ctx, bilingualMarkdown(statusCardMarkdown(input), statusCardMarkdownEnglish(input)));
|
|
8832
9099
|
}
|
|
8833
9100
|
async function handleIsolation(args, ctx) {
|
|
8834
9101
|
if (ctx.chatMode === "p2p") {
|
|
8835
|
-
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.");
|
|
8836
9103
|
return;
|
|
8837
9104
|
}
|
|
8838
9105
|
const mode = args.trim().toLowerCase();
|
|
8839
9106
|
if (!mode) {
|
|
8840
|
-
await
|
|
9107
|
+
await reply8(
|
|
8841
9108
|
ctx,
|
|
8842
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`,
|
|
8843
9110
|
`Current group isolation mode: **${ctx.isolationStore?.get(ctx.chatId) ?? "topic"}**. Use \`/isolation group|topic|member\` to switch.`
|
|
@@ -8845,16 +9112,16 @@ async function handleIsolation(args, ctx) {
|
|
|
8845
9112
|
return;
|
|
8846
9113
|
}
|
|
8847
9114
|
if (mode !== "group" && mode !== "topic" && mode !== "member") {
|
|
8848
|
-
await
|
|
9115
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/isolation group|topic|member`", "Usage: `/isolation group|topic|member`");
|
|
8849
9116
|
return;
|
|
8850
9117
|
}
|
|
8851
|
-
if (!
|
|
9118
|
+
if (!requireAdmin4(ctx)) return;
|
|
8852
9119
|
if (!ctx.isolationStore) {
|
|
8853
|
-
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.");
|
|
8854
9121
|
return;
|
|
8855
9122
|
}
|
|
8856
9123
|
ctx.isolationStore.set(ctx.chatId, mode);
|
|
8857
|
-
await
|
|
9124
|
+
await reply8(
|
|
8858
9125
|
ctx,
|
|
8859
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`,
|
|
8860
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.`
|
|
@@ -8881,25 +9148,25 @@ async function handleVersion(_args, ctx) {
|
|
|
8881
9148
|
lines.push("\u6700\u65B0\u7248\u672C\u67E5\u8BE2\u6682\u4E0D\u53EF\u7528\uFF08\u7F51\u7EDC / registry \u5F02\u5E38\uFF09\u3002");
|
|
8882
9149
|
english.push("Latest-version lookup is unavailable (network or registry error).");
|
|
8883
9150
|
}
|
|
8884
|
-
await
|
|
9151
|
+
await reply8(ctx, lines.join("\n"), english.join("\n"));
|
|
8885
9152
|
}
|
|
8886
9153
|
async function handleUpgrade(_args, ctx) {
|
|
8887
|
-
if (!
|
|
9154
|
+
if (!requireAdmin4(ctx)) return;
|
|
8888
9155
|
if (!ctx.senderId || !ctx.channelUpdates) {
|
|
8889
|
-
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.");
|
|
8890
9157
|
return;
|
|
8891
9158
|
}
|
|
8892
9159
|
const result = await ctx.channelUpdates.check({ scope: ctx.scope, actorId: ctx.senderId });
|
|
8893
9160
|
if (result.kind === "unavailable") {
|
|
8894
|
-
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.");
|
|
8895
9162
|
return;
|
|
8896
9163
|
}
|
|
8897
9164
|
if (result.kind === "current") {
|
|
8898
|
-
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}\`.`);
|
|
8899
9166
|
return;
|
|
8900
9167
|
}
|
|
8901
9168
|
if (!ctx.channel.sendCard) {
|
|
8902
|
-
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.");
|
|
8903
9170
|
return;
|
|
8904
9171
|
}
|
|
8905
9172
|
await ctx.channel.sendCard(ctx.chatId, renderUpdateCard({
|
|
@@ -8914,9 +9181,9 @@ async function handleUpgrade(_args, ctx) {
|
|
|
8914
9181
|
});
|
|
8915
9182
|
}
|
|
8916
9183
|
async function handleDoctor(_args, ctx) {
|
|
8917
|
-
if (!
|
|
9184
|
+
if (!requireAdmin4(ctx)) return;
|
|
8918
9185
|
if (!ctx.createDiagnosticBundle || !ctx.channel.sendFile) {
|
|
8919
|
-
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.");
|
|
8920
9187
|
return;
|
|
8921
9188
|
}
|
|
8922
9189
|
let file;
|
|
@@ -8933,7 +9200,7 @@ async function handleDoctor(_args, ctx) {
|
|
|
8933
9200
|
...status.jobs ? { jobs: status.jobs } : {}
|
|
8934
9201
|
}), ctx.diagnosticTimeoutMs?.generate ?? DOCTOR_GENERATE_TIMEOUT_MS);
|
|
8935
9202
|
} catch {
|
|
8936
|
-
await
|
|
9203
|
+
await reply8(
|
|
8937
9204
|
ctx,
|
|
8938
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",
|
|
8939
9206
|
"\u26A0\uFE0F Failed to generate the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
@@ -8947,21 +9214,21 @@ async function handleDoctor(_args, ctx) {
|
|
|
8947
9214
|
}), ctx.diagnosticTimeoutMs?.upload ?? DOCTOR_UPLOAD_TIMEOUT_MS);
|
|
8948
9215
|
} catch (error) {
|
|
8949
9216
|
if (error instanceof OperationTimeoutError) {
|
|
8950
|
-
await
|
|
9217
|
+
await reply8(
|
|
8951
9218
|
ctx,
|
|
8952
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",
|
|
8953
9220
|
"\u26A0\uFE0F Diagnostic upload timed out with an unknown result: the file may still arrive later. Check this chat before retrying."
|
|
8954
9221
|
);
|
|
8955
9222
|
return;
|
|
8956
9223
|
}
|
|
8957
|
-
await
|
|
9224
|
+
await reply8(
|
|
8958
9225
|
ctx,
|
|
8959
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",
|
|
8960
9227
|
"\u26A0\uFE0F Failed to send the diagnostic bundle. Retry later; if it keeps failing, run `dsh-lark-bot doctor` locally."
|
|
8961
9228
|
);
|
|
8962
9229
|
return;
|
|
8963
9230
|
}
|
|
8964
|
-
await
|
|
9231
|
+
await reply8(
|
|
8965
9232
|
ctx,
|
|
8966
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",
|
|
8967
9234
|
"\u2705 The redacted diagnostic bundle was generated and sent. You can download and forward it to a maintainer."
|
|
@@ -8971,18 +9238,18 @@ async function handleResume(_args, ctx) {
|
|
|
8971
9238
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
8972
9239
|
const history = ctx.sessions.historyFor(ctx.scope, cwd);
|
|
8973
9240
|
if (history.length === 0) {
|
|
8974
|
-
await
|
|
9241
|
+
await reply8(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002", "This session has no conversation history.");
|
|
8975
9242
|
return;
|
|
8976
9243
|
}
|
|
8977
9244
|
const recent = history.slice(-6).map((message) => {
|
|
8978
9245
|
const speaker = message.role === "user" ? "\u{1F464}" : "\u{1F916}";
|
|
8979
9246
|
return `${speaker} ${message.content.slice(0, 300)}`;
|
|
8980
9247
|
});
|
|
8981
|
-
await
|
|
9248
|
+
await reply8(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"), [`Current scope: \`${ctx.scope}\``, "", ...recent].join("\n"));
|
|
8982
9249
|
}
|
|
8983
9250
|
async function handleJobs(args, ctx) {
|
|
8984
9251
|
if (!ctx.jobs) {
|
|
8985
|
-
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.");
|
|
8986
9253
|
return;
|
|
8987
9254
|
}
|
|
8988
9255
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -8990,11 +9257,11 @@ async function handleJobs(args, ctx) {
|
|
|
8990
9257
|
const records = ctx.jobs.list(ctx.scope, cwd, 20);
|
|
8991
9258
|
if (sub === "retry") {
|
|
8992
9259
|
if (!id) {
|
|
8993
|
-
await
|
|
9260
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/jobs retry <\u6D88\u606FID>`", "Usage: `/jobs retry <message-id>`");
|
|
8994
9261
|
return;
|
|
8995
9262
|
}
|
|
8996
9263
|
const queued = await ctx.requeueJob?.(id, ctx.scope, cwd);
|
|
8997
|
-
await
|
|
9264
|
+
await reply8(
|
|
8998
9265
|
ctx,
|
|
8999
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`,
|
|
9000
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.`
|
|
@@ -9004,19 +9271,19 @@ async function handleJobs(args, ctx) {
|
|
|
9004
9271
|
if (sub === "show") {
|
|
9005
9272
|
const record = id ? ctx.jobs.get(id, ctx.scope, cwd) : void 0;
|
|
9006
9273
|
if (!record) {
|
|
9007
|
-
await
|
|
9274
|
+
await reply8(ctx, `\u5F53\u524D workspace \u672A\u627E\u5230\u4EFB\u52A1\uFF1A**${id ?? ""}**`, `Job not found in this workspace: **${id ?? ""}**`);
|
|
9008
9275
|
return;
|
|
9009
9276
|
}
|
|
9010
|
-
await
|
|
9277
|
+
await reply8(ctx, jobDetail(record, "zh"), jobDetail(record, "en"));
|
|
9011
9278
|
return;
|
|
9012
9279
|
}
|
|
9013
9280
|
if (sub && sub !== "list") {
|
|
9014
|
-
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>]`");
|
|
9015
9282
|
return;
|
|
9016
9283
|
}
|
|
9017
9284
|
const counts = ctx.jobs.counts(ctx.scope, cwd);
|
|
9018
9285
|
const rows = records.slice(0, 10);
|
|
9019
|
-
await
|
|
9286
|
+
await reply8(
|
|
9020
9287
|
ctx,
|
|
9021
9288
|
[
|
|
9022
9289
|
"**\u4EFB\u52A1\u5BF9\u8D26**",
|
|
@@ -9063,7 +9330,7 @@ async function handleStop(_args, ctx) {
|
|
|
9063
9330
|
});
|
|
9064
9331
|
let stopped = 0;
|
|
9065
9332
|
for (const scope of scopes) stopped += await ctx.activeRuns.interrupt(scope);
|
|
9066
|
-
await
|
|
9333
|
+
await reply8(
|
|
9067
9334
|
ctx,
|
|
9068
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",
|
|
9069
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."
|
|
@@ -9074,7 +9341,7 @@ async function handleTimeout(args, ctx) {
|
|
|
9074
9341
|
const effectiveMs = ctx.runPolicies.get(ctx.scope) ?? ctx.defaultRunTimeoutMs;
|
|
9075
9342
|
if (!input) {
|
|
9076
9343
|
const minutes2 = effectiveMs > 0 ? Math.round(effectiveMs / 6e4) : 0;
|
|
9077
|
-
await
|
|
9344
|
+
await reply8(
|
|
9078
9345
|
ctx,
|
|
9079
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",
|
|
9080
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."
|
|
@@ -9083,27 +9350,27 @@ async function handleTimeout(args, ctx) {
|
|
|
9083
9350
|
}
|
|
9084
9351
|
if (input === "off") {
|
|
9085
9352
|
ctx.runPolicies.set(ctx.scope, 0);
|
|
9086
|
-
await
|
|
9353
|
+
await reply8(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u7A7A\u95F2\u8D85\u65F6\u3002", "Disabled the session idle timeout.");
|
|
9087
9354
|
return;
|
|
9088
9355
|
}
|
|
9089
9356
|
if (input === "default") {
|
|
9090
9357
|
ctx.runPolicies.clear(ctx.scope);
|
|
9091
|
-
await
|
|
9358
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u7A7A\u95F2\u8D85\u65F6\u3002", "Restored the default idle timeout.");
|
|
9092
9359
|
return;
|
|
9093
9360
|
}
|
|
9094
9361
|
const minutes = Number(input);
|
|
9095
9362
|
if (!Number.isInteger(minutes) || minutes <= 0) {
|
|
9096
|
-
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.");
|
|
9097
9364
|
return;
|
|
9098
9365
|
}
|
|
9099
9366
|
ctx.runPolicies.set(ctx.scope, minutes * 6e4);
|
|
9100
|
-
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.`);
|
|
9101
9368
|
}
|
|
9102
9369
|
async function handleConcurrency(args, ctx) {
|
|
9103
9370
|
const input = args.trim();
|
|
9104
9371
|
const effective = ctx.concurrencyStore.get(ctx.scope) ?? ctx.defaultScopeConcurrency;
|
|
9105
9372
|
if (!input) {
|
|
9106
|
-
await
|
|
9373
|
+
await reply8(
|
|
9107
9374
|
ctx,
|
|
9108
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`,
|
|
9109
9376
|
`Parallel tasks for this scope: **${String(effective)}**. Use \`/concurrency <N|default>\` to change it (N \u2265 1).`
|
|
@@ -9112,16 +9379,16 @@ async function handleConcurrency(args, ctx) {
|
|
|
9112
9379
|
}
|
|
9113
9380
|
if (input === "default") {
|
|
9114
9381
|
ctx.concurrencyStore.clear(ctx.scope);
|
|
9115
|
-
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)}).`);
|
|
9116
9383
|
return;
|
|
9117
9384
|
}
|
|
9118
9385
|
const n = Number(input);
|
|
9119
9386
|
if (!Number.isInteger(n) || n < 1) {
|
|
9120
|
-
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.");
|
|
9121
9388
|
return;
|
|
9122
9389
|
}
|
|
9123
9390
|
ctx.concurrencyStore.set(ctx.scope, n);
|
|
9124
|
-
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)}**.`);
|
|
9125
9392
|
}
|
|
9126
9393
|
async function handlePermission(args, ctx) {
|
|
9127
9394
|
const [rawPolicy = "", rawTarget = "", ...extra] = args.trim().split(/\s+/);
|
|
@@ -9133,7 +9400,7 @@ async function handlePermission(args, ctx) {
|
|
|
9133
9400
|
};
|
|
9134
9401
|
if (!input) {
|
|
9135
9402
|
const current = ctx.permissionPolicies?.get(ctx.scope) ?? "ask";
|
|
9136
|
-
await
|
|
9403
|
+
await reply8(
|
|
9137
9404
|
ctx,
|
|
9138
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`,
|
|
9139
9406
|
`Tool permission policy for this scope: **${labels[current].en}** (\`${current}\`). Use \`/permission ask|allow|deny\` to change it (admin only).`
|
|
@@ -9141,21 +9408,21 @@ async function handlePermission(args, ctx) {
|
|
|
9141
9408
|
return;
|
|
9142
9409
|
}
|
|
9143
9410
|
if (input !== "ask" && input !== "allow" && input !== "deny" || extra.length > 0) {
|
|
9144
|
-
await
|
|
9411
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/permission [ask|allow|deny] [scope]`", "Usage: `/permission [ask|allow|deny] [scope]`");
|
|
9145
9412
|
return;
|
|
9146
9413
|
}
|
|
9147
|
-
if (!
|
|
9414
|
+
if (!requireAdmin4(ctx)) return;
|
|
9148
9415
|
if (!ctx.permissionPolicies) {
|
|
9149
|
-
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.");
|
|
9150
9417
|
return;
|
|
9151
9418
|
}
|
|
9152
9419
|
const targetScope = rawTarget || ctx.scope;
|
|
9153
9420
|
if (targetScope !== ctx.chatId && !targetScope.startsWith(`${ctx.chatId}:`)) {
|
|
9154
|
-
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`.");
|
|
9155
9422
|
return;
|
|
9156
9423
|
}
|
|
9157
9424
|
await ctx.permissionPolicies.set(targetScope, input);
|
|
9158
|
-
await
|
|
9425
|
+
await reply8(
|
|
9159
9426
|
ctx,
|
|
9160
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`,
|
|
9161
9428
|
`Set scope \`${targetScope}\`'s tool permission policy to **${labels[input].en}** (\`${input}\`). It applies to the next tool approval; plan approval remains enforced.`
|
|
@@ -9165,7 +9432,7 @@ async function handleDensity(args, ctx) {
|
|
|
9165
9432
|
const input = args.trim().toLowerCase();
|
|
9166
9433
|
if (!input) {
|
|
9167
9434
|
const current = ctx.densityStore?.get(ctx.scope) ?? "standard";
|
|
9168
|
-
await
|
|
9435
|
+
await reply8(
|
|
9169
9436
|
ctx,
|
|
9170
9437
|
`\u5F53\u524D\u5361\u7247\u5BC6\u5EA6\uFF1A**${current}**\u3002\u53EF\u7528 \`/density compact|standard|detailed\` \u8C03\u6574\u3002`,
|
|
9171
9438
|
`Current card density: **${current}**. Use \`/density compact|standard|detailed\` to change it.`
|
|
@@ -9174,21 +9441,21 @@ async function handleDensity(args, ctx) {
|
|
|
9174
9441
|
}
|
|
9175
9442
|
if (input === "default") {
|
|
9176
9443
|
ctx.densityStore?.clear(ctx.scope);
|
|
9177
|
-
await
|
|
9444
|
+
await reply8(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002", "Restored the default card density.");
|
|
9178
9445
|
return;
|
|
9179
9446
|
}
|
|
9180
9447
|
const density = parseCardDensity(input);
|
|
9181
9448
|
if (!density) {
|
|
9182
|
-
await
|
|
9449
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`", "Usage: `/density [compact|standard|detailed|default]`");
|
|
9183
9450
|
return;
|
|
9184
9451
|
}
|
|
9185
9452
|
ctx.densityStore?.set(ctx.scope, density);
|
|
9186
|
-
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}**.`);
|
|
9187
9454
|
}
|
|
9188
9455
|
async function handleExecutionMode(args, ctx) {
|
|
9189
9456
|
const input = args.trim().toLowerCase();
|
|
9190
9457
|
if (!ctx.executionModes) {
|
|
9191
|
-
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.");
|
|
9192
9459
|
return;
|
|
9193
9460
|
}
|
|
9194
9461
|
if (!input) {
|
|
@@ -9205,7 +9472,7 @@ async function handleExecutionMode(args, ctx) {
|
|
|
9205
9472
|
} catch {
|
|
9206
9473
|
}
|
|
9207
9474
|
}
|
|
9208
|
-
await
|
|
9475
|
+
await reply8(
|
|
9209
9476
|
ctx,
|
|
9210
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`,
|
|
9211
9478
|
`Current execution mode: **${current}**. Use \`/mode quick|balanced|deep\`; changes apply next turn.`
|
|
@@ -9213,18 +9480,18 @@ async function handleExecutionMode(args, ctx) {
|
|
|
9213
9480
|
return;
|
|
9214
9481
|
}
|
|
9215
9482
|
if (input !== "quick" && input !== "balanced" && input !== "deep") {
|
|
9216
|
-
await
|
|
9483
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/mode [quick|balanced|deep]`", "Usage: `/mode [quick|balanced|deep]`");
|
|
9217
9484
|
return;
|
|
9218
9485
|
}
|
|
9219
9486
|
await ctx.executionModes.set(ctx.scope, input);
|
|
9220
|
-
await
|
|
9487
|
+
await reply8(
|
|
9221
9488
|
ctx,
|
|
9222
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`,
|
|
9223
9490
|
`Set this session\u2019s execution mode to **${input}** for the next turn. Active work and existing context are unchanged.`
|
|
9224
9491
|
);
|
|
9225
9492
|
}
|
|
9226
9493
|
async function handleEffortAlias(args, ctx) {
|
|
9227
|
-
await
|
|
9494
|
+
await reply8(
|
|
9228
9495
|
ctx,
|
|
9229
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",
|
|
9230
9497
|
"`/effort` is a compatibility alias for `/mode`; it controls task execution intensity, not the model reasoning-effort setting."
|
|
@@ -9234,11 +9501,11 @@ async function handleEffortAlias(args, ctx) {
|
|
|
9234
9501
|
async function handleAsk(args, ctx) {
|
|
9235
9502
|
const question = args.trim();
|
|
9236
9503
|
if (!question) {
|
|
9237
|
-
await
|
|
9504
|
+
await reply8(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`", "Usage: `/ask <question>`");
|
|
9238
9505
|
return;
|
|
9239
9506
|
}
|
|
9240
9507
|
if (!ctx.questions) {
|
|
9241
|
-
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).");
|
|
9242
9509
|
return;
|
|
9243
9510
|
}
|
|
9244
9511
|
const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
|
|
@@ -9260,18 +9527,18 @@ async function handleAsk(args, ctx) {
|
|
|
9260
9527
|
if (answer !== void 0) {
|
|
9261
9528
|
const text = Array.isArray(answer) ? answer.join("\u3001") : answer;
|
|
9262
9529
|
ctx.sessions.recordExchange(ctx.scope, cwd, [text], void 0);
|
|
9263
|
-
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.");
|
|
9264
9531
|
} else {
|
|
9265
|
-
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).");
|
|
9266
9533
|
}
|
|
9267
9534
|
}
|
|
9268
9535
|
async function handleInvite(args, ctx) {
|
|
9269
9536
|
const [kind, ...rest] = args.trim().split(/\s+/);
|
|
9270
9537
|
const id = rest.join(" ").trim();
|
|
9271
|
-
if (kind !== "list" && !
|
|
9538
|
+
if (kind !== "list" && !requireAdmin4(ctx)) return;
|
|
9272
9539
|
if (kind === "list") {
|
|
9273
9540
|
const snapshot = ctx.accessManager.snapshot();
|
|
9274
|
-
await
|
|
9541
|
+
await reply8(
|
|
9275
9542
|
ctx,
|
|
9276
9543
|
[
|
|
9277
9544
|
"**\u8BBF\u95EE\u767D\u540D\u5355**",
|
|
@@ -9289,7 +9556,7 @@ async function handleInvite(args, ctx) {
|
|
|
9289
9556
|
return;
|
|
9290
9557
|
}
|
|
9291
9558
|
if (!kind || !id) {
|
|
9292
|
-
await
|
|
9559
|
+
await reply8(
|
|
9293
9560
|
ctx,
|
|
9294
9561
|
"\u7528\u6CD5\uFF1A`/invite user|admin|group <id>`\u3001`/invite list`\u3001`/invite remove user|group <id>`",
|
|
9295
9562
|
"Usage: `/invite user|admin|group <id>`, `/invite list`, or `/invite remove user|group <id>`"
|
|
@@ -9298,39 +9565,39 @@ async function handleInvite(args, ctx) {
|
|
|
9298
9565
|
}
|
|
9299
9566
|
if (kind === "user") {
|
|
9300
9567
|
await ctx.accessManager.addUser(id);
|
|
9301
|
-
await
|
|
9568
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``, `Allowed user: \`${id}\``);
|
|
9302
9569
|
return;
|
|
9303
9570
|
}
|
|
9304
9571
|
if (kind === "admin") {
|
|
9305
9572
|
await ctx.accessManager.addAdmin(id);
|
|
9306
|
-
await
|
|
9573
|
+
await reply8(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``, `Granted admin access to: \`${id}\``);
|
|
9307
9574
|
return;
|
|
9308
9575
|
}
|
|
9309
9576
|
if (kind === "group") {
|
|
9310
9577
|
await ctx.accessManager.addChat(id);
|
|
9311
|
-
await
|
|
9578
|
+
await reply8(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``, `Allowed group chat: \`${id}\``);
|
|
9312
9579
|
return;
|
|
9313
9580
|
}
|
|
9314
9581
|
if (kind === "remove") {
|
|
9315
9582
|
const [sub, target] = rest;
|
|
9316
9583
|
if (sub === "user" && target) {
|
|
9317
9584
|
await ctx.accessManager.removeUser(target);
|
|
9318
|
-
await
|
|
9585
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``, `Removed user: \`${target}\``);
|
|
9319
9586
|
return;
|
|
9320
9587
|
}
|
|
9321
9588
|
if (sub === "group" && target) {
|
|
9322
9589
|
await ctx.accessManager.removeChat(target);
|
|
9323
|
-
await
|
|
9590
|
+
await reply8(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``, `Removed group chat: \`${target}\``);
|
|
9324
9591
|
return;
|
|
9325
9592
|
}
|
|
9326
|
-
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>`");
|
|
9327
9594
|
return;
|
|
9328
9595
|
}
|
|
9329
|
-
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.");
|
|
9330
9597
|
}
|
|
9331
|
-
function
|
|
9598
|
+
function requireAdmin4(ctx) {
|
|
9332
9599
|
if (!ctx.accessManager.isAdmin(ctx.senderId)) {
|
|
9333
|
-
void
|
|
9600
|
+
void reply8(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u8BE5\u64CD\u4F5C\u3002", "Only admins can perform this operation.");
|
|
9334
9601
|
return false;
|
|
9335
9602
|
}
|
|
9336
9603
|
return true;
|
|
@@ -9383,20 +9650,20 @@ async function handleKeyDispatch(args, ctx) {
|
|
|
9383
9650
|
await handleKey(args, ctx);
|
|
9384
9651
|
}
|
|
9385
9652
|
async function handleHelp(_args, ctx) {
|
|
9386
|
-
await
|
|
9653
|
+
await reply8(ctx, renderCommandHelp("zh"), renderCommandHelp("en"));
|
|
9387
9654
|
}
|
|
9388
9655
|
async function handleLanguage(args, ctx) {
|
|
9389
9656
|
if (!ctx.languagePolicies) {
|
|
9390
|
-
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.");
|
|
9391
9658
|
return;
|
|
9392
9659
|
}
|
|
9393
9660
|
const [action = "show", field, value] = args.trim().split(/\s+/);
|
|
9394
9661
|
if (action === "show") {
|
|
9395
9662
|
const policy = ctx.languagePolicies.get();
|
|
9396
|
-
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}`);
|
|
9397
9664
|
return;
|
|
9398
9665
|
}
|
|
9399
|
-
if (!
|
|
9666
|
+
if (!requireAdmin4(ctx)) return;
|
|
9400
9667
|
if (action === "set" && field === "plain" && (value === "bilingual" || value === "zh" || value === "en")) {
|
|
9401
9668
|
await ctx.languagePolicies.set({ plain: value });
|
|
9402
9669
|
} else if (action === "set" && field === "agent" && (value === "auto" || value === "zh" || value === "en")) {
|
|
@@ -9404,7 +9671,7 @@ async function handleLanguage(args, ctx) {
|
|
|
9404
9671
|
} else if (action === "reset" && (field === void 0 || field === "all" || field === "plain" || field === "agent")) {
|
|
9405
9672
|
await ctx.languagePolicies.reset(field ?? "all");
|
|
9406
9673
|
} else {
|
|
9407
|
-
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]`");
|
|
9408
9675
|
return;
|
|
9409
9676
|
}
|
|
9410
9677
|
process.env.DSH_LARK_REPLY_LANG = ctx.languagePolicies.get().plain;
|
|
@@ -9415,37 +9682,37 @@ function parseSecretTarget(value) {
|
|
|
9415
9682
|
}
|
|
9416
9683
|
async function handleSecret(args, ctx) {
|
|
9417
9684
|
if (!ctx.secretService) {
|
|
9418
|
-
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.");
|
|
9419
9686
|
return;
|
|
9420
9687
|
}
|
|
9421
9688
|
const [action, targetRaw, referenceRaw, ...purposeParts] = args.trim().split(/\s+/);
|
|
9422
9689
|
const target = parseSecretTarget(targetRaw);
|
|
9423
9690
|
const reference = referenceRaw?.trim();
|
|
9424
9691
|
if (!target || !reference || !["status", "set", "remove"].includes(action ?? "")) {
|
|
9425
|
-
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>`");
|
|
9426
9693
|
return;
|
|
9427
9694
|
}
|
|
9428
|
-
if (action !== "status" && !
|
|
9695
|
+
if (action !== "status" && !requireAdmin4(ctx)) return;
|
|
9429
9696
|
if (action === "status") {
|
|
9430
9697
|
const configured = await ctx.secretService.configured(target, reference);
|
|
9431
|
-
await
|
|
9698
|
+
await reply8(ctx, `${target} \`${reference}\`\uFF1A${configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E"}`, `${target} \`${reference}\`: ${configured ? "configured" : "not configured"}`);
|
|
9432
9699
|
return;
|
|
9433
9700
|
}
|
|
9434
9701
|
if (action === "remove") {
|
|
9435
9702
|
const removed = await ctx.secretService.remove(target, reference);
|
|
9436
|
-
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.");
|
|
9437
9704
|
return;
|
|
9438
9705
|
}
|
|
9439
9706
|
if (!ctx.senderId) {
|
|
9440
|
-
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.");
|
|
9441
9708
|
return;
|
|
9442
9709
|
}
|
|
9443
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" });
|
|
9444
|
-
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.");
|
|
9445
9712
|
}
|
|
9446
9713
|
async function handleSessionProjection(args, ctx) {
|
|
9447
9714
|
if (!ctx.sessionProjection) {
|
|
9448
|
-
await
|
|
9715
|
+
await reply8(
|
|
9449
9716
|
ctx,
|
|
9450
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",
|
|
9451
9718
|
"The current adapter does not provide DSH session projection; use the `web` adapter and ensure the Web host is reachable."
|
|
@@ -9475,6 +9742,7 @@ var handlers = {
|
|
|
9475
9742
|
"/role": handleRole,
|
|
9476
9743
|
"/notify": handleNotify,
|
|
9477
9744
|
"/notifications": handleNotifications,
|
|
9745
|
+
"/channels": handleChannels,
|
|
9478
9746
|
"/replies": handleReplies,
|
|
9479
9747
|
"/retention": handleRetention,
|
|
9480
9748
|
"/archive": handleArchive,
|
|
@@ -9954,24 +10222,26 @@ var GroupMessagePoller = class {
|
|
|
9954
10222
|
|
|
9955
10223
|
// src/bridge/reconnect-notifier.ts
|
|
9956
10224
|
var ReconnectNotifier = class {
|
|
9957
|
-
constructor(channel, directory, now = Date.now, reconciliation) {
|
|
10225
|
+
constructor(channel, directory, now = Date.now, reconciliation, onFault) {
|
|
9958
10226
|
this.channel = channel;
|
|
9959
10227
|
this.directory = directory;
|
|
9960
10228
|
this.now = now;
|
|
9961
10229
|
this.reconciliation = reconciliation;
|
|
10230
|
+
this.onFault = onFault;
|
|
9962
10231
|
}
|
|
9963
10232
|
channel;
|
|
9964
10233
|
directory;
|
|
9965
10234
|
now;
|
|
9966
10235
|
reconciliation;
|
|
10236
|
+
onFault;
|
|
9967
10237
|
startedAt;
|
|
9968
10238
|
async reconnecting() {
|
|
9969
10239
|
if (this.startedAt !== void 0) return;
|
|
9970
10240
|
this.startedAt = this.now();
|
|
9971
|
-
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
);
|
|
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 });
|
|
9975
10245
|
}
|
|
9976
10246
|
async reconnected() {
|
|
9977
10247
|
if (this.startedAt === void 0) return;
|
|
@@ -9979,12 +10249,12 @@ var ReconnectNotifier = class {
|
|
|
9979
10249
|
this.startedAt = void 0;
|
|
9980
10250
|
const target = this.directory?.recentDestination();
|
|
9981
10251
|
const summary = target ? this.reconciliation?.(target.scope) : void 0;
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
${summary
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
);
|
|
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 });
|
|
9988
10258
|
}
|
|
9989
10259
|
async send(zhCn, enUs) {
|
|
9990
10260
|
const target = this.directory?.recentDestination();
|
|
@@ -9994,6 +10264,13 @@ ${summary.enUs}` : ""}`
|
|
|
9994
10264
|
...target.threadId ? { threadId: target.threadId } : {}
|
|
9995
10265
|
});
|
|
9996
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
|
+
}
|
|
9997
10274
|
};
|
|
9998
10275
|
function formatDurationEnglish(ms) {
|
|
9999
10276
|
if (ms < 1e3) return "less than 1 second";
|
|
@@ -11196,7 +11473,8 @@ async function startChannel(deps) {
|
|
|
11196
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`,
|
|
11197
11474
|
enUs: `Ledger reconciliation: queued ${counts.queued} \xB7 running ${counts.running} \xB7 interrupted ${counts.interrupted} \xB7 failed ${counts.failed}; use \`/jobs\` for details.`
|
|
11198
11475
|
};
|
|
11199
|
-
} : void 0
|
|
11476
|
+
} : void 0,
|
|
11477
|
+
deps.faultNotifier
|
|
11200
11478
|
);
|
|
11201
11479
|
const isolationStore = deps.isolationStore ?? EMPTY_ISOLATION_STORE;
|
|
11202
11480
|
let groupPoller;
|
|
@@ -11331,6 +11609,8 @@ async function startChannel(deps) {
|
|
|
11331
11609
|
...deps.permissionPolicies ? { permissionPolicies: deps.permissionPolicies } : {},
|
|
11332
11610
|
...deps.notificationPreferences ? { notificationPreferences: deps.notificationPreferences } : {},
|
|
11333
11611
|
...deps.defaultNotificationPreference ? { defaultNotificationPreference: deps.defaultNotificationPreference } : {},
|
|
11612
|
+
...deps.notificationChannels ? { notificationChannels: deps.notificationChannels } : {},
|
|
11613
|
+
...deps.notificationSinks ? { notificationSinks: deps.notificationSinks } : {},
|
|
11334
11614
|
...deps.replyPolicies ? { replyPolicies: deps.replyPolicies } : {},
|
|
11335
11615
|
...deps.executionModes ? { executionModes: deps.executionModes } : {},
|
|
11336
11616
|
...deps.languagePolicies ? { languagePolicies: deps.languagePolicies } : {},
|
|
@@ -12053,7 +12333,7 @@ function attachRunCardAnchors(channel, anchors) {
|
|
|
12053
12333
|
}
|
|
12054
12334
|
|
|
12055
12335
|
// src/bridge/scope-directory.ts
|
|
12056
|
-
import { readFile as
|
|
12336
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
12057
12337
|
var ScopeDirectory = class {
|
|
12058
12338
|
data = { entries: {} };
|
|
12059
12339
|
saving = Promise.resolve();
|
|
@@ -12063,7 +12343,7 @@ var ScopeDirectory = class {
|
|
|
12063
12343
|
}
|
|
12064
12344
|
async load() {
|
|
12065
12345
|
try {
|
|
12066
|
-
const raw = await
|
|
12346
|
+
const raw = await readFile12(this.path, "utf8");
|
|
12067
12347
|
const parsed = JSON.parse(raw);
|
|
12068
12348
|
this.data = { entries: parsed.entries ?? {} };
|
|
12069
12349
|
} catch (error) {
|
|
@@ -12804,6 +13084,11 @@ var NotificationDispatcher = class {
|
|
|
12804
13084
|
setDefaultPreference(preference) {
|
|
12805
13085
|
this.defaultPreference = clonePreference(preference);
|
|
12806
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
|
+
*/
|
|
12807
13092
|
async notify(scope, event, detail) {
|
|
12808
13093
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
12809
13094
|
if (!preference?.events.includes(event)) return false;
|
|
@@ -12812,21 +13097,52 @@ var NotificationDispatcher = class {
|
|
|
12812
13097
|
log.warn("notification", "target-unavailable", { scope, target: preference.target });
|
|
12813
13098
|
return false;
|
|
12814
13099
|
}
|
|
12815
|
-
const title = event
|
|
13100
|
+
const title = titleForEvent(event);
|
|
12816
13101
|
try {
|
|
12817
|
-
await this.deps.send(destination.chatId,
|
|
12818
|
-
`${title[0]}\uFF08scope \`${scope}\`\uFF09${detail ? `\uFF1A${detail}` : ""}`,
|
|
12819
|
-
`${title[1]} (scope \`${scope}\`)${detail ? `: ${detail}` : ""}`
|
|
12820
|
-
), {
|
|
13102
|
+
await this.deps.send(destination.chatId, renderFeishuBody(title, scope, detail), {
|
|
12821
13103
|
...destination.threadId ? { threadId: destination.threadId } : {},
|
|
12822
13104
|
...destination.messageId ? { replyTo: destination.messageId } : {},
|
|
12823
13105
|
...preference.mentionUserIds.length > 0 ? { mentions: preference.mentionUserIds.map((userId) => ({ userId })) } : {}
|
|
12824
13106
|
});
|
|
12825
|
-
return true;
|
|
12826
13107
|
} catch (error) {
|
|
12827
13108
|
log.warn("notification", "send-failed", { scope, event, error });
|
|
12828
13109
|
return false;
|
|
12829
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
|
+
}
|
|
12830
13146
|
}
|
|
12831
13147
|
scheduleApprovalReminder(scope, toolName) {
|
|
12832
13148
|
const preference = this.deps.preferences.resolve(scope, this.defaultPreference);
|
|
@@ -12837,11 +13153,197 @@ var NotificationDispatcher = class {
|
|
|
12837
13153
|
timer.unref?.();
|
|
12838
13154
|
return () => clearTimeout(timer);
|
|
12839
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
|
+
}
|
|
12840
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
|
+
}
|
|
12841
13191
|
function clonePreference(preference) {
|
|
12842
|
-
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;
|
|
12843
13193
|
}
|
|
12844
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
|
+
|
|
12845
13347
|
// src/bridge/reply-dispatcher.ts
|
|
12846
13348
|
var ReplyDispatcher = class {
|
|
12847
13349
|
constructor(deps) {
|
|
@@ -12900,8 +13402,8 @@ var ReplyDispatcher = class {
|
|
|
12900
13402
|
}
|
|
12901
13403
|
}
|
|
12902
13404
|
};
|
|
12903
|
-
function replyRoute(
|
|
12904
|
-
return `${
|
|
13405
|
+
function replyRoute(reply9) {
|
|
13406
|
+
return `${reply9.chatId}\0${reply9.options?.threadId ?? ""}`;
|
|
12905
13407
|
}
|
|
12906
13408
|
function mergedMarkdown(batch) {
|
|
12907
13409
|
if (batch.length === 1) return batch[0].markdown;
|
|
@@ -12952,6 +13454,7 @@ function resolveAppPaths(root = defaultHome()) {
|
|
|
12952
13454
|
jobsFile: (profile) => profilePath(profile, "jobs.json"),
|
|
12953
13455
|
permissionPoliciesFile: (profile) => profilePath(profile, "permission-policies.json"),
|
|
12954
13456
|
notificationPreferencesFile: (profile) => profilePath(profile, "notification-preferences.json"),
|
|
13457
|
+
notificationChannelsFile: (profile) => profilePath(profile, "notification-channels.json"),
|
|
12955
13458
|
replyPoliciesFile: (profile) => profilePath(profile, "reply-policies.json"),
|
|
12956
13459
|
executionModesFile: (profile) => profilePath(profile, "execution-modes.json"),
|
|
12957
13460
|
languagePoliciesFile: (profile) => profilePath(profile, "language-policy.json"),
|
|
@@ -13305,11 +13808,11 @@ function loadRuntimeEnv(source = process.env) {
|
|
|
13305
13808
|
}
|
|
13306
13809
|
|
|
13307
13810
|
// src/config/profile-store.ts
|
|
13308
|
-
import { mkdir as mkdir7, readFile as
|
|
13811
|
+
import { mkdir as mkdir7, readFile as readFile14 } from "fs/promises";
|
|
13309
13812
|
import { dirname as dirname8 } from "path";
|
|
13310
13813
|
|
|
13311
13814
|
// src/platform/file-lock.ts
|
|
13312
|
-
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";
|
|
13313
13816
|
import { randomUUID as randomUUID10 } from "crypto";
|
|
13314
13817
|
import { dirname as dirname7, join as join12 } from "path";
|
|
13315
13818
|
var RETRY_MS = 10;
|
|
@@ -13393,7 +13896,7 @@ async function olderThan(path, thresholdMs) {
|
|
|
13393
13896
|
}
|
|
13394
13897
|
async function readLease(path) {
|
|
13395
13898
|
try {
|
|
13396
|
-
const value = JSON.parse(await
|
|
13899
|
+
const value = JSON.parse(await readFile13(path, "utf8"));
|
|
13397
13900
|
return typeof value.pid === "number" && typeof value.token === "string" && typeof value.createdAt === "number" ? value : void 0;
|
|
13398
13901
|
} catch {
|
|
13399
13902
|
return void 0;
|
|
@@ -13421,7 +13924,7 @@ var ConfigStore = class {
|
|
|
13421
13924
|
return this.data;
|
|
13422
13925
|
}
|
|
13423
13926
|
try {
|
|
13424
|
-
const raw = await
|
|
13927
|
+
const raw = await readFile14(this.path, "utf8");
|
|
13425
13928
|
const parsed = JSON.parse(raw);
|
|
13426
13929
|
this.data = {
|
|
13427
13930
|
schemaVersion: 1,
|
|
@@ -13589,7 +14092,7 @@ async function onboardPersonalAgent(deps = {}) {
|
|
|
13589
14092
|
}
|
|
13590
14093
|
|
|
13591
14094
|
// src/media/attachments.ts
|
|
13592
|
-
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";
|
|
13593
14096
|
import { join as join13 } from "path";
|
|
13594
14097
|
var MAX_TEXT_FILE_BYTES = 256e3;
|
|
13595
14098
|
function assertSafeMediaName(mediaDir, destination) {
|
|
@@ -13630,7 +14133,7 @@ async function prepareAttachments(channel, message, mediaDir) {
|
|
|
13630
14133
|
result.textFileNotes.push(`[attachment: ${resource.fileName ?? resource.fileKey}] ${destination}`);
|
|
13631
14134
|
continue;
|
|
13632
14135
|
}
|
|
13633
|
-
const content2 = await
|
|
14136
|
+
const content2 = await readFile15(destination, "utf8");
|
|
13634
14137
|
const safeContent = truncateUtf8Safe(content2, MAX_TEXT_FILE_BYTES);
|
|
13635
14138
|
result.textFileNotes.push(
|
|
13636
14139
|
`[attachment: ${resource.fileName ?? resource.fileKey}]
|
|
@@ -13641,7 +14144,7 @@ ${safeContent}`
|
|
|
13641
14144
|
}
|
|
13642
14145
|
|
|
13643
14146
|
// src/session/store.ts
|
|
13644
|
-
import { readFile as
|
|
14147
|
+
import { readFile as readFile16 } from "fs/promises";
|
|
13645
14148
|
var MAX_CONTEXT_SNAPSHOTS_PER_WORKSPACE = 32;
|
|
13646
14149
|
var SessionStore = class {
|
|
13647
14150
|
constructor(path) {
|
|
@@ -13656,7 +14159,7 @@ var SessionStore = class {
|
|
|
13656
14159
|
pendingArchive = Promise.resolve();
|
|
13657
14160
|
async load() {
|
|
13658
14161
|
try {
|
|
13659
|
-
const raw = JSON.parse(await
|
|
14162
|
+
const raw = JSON.parse(await readFile16(this.path, "utf8"));
|
|
13660
14163
|
const legacy = raw.schema !== 2 || !isObject(raw.workspaces);
|
|
13661
14164
|
this.data = legacy ? migrateLegacy(raw) : normalizeV2(raw.workspaces);
|
|
13662
14165
|
this.legacyScopes = legacy ? new Set(Object.keys(this.data.workspaces)) : /* @__PURE__ */ new Set();
|
|
@@ -13753,13 +14256,13 @@ var SessionStore = class {
|
|
|
13753
14256
|
fullHistoryFor(scopeId, cwd) {
|
|
13754
14257
|
return this.historyFor(scopeId, cwd);
|
|
13755
14258
|
}
|
|
13756
|
-
recordUsage(scopeId, cwd,
|
|
14259
|
+
recordUsage(scopeId, cwd, usage4) {
|
|
13757
14260
|
const state = this.ensureState(scopeId, cwd);
|
|
13758
14261
|
const next = { ...state.metrics };
|
|
13759
|
-
addMetric(next, "inputTokens",
|
|
13760
|
-
addMetric(next, "outputTokens",
|
|
13761
|
-
addMetric(next, "cacheReadTokens",
|
|
13762
|
-
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);
|
|
13763
14266
|
state.metrics = next;
|
|
13764
14267
|
this.schedulePersist();
|
|
13765
14268
|
}
|
|
@@ -13975,7 +14478,7 @@ function normalizeContextSnapshot(value) {
|
|
|
13975
14478
|
}
|
|
13976
14479
|
|
|
13977
14480
|
// src/session/projection-store.ts
|
|
13978
|
-
import { readFile as
|
|
14481
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
13979
14482
|
import { randomUUID as randomUUID11 } from "crypto";
|
|
13980
14483
|
var MAX_RECENT_MESSAGES = 64;
|
|
13981
14484
|
var MAX_PROMPT_CORRELATIONS = 64;
|
|
@@ -13989,7 +14492,7 @@ var SessionProjectionStore = class {
|
|
|
13989
14492
|
saving = Promise.resolve();
|
|
13990
14493
|
async load() {
|
|
13991
14494
|
try {
|
|
13992
|
-
const parsed = JSON.parse(await
|
|
14495
|
+
const parsed = JSON.parse(await readFile17(this.path, "utf8"));
|
|
13993
14496
|
const bindings = Object.fromEntries(
|
|
13994
14497
|
Object.values(parsed.bindings ?? {}).flatMap((value) => {
|
|
13995
14498
|
const normalized = normalizeBinding(value);
|
|
@@ -14201,7 +14704,7 @@ function normalizeCorrelation(value) {
|
|
|
14201
14704
|
// src/session/archive.ts
|
|
14202
14705
|
import { execFile } from "child_process";
|
|
14203
14706
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
14204
|
-
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";
|
|
14205
14708
|
import { join as join14 } from "path";
|
|
14206
14709
|
import { promisify } from "util";
|
|
14207
14710
|
var execFileAsync = promisify(execFile);
|
|
@@ -14383,8 +14886,8 @@ var SessionArchive = class {
|
|
|
14383
14886
|
const jsonlPath = join14(scopeDir, `${id}.jsonl`);
|
|
14384
14887
|
const markdownPath = join14(scopeDir, `${id}.md`);
|
|
14385
14888
|
const [jsonl, markdown2] = await Promise.all([
|
|
14386
|
-
|
|
14387
|
-
|
|
14889
|
+
readFile18(jsonlPath, "utf8"),
|
|
14890
|
+
readFile18(markdownPath, "utf8")
|
|
14388
14891
|
]);
|
|
14389
14892
|
const [first = "", ...rest] = jsonl.split("\n");
|
|
14390
14893
|
const header = JSON.parse(first);
|
|
@@ -14499,7 +15002,7 @@ async function defaultRunGit(args, cwd) {
|
|
|
14499
15002
|
}
|
|
14500
15003
|
async function readArchiveHeader(jsonlPath) {
|
|
14501
15004
|
try {
|
|
14502
|
-
const raw = await
|
|
15005
|
+
const raw = await readFile18(jsonlPath, "utf8");
|
|
14503
15006
|
const firstLine = raw.split("\n", 1)[0];
|
|
14504
15007
|
if (!firstLine) return void 0;
|
|
14505
15008
|
const header = JSON.parse(firstLine);
|
|
@@ -14549,7 +15052,7 @@ async function defaultCopyRulesFile(source, target) {
|
|
|
14549
15052
|
await mkdir10(dirname9(target), { recursive: true });
|
|
14550
15053
|
await copyFile(source, target);
|
|
14551
15054
|
}
|
|
14552
|
-
function
|
|
15055
|
+
function slugify2(scope) {
|
|
14553
15056
|
const slug = scope.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[.]+/, "").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
14554
15057
|
return slug || "session";
|
|
14555
15058
|
}
|
|
@@ -14565,7 +15068,7 @@ var GitWorktreeManager = class {
|
|
|
14565
15068
|
async ensure(scope, base) {
|
|
14566
15069
|
const isGit = await this.isGitRepository(base);
|
|
14567
15070
|
if (!isGit) return { cwd: base, created: false };
|
|
14568
|
-
const scopeSlug =
|
|
15071
|
+
const scopeSlug = slugify2(scope);
|
|
14569
15072
|
const project = createHash2("sha256").update(base).digest("hex").slice(0, 10);
|
|
14570
15073
|
const slug = `${scopeSlug}-${project}`;
|
|
14571
15074
|
const target = join15(this.options.worktreesRoot, slug);
|
|
@@ -14602,7 +15105,7 @@ var GitWorktreeManager = class {
|
|
|
14602
15105
|
}
|
|
14603
15106
|
/** Owning main worktree for the legacy scope-only linked worktree. */
|
|
14604
15107
|
async legacyWorkspaceBase(scope) {
|
|
14605
|
-
const legacyTarget = join15(this.options.worktreesRoot,
|
|
15108
|
+
const legacyTarget = join15(this.options.worktreesRoot, slugify2(scope));
|
|
14606
15109
|
if (!await exists2(legacyTarget)) return void 0;
|
|
14607
15110
|
try {
|
|
14608
15111
|
const output = await this.runGit(["worktree", "list", "--porcelain"], legacyTarget);
|
|
@@ -14634,7 +15137,7 @@ async function samePath(left, right) {
|
|
|
14634
15137
|
}
|
|
14635
15138
|
|
|
14636
15139
|
// src/workspace/store.ts
|
|
14637
|
-
import { readFile as
|
|
15140
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
14638
15141
|
var WorkspaceStore = class {
|
|
14639
15142
|
data = { chats: {}, named: {}, lastUsed: {} };
|
|
14640
15143
|
saving = Promise.resolve();
|
|
@@ -14644,7 +15147,7 @@ var WorkspaceStore = class {
|
|
|
14644
15147
|
}
|
|
14645
15148
|
async load() {
|
|
14646
15149
|
try {
|
|
14647
|
-
const raw = await
|
|
15150
|
+
const raw = await readFile19(this.path, "utf8");
|
|
14648
15151
|
const parsed = JSON.parse(raw);
|
|
14649
15152
|
this.data = {
|
|
14650
15153
|
chats: parsed.chats ?? {},
|
|
@@ -14716,7 +15219,7 @@ var WorkspaceStore = class {
|
|
|
14716
15219
|
};
|
|
14717
15220
|
|
|
14718
15221
|
// src/guardian/heartbeat.ts
|
|
14719
|
-
import { readFile as
|
|
15222
|
+
import { readFile as readFile20 } from "fs/promises";
|
|
14720
15223
|
var DEFAULT_HEARTBEAT_INTERVAL_MS = 5e3;
|
|
14721
15224
|
function startHeartbeat(file, pid, intervalMs = DEFAULT_HEARTBEAT_INTERVAL_MS, getChannelHealth) {
|
|
14722
15225
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -14797,7 +15300,7 @@ var UpdateNotifier = class {
|
|
|
14797
15300
|
};
|
|
14798
15301
|
|
|
14799
15302
|
// src/bot/fleet-store.ts
|
|
14800
|
-
import { mkdir as mkdir11, readFile as
|
|
15303
|
+
import { mkdir as mkdir11, readFile as readFile21 } from "fs/promises";
|
|
14801
15304
|
import { dirname as dirname10 } from "path";
|
|
14802
15305
|
function validBotInstanceName(name2) {
|
|
14803
15306
|
return /^[a-z][a-z0-9-]{0,31}$/u.test(name2);
|
|
@@ -14810,7 +15313,7 @@ var BotFleetStore = class {
|
|
|
14810
15313
|
data = { schemaVersion: 1, bots: {} };
|
|
14811
15314
|
async load() {
|
|
14812
15315
|
try {
|
|
14813
|
-
const parsed = JSON.parse(await
|
|
15316
|
+
const parsed = JSON.parse(await readFile21(this.path, "utf8"));
|
|
14814
15317
|
this.data = {
|
|
14815
15318
|
schemaVersion: 1,
|
|
14816
15319
|
bots: Object.fromEntries(
|
|
@@ -14938,7 +15441,7 @@ function isFleetEntry(value) {
|
|
|
14938
15441
|
}
|
|
14939
15442
|
|
|
14940
15443
|
// src/bot/handoff-guard.ts
|
|
14941
|
-
import { mkdir as mkdir12, readFile as
|
|
15444
|
+
import { mkdir as mkdir12, readFile as readFile22 } from "fs/promises";
|
|
14942
15445
|
import { dirname as dirname11 } from "path";
|
|
14943
15446
|
var BotHandoffGuard = class {
|
|
14944
15447
|
constructor(path) {
|
|
@@ -14979,7 +15482,7 @@ var BotHandoffGuard = class {
|
|
|
14979
15482
|
}
|
|
14980
15483
|
async read() {
|
|
14981
15484
|
try {
|
|
14982
|
-
const parsed = JSON.parse(await
|
|
15485
|
+
const parsed = JSON.parse(await readFile22(this.path, "utf8"));
|
|
14983
15486
|
return { schemaVersion: 1, chats: parsed.chats ?? {} };
|
|
14984
15487
|
} catch (error) {
|
|
14985
15488
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -15005,7 +15508,7 @@ init_dsh_runtime();
|
|
|
15005
15508
|
import { homedir as homedir14 } from "os";
|
|
15006
15509
|
|
|
15007
15510
|
// src/bot/job-ledger.ts
|
|
15008
|
-
import { readFile as
|
|
15511
|
+
import { readFile as readFile23, rename as rename3 } from "fs/promises";
|
|
15009
15512
|
var EMPTY_COUNTS = {
|
|
15010
15513
|
queued: 0,
|
|
15011
15514
|
running: 0,
|
|
@@ -15028,7 +15531,7 @@ var JobLedger = class {
|
|
|
15028
15531
|
if (this.path === ":memory:") return;
|
|
15029
15532
|
let raw;
|
|
15030
15533
|
try {
|
|
15031
|
-
raw = await
|
|
15534
|
+
raw = await readFile23(this.path, "utf8");
|
|
15032
15535
|
} catch (error) {
|
|
15033
15536
|
if (error.code === "ENOENT") return;
|
|
15034
15537
|
throw error;
|
|
@@ -15604,21 +16107,91 @@ function projectDiagnosticLogs(lines) {
|
|
|
15604
16107
|
}
|
|
15605
16108
|
|
|
15606
16109
|
// src/service/manager.ts
|
|
15607
|
-
import { mkdir as mkdir19, readFile as
|
|
15608
|
-
import { dirname as
|
|
16110
|
+
import { mkdir as mkdir19, readFile as readFile28, rename as rename4, rm as rm9, stat as stat8 } from "fs/promises";
|
|
16111
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
15609
16112
|
import { homedir as homedir13 } from "os";
|
|
15610
16113
|
init_dsh_runtime();
|
|
15611
16114
|
|
|
15612
16115
|
// src/guardian/install.ts
|
|
15613
|
-
|
|
15614
|
-
init_dsh_runtime();
|
|
15615
|
-
import { mkdir as mkdir14, readFile as readFile24, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
16116
|
+
import { mkdir as mkdir14, readFile as readFile25, rm as rm5, writeFile as writeFile6 } from "fs/promises";
|
|
15616
16117
|
import { existsSync as existsSync5 } from "fs";
|
|
15617
16118
|
import { homedir as homedir9 } from "os";
|
|
15618
|
-
import {
|
|
16119
|
+
import { dirname as dirname14, join as join16 } from "path";
|
|
16120
|
+
|
|
16121
|
+
// src/platform/path.ts
|
|
16122
|
+
import { delimiter, dirname as dirname12 } from "path";
|
|
16123
|
+
function sanitizeServicePath(nodeBin, inheritedPath = process.env.PATH) {
|
|
16124
|
+
const entries = [dirname12(nodeBin), ...inheritedPath?.split(delimiter) ?? []];
|
|
16125
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16126
|
+
return entries.filter((entry) => {
|
|
16127
|
+
if (!entry) return false;
|
|
16128
|
+
const normalized = entry.replaceAll("\\", "/");
|
|
16129
|
+
if (normalized.includes("/node_modules/.bin") || /\/_npx(?:\/|$)/.test(normalized) || /\/guardian\/update-worker\/npm-cache(?:\/|$)/.test(normalized)) {
|
|
16130
|
+
return false;
|
|
16131
|
+
}
|
|
16132
|
+
if (seen.has(entry)) return false;
|
|
16133
|
+
seen.add(entry);
|
|
16134
|
+
return true;
|
|
16135
|
+
}).join(delimiter);
|
|
16136
|
+
}
|
|
16137
|
+
|
|
16138
|
+
// src/guardian/install.ts
|
|
16139
|
+
init_own_package();
|
|
16140
|
+
init_dsh_runtime();
|
|
15619
16141
|
|
|
15620
16142
|
// src/guardian/process.ts
|
|
15621
16143
|
import { spawn as spawn5 } from "child_process";
|
|
16144
|
+
function commandTokens(cmdline) {
|
|
16145
|
+
const tokens = [];
|
|
16146
|
+
let token = "";
|
|
16147
|
+
let quote;
|
|
16148
|
+
const input = cmdline.trim();
|
|
16149
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
16150
|
+
const character = input[index] ?? "";
|
|
16151
|
+
const next = input[index + 1];
|
|
16152
|
+
if (character === "\\" && quote === void 0 && next !== void 0 && /[\s\\"']/u.test(next)) {
|
|
16153
|
+
token += next;
|
|
16154
|
+
index += 1;
|
|
16155
|
+
} else if (character === "\\" && quote === '"' && next === '"') {
|
|
16156
|
+
token += next;
|
|
16157
|
+
index += 1;
|
|
16158
|
+
} else if (quote !== void 0) {
|
|
16159
|
+
if (character === quote) quote = void 0;
|
|
16160
|
+
else token += character;
|
|
16161
|
+
} else if (character === '"' || character === "'") {
|
|
16162
|
+
quote = character;
|
|
16163
|
+
} else if (/\s/u.test(character)) {
|
|
16164
|
+
if (token !== "") {
|
|
16165
|
+
tokens.push(token);
|
|
16166
|
+
token = "";
|
|
16167
|
+
}
|
|
16168
|
+
} else {
|
|
16169
|
+
token += character;
|
|
16170
|
+
}
|
|
16171
|
+
}
|
|
16172
|
+
if (token !== "") tokens.push(token);
|
|
16173
|
+
return tokens;
|
|
16174
|
+
}
|
|
16175
|
+
function isGuardianCliEntry(token) {
|
|
16176
|
+
const normalized = token.replace(/\\/gu, "/").toLowerCase();
|
|
16177
|
+
return normalized.endsWith("/dist/cli.js") && (normalized.includes("/dsh-lark-bot/") || normalized.includes("/dsh-feishu-bot/"));
|
|
16178
|
+
}
|
|
16179
|
+
function hasOnlyGuardianRunOptions(tokens) {
|
|
16180
|
+
for (let index = 0; index < tokens.length; index += 2) {
|
|
16181
|
+
if (tokens[index] !== "--dsh-profile" && tokens[index] !== "--bridge-profile") return false;
|
|
16182
|
+
if (tokens[index + 1] === void 0 || tokens[index + 1]?.startsWith("--")) return false;
|
|
16183
|
+
}
|
|
16184
|
+
return true;
|
|
16185
|
+
}
|
|
16186
|
+
function matchGuardianProcess(cmdline) {
|
|
16187
|
+
const tokens = commandTokens(cmdline);
|
|
16188
|
+
const cliIndex = tokens.findIndex(isGuardianCliEntry);
|
|
16189
|
+
if (cliIndex < 1) return false;
|
|
16190
|
+
const executable = tokens[cliIndex - 1]?.replace(/\\/gu, "/").split("/").pop()?.toLowerCase();
|
|
16191
|
+
if (executable !== "node" && executable !== "node.exe") return false;
|
|
16192
|
+
if (tokens[cliIndex + 1] !== "guardian" || tokens[cliIndex + 2] !== "run") return false;
|
|
16193
|
+
return hasOnlyGuardianRunOptions(tokens.slice(cliIndex + 3));
|
|
16194
|
+
}
|
|
15622
16195
|
function hasProfileFlag(cmdline, dshProfile) {
|
|
15623
16196
|
const pattern = new RegExp(
|
|
15624
16197
|
`(?:^|\\s)--profile(?:\\s+|=)${escapeRegExp(dshProfile)}(?:\\s|$)`
|
|
@@ -15645,12 +16218,18 @@ async function listProcesses(platform = process.platform, run = captureOutput) {
|
|
|
15645
16218
|
return listProcessesPosix(run);
|
|
15646
16219
|
}
|
|
15647
16220
|
async function listProcessesPosix(run) {
|
|
15648
|
-
const { stdout } = await run("ps", ["-axo", "pid=,args="], 1e4);
|
|
16221
|
+
const { stdout } = await run("ps", ["-axo", "pid=,ppid=,args="], 1e4);
|
|
15649
16222
|
const result = [];
|
|
15650
16223
|
for (const line of stdout.split("\n")) {
|
|
15651
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
15652
|
-
if (match)
|
|
15653
|
-
|
|
16224
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
16225
|
+
if (!match) continue;
|
|
16226
|
+
const pid = Number(match[1]);
|
|
16227
|
+
const ppid = Number(match[2]);
|
|
16228
|
+
if (!Number.isInteger(pid) || pid <= 0) continue;
|
|
16229
|
+
if (Number.isInteger(ppid) && ppid > 0) {
|
|
16230
|
+
result.push({ pid, ppid, cmdline: match[3] ?? "" });
|
|
16231
|
+
} else {
|
|
16232
|
+
result.push({ pid, cmdline: match[3] ?? "" });
|
|
15654
16233
|
}
|
|
15655
16234
|
}
|
|
15656
16235
|
return result;
|
|
@@ -15662,7 +16241,7 @@ async function listProcessesWindows(run) {
|
|
|
15662
16241
|
"-NoProfile",
|
|
15663
16242
|
"-NonInteractive",
|
|
15664
16243
|
"-Command",
|
|
15665
|
-
"Get-CimInstance Win32_Process | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
16244
|
+
"Get-CimInstance Win32_Process | Select-Object ProcessId, ParentProcessId, CommandLine | ConvertTo-Json -Compress"
|
|
15666
16245
|
],
|
|
15667
16246
|
1e4
|
|
15668
16247
|
);
|
|
@@ -15673,8 +16252,11 @@ async function listProcessesWindows(run) {
|
|
|
15673
16252
|
if (typeof row !== "object" || row === null) return [];
|
|
15674
16253
|
const record = row;
|
|
15675
16254
|
const pid = Number(record.ProcessId);
|
|
16255
|
+
const ppid = Number(record.ParentProcessId);
|
|
15676
16256
|
const cmdline = record.CommandLine;
|
|
15677
|
-
|
|
16257
|
+
if (!Number.isInteger(pid) || pid <= 0 || typeof cmdline !== "string") return [];
|
|
16258
|
+
if (Number.isInteger(ppid) && ppid > 0) return [{ pid, ppid, cmdline }];
|
|
16259
|
+
return [{ pid, cmdline }];
|
|
15678
16260
|
});
|
|
15679
16261
|
} catch {
|
|
15680
16262
|
return [];
|
|
@@ -15722,8 +16304,8 @@ async function findProfileProcess(dshProfile) {
|
|
|
15722
16304
|
}
|
|
15723
16305
|
|
|
15724
16306
|
// src/guardian/state.ts
|
|
15725
|
-
import { readFile as
|
|
15726
|
-
import { dirname as
|
|
16307
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
16308
|
+
import { dirname as dirname13 } from "path";
|
|
15727
16309
|
import { mkdir as mkdir13 } from "fs/promises";
|
|
15728
16310
|
|
|
15729
16311
|
// src/guardian/install.ts
|
|
@@ -15743,7 +16325,7 @@ function resolveGuardianCliEntry(dshProfile, root, env = process.env) {
|
|
|
15743
16325
|
|
|
15744
16326
|
// src/service/command.ts
|
|
15745
16327
|
import { execFile as execFile3 } from "child_process";
|
|
15746
|
-
import { basename as basename4, dirname as
|
|
16328
|
+
import { basename as basename4, dirname as dirname15, join as join17 } from "path";
|
|
15747
16329
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
15748
16330
|
var runCommand = (command, args, options) => new Promise((resolve6) => {
|
|
15749
16331
|
execFile3(
|
|
@@ -15769,21 +16351,21 @@ function resolveCliJsPath(metaUrl = import.meta.url) {
|
|
|
15769
16351
|
const current = fileURLToPath2(metaUrl);
|
|
15770
16352
|
const name2 = basename4(current);
|
|
15771
16353
|
if (name2 === "cli.js" || name2 === "cli.mjs" || name2 === "cli.cjs") return current;
|
|
15772
|
-
return join17(
|
|
16354
|
+
return join17(dirname15(current), "..", "dist", "cli.js");
|
|
15773
16355
|
}
|
|
15774
|
-
function
|
|
16356
|
+
function slugify3(value) {
|
|
15775
16357
|
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15776
16358
|
return slug || "default";
|
|
15777
16359
|
}
|
|
15778
16360
|
function serviceNameFor(profile) {
|
|
15779
|
-
return `dsh-lark-engine-${
|
|
16361
|
+
return `dsh-lark-engine-${slugify3(profile)}`;
|
|
15780
16362
|
}
|
|
15781
16363
|
function launchdLabelFor(profile) {
|
|
15782
|
-
return `io.dsh-lark.engine.${
|
|
16364
|
+
return `io.dsh-lark.engine.${slugify3(profile)}`;
|
|
15783
16365
|
}
|
|
15784
16366
|
|
|
15785
16367
|
// src/service/env-snapshot.ts
|
|
15786
|
-
import { readFile as
|
|
16368
|
+
import { readFile as readFile26 } from "fs/promises";
|
|
15787
16369
|
var EXTRA_KEYS = ["PATH", "HOME", "DEEPSEEK_API_KEY", "DSH_HOME"];
|
|
15788
16370
|
var INTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
15789
16371
|
"DSH_LARK_NOTIFY_URL",
|
|
@@ -15812,6 +16394,9 @@ function snapshotServiceEnv(source = process.env, extraKeys = [], inherited = {}
|
|
|
15812
16394
|
}
|
|
15813
16395
|
}
|
|
15814
16396
|
}
|
|
16397
|
+
if (typeof env.PATH === "string" && env.PATH.length > 0) {
|
|
16398
|
+
env.PATH = sanitizeServicePath(process.execPath, env.PATH);
|
|
16399
|
+
}
|
|
15815
16400
|
return env;
|
|
15816
16401
|
}
|
|
15817
16402
|
function formatEnvFile(env) {
|
|
@@ -15852,7 +16437,7 @@ async function writeServiceEnv(file, env) {
|
|
|
15852
16437
|
}
|
|
15853
16438
|
async function readServiceEnv(file) {
|
|
15854
16439
|
try {
|
|
15855
|
-
return parseEnvFile(await
|
|
16440
|
+
return parseEnvFile(await readFile26(file, "utf8"));
|
|
15856
16441
|
} catch (error) {
|
|
15857
16442
|
if (error.code === "ENOENT") return {};
|
|
15858
16443
|
throw error;
|
|
@@ -16164,7 +16749,7 @@ var LaunchdServiceController = class {
|
|
|
16164
16749
|
|
|
16165
16750
|
// src/service/portable.ts
|
|
16166
16751
|
import { spawn as spawn6 } from "child_process";
|
|
16167
|
-
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";
|
|
16168
16753
|
import { homedir as homedir12 } from "os";
|
|
16169
16754
|
import { join as join20 } from "path";
|
|
16170
16755
|
function defaultAutostartDir() {
|
|
@@ -16199,8 +16784,8 @@ function buildDesktopEntry(spec) {
|
|
|
16199
16784
|
async function readLinuxProcessIdentity(pid) {
|
|
16200
16785
|
try {
|
|
16201
16786
|
const [statText, cmdlineText] = await Promise.all([
|
|
16202
|
-
|
|
16203
|
-
|
|
16787
|
+
readFile27(`/proc/${pid}/stat`, "utf8"),
|
|
16788
|
+
readFile27(`/proc/${pid}/cmdline`, "utf8")
|
|
16204
16789
|
]);
|
|
16205
16790
|
const close = statText.lastIndexOf(") ");
|
|
16206
16791
|
if (close < 0) return void 0;
|
|
@@ -16231,7 +16816,7 @@ function isProcessAlive(pid) {
|
|
|
16231
16816
|
}
|
|
16232
16817
|
async function readSupervisorStatus(file) {
|
|
16233
16818
|
try {
|
|
16234
|
-
return JSON.parse(await
|
|
16819
|
+
return JSON.parse(await readFile27(file, "utf8"));
|
|
16235
16820
|
} catch {
|
|
16236
16821
|
return void 0;
|
|
16237
16822
|
}
|
|
@@ -16397,7 +16982,7 @@ var PortableServiceController = class {
|
|
|
16397
16982
|
|
|
16398
16983
|
// src/service/windows-task.ts
|
|
16399
16984
|
import { mkdir as mkdir18, writeFile as writeFile8 } from "fs/promises";
|
|
16400
|
-
import { dirname as
|
|
16985
|
+
import { dirname as dirname16, join as join21 } from "path";
|
|
16401
16986
|
function psQuote(value) {
|
|
16402
16987
|
return `'${value.replace(/'/g, "''")}'`;
|
|
16403
16988
|
}
|
|
@@ -16408,7 +16993,7 @@ function buildScheduledTaskScript(action, spec) {
|
|
|
16408
16993
|
`$arguments = ${psQuote(spec.commandArgs.map((arg) => `"${arg.replace(/"/g, '\\"')}"`).join(" "))}`,
|
|
16409
16994
|
`$log = ${psQuote(spec.logFile)}`,
|
|
16410
16995
|
`$profile = ${psQuote(spec.profile)}`,
|
|
16411
|
-
`$workDir = ${psQuote(
|
|
16996
|
+
`$workDir = ${psQuote(dirname16(spec.commandPath))}`
|
|
16412
16997
|
].join("\n");
|
|
16413
16998
|
const install = [
|
|
16414
16999
|
`$inner = '"' + $command + '" ' + $arguments + ' >> "' + $log + '" 2>&1'`,
|
|
@@ -16606,7 +17191,7 @@ var ServiceManager = class {
|
|
|
16606
17191
|
async readIntent() {
|
|
16607
17192
|
try {
|
|
16608
17193
|
const parsed = JSON.parse(
|
|
16609
|
-
await
|
|
17194
|
+
await readFile28(this.paths.serviceIntentFile(this.profile), "utf8")
|
|
16610
17195
|
);
|
|
16611
17196
|
if (parsed.schemaVersion !== 1 || !["running", "stopped"].includes(parsed.desiredState ?? "")) {
|
|
16612
17197
|
return void 0;
|
|
@@ -16626,14 +17211,14 @@ var ServiceManager = class {
|
|
|
16626
17211
|
}
|
|
16627
17212
|
async withLifecycleLock(operation) {
|
|
16628
17213
|
const lockDir = this.paths.serviceLockDir(this.profile);
|
|
16629
|
-
await mkdir19(
|
|
17214
|
+
await mkdir19(dirname17(lockDir), { recursive: true });
|
|
16630
17215
|
try {
|
|
16631
17216
|
await mkdir19(lockDir);
|
|
16632
17217
|
} catch (error) {
|
|
16633
17218
|
if (error.code !== "EEXIST") throw error;
|
|
16634
17219
|
let ownerPid;
|
|
16635
17220
|
try {
|
|
16636
|
-
const owner = JSON.parse(await
|
|
17221
|
+
const owner = JSON.parse(await readFile28(join22(lockDir, "owner.json"), "utf8"));
|
|
16637
17222
|
if (typeof owner.pid === "number") ownerPid = owner.pid;
|
|
16638
17223
|
} catch {
|
|
16639
17224
|
}
|
|
@@ -16666,11 +17251,39 @@ var ServiceManager = class {
|
|
|
16666
17251
|
if (serviceStatus.state === "running") return;
|
|
16667
17252
|
const process2 = await this.findProcess(this.profile);
|
|
16668
17253
|
if (process2) {
|
|
17254
|
+
if (await this.isGuardianSpawned(process2)) {
|
|
17255
|
+
await this.stopGuardianSpawnedProcess(process2.pid);
|
|
17256
|
+
return;
|
|
17257
|
+
}
|
|
16669
17258
|
throw new Error(
|
|
16670
17259
|
`\u68C0\u6D4B\u5230\u672A\u53D7\u7BA1\u7684 dsh --profile ${this.profile}\uFF08pid ${process2.pid}\uFF09\u3002\u8BF7\u5148\u5728\u539F\u7EC8\u7AEF\u505C\u6B62\u5B83\uFF0C\u518D\u91CD\u8BD5\u3002`
|
|
16671
17260
|
);
|
|
16672
17261
|
}
|
|
16673
17262
|
}
|
|
17263
|
+
/** True when the profile process was spawned by the resident guardian. */
|
|
17264
|
+
async isGuardianSpawned(process2) {
|
|
17265
|
+
const ppid = process2.ppid;
|
|
17266
|
+
if (!ppid) return false;
|
|
17267
|
+
const parent = (await listProcesses()).find((entry) => entry.pid === ppid);
|
|
17268
|
+
return parent !== void 0 && matchGuardianProcess(parent.cmdline);
|
|
17269
|
+
}
|
|
17270
|
+
/** Best-effort stop of a guardian-spawned profile process (grace, then kill). */
|
|
17271
|
+
async stopGuardianSpawnedProcess(pid) {
|
|
17272
|
+
try {
|
|
17273
|
+
process.kill(pid, "SIGTERM");
|
|
17274
|
+
} catch {
|
|
17275
|
+
return;
|
|
17276
|
+
}
|
|
17277
|
+
const deadline = Date.now() + 5e3;
|
|
17278
|
+
while (Date.now() < deadline && isPidAlive2(pid)) {
|
|
17279
|
+
await new Promise((resolve6) => setTimeout(resolve6, 100));
|
|
17280
|
+
}
|
|
17281
|
+
if (!isPidAlive2(pid)) return;
|
|
17282
|
+
try {
|
|
17283
|
+
process.kill(pid, "SIGKILL");
|
|
17284
|
+
} catch {
|
|
17285
|
+
}
|
|
17286
|
+
}
|
|
16674
17287
|
async buildSpec(requireProfile = false) {
|
|
16675
17288
|
const previous = await this.readMetadata();
|
|
16676
17289
|
const dshBin = this.dshBinOverride ?? discoverDshBin(this.home, this.sourceEnv) ?? previous?.dshBin;
|
|
@@ -16759,7 +17372,7 @@ var ServiceManager = class {
|
|
|
16759
17372
|
}
|
|
16760
17373
|
async readMetadata() {
|
|
16761
17374
|
try {
|
|
16762
|
-
const parsed = JSON.parse(await
|
|
17375
|
+
const parsed = JSON.parse(await readFile28(this.metadataFile(), "utf8"));
|
|
16763
17376
|
if (parsed.schemaVersion !== 2 || typeof parsed.profile !== "string") return void 0;
|
|
16764
17377
|
return parsed;
|
|
16765
17378
|
} catch {
|
|
@@ -16767,8 +17380,8 @@ var ServiceManager = class {
|
|
|
16767
17380
|
}
|
|
16768
17381
|
}
|
|
16769
17382
|
async prepareFiles(spec, platform) {
|
|
16770
|
-
await mkdir19(
|
|
16771
|
-
await mkdir19(
|
|
17383
|
+
await mkdir19(dirname17(spec.logFile), { recursive: true });
|
|
17384
|
+
await mkdir19(dirname17(spec.envFile), { recursive: true });
|
|
16772
17385
|
await writeServiceEnv(spec.envFile, spec.env);
|
|
16773
17386
|
if (platform === "win32-task") {
|
|
16774
17387
|
try {
|
|
@@ -16861,7 +17474,7 @@ var ServiceManager = class {
|
|
|
16861
17474
|
async logs(lines = 100) {
|
|
16862
17475
|
const path = this.paths.serviceLogFile(this.profile);
|
|
16863
17476
|
try {
|
|
16864
|
-
const content2 = await
|
|
17477
|
+
const content2 = await readFile28(path, "utf8");
|
|
16865
17478
|
return { path, text: content2.split(/\r?\n/).slice(-Math.max(1, lines)).join("\n") };
|
|
16866
17479
|
} catch (error) {
|
|
16867
17480
|
if (error.code === "ENOENT") return { path, text: "" };
|
|
@@ -17036,8 +17649,8 @@ init_own_package();
|
|
|
17036
17649
|
// src/guardian/update-handoff.ts
|
|
17037
17650
|
import { createHash as createHash3, randomUUID as randomUUID13 } from "crypto";
|
|
17038
17651
|
import { spawn as spawn7 } from "child_process";
|
|
17039
|
-
import { chmod, mkdir as mkdir20, readFile as
|
|
17040
|
-
import { dirname as
|
|
17652
|
+
import { chmod, mkdir as mkdir20, readFile as readFile29, rm as rm10 } from "fs/promises";
|
|
17653
|
+
import { dirname as dirname18, join as join23 } from "path";
|
|
17041
17654
|
init_own_package();
|
|
17042
17655
|
function guardianUpdateFailureHint(code) {
|
|
17043
17656
|
switch (code) {
|
|
@@ -17065,7 +17678,7 @@ function guardianUpdateFailureHint(code) {
|
|
|
17065
17678
|
}
|
|
17066
17679
|
async function loadState(file) {
|
|
17067
17680
|
try {
|
|
17068
|
-
const parsed = JSON.parse(await
|
|
17681
|
+
const parsed = JSON.parse(await readFile29(file, "utf8"));
|
|
17069
17682
|
return parsed.schemaVersion === 1 && typeof parsed.id === "string" ? parsed : void 0;
|
|
17070
17683
|
} catch (error) {
|
|
17071
17684
|
if (error.code === "ENOENT") return void 0;
|
|
@@ -17073,7 +17686,7 @@ async function loadState(file) {
|
|
|
17073
17686
|
}
|
|
17074
17687
|
}
|
|
17075
17688
|
async function saveState(file, state) {
|
|
17076
|
-
await mkdir20(
|
|
17689
|
+
await mkdir20(dirname18(file), { recursive: true });
|
|
17077
17690
|
await writeFileAtomic(file, `${JSON.stringify(state, null, 2)}
|
|
17078
17691
|
`, { mode: 384 });
|
|
17079
17692
|
}
|
|
@@ -17350,6 +17963,8 @@ async function startBridgeEngine(options) {
|
|
|
17350
17963
|
const isolationStore = new IsolationStore(paths.profilePath(profileName, "isolation.json"));
|
|
17351
17964
|
const permissionPolicies = new PermissionPolicyStore(paths.permissionPoliciesFile(profileName));
|
|
17352
17965
|
const notificationPreferences = new NotificationPreferenceStore(paths.notificationPreferencesFile(profileName));
|
|
17966
|
+
const notificationChannels = new NotificationChannelStore(paths.notificationChannelsFile(profileName));
|
|
17967
|
+
const notificationSinks = new OutboundSinkRegistry(notificationChannels);
|
|
17353
17968
|
const replyPolicies = new ReplyPolicyStore(paths.replyPoliciesFile(profileName));
|
|
17354
17969
|
const executionModes = new ExecutionModeStore(paths.executionModesFile(profileName));
|
|
17355
17970
|
const languagePolicies = new LanguagePolicyStore(paths.languagePoliciesFile(profileName));
|
|
@@ -17372,6 +17987,7 @@ async function startBridgeEngine(options) {
|
|
|
17372
17987
|
isolationStore.load(),
|
|
17373
17988
|
permissionPolicies.load(),
|
|
17374
17989
|
notificationPreferences.load(),
|
|
17990
|
+
notificationChannels.load(),
|
|
17375
17991
|
replyPolicies.load(),
|
|
17376
17992
|
executionModes.load(),
|
|
17377
17993
|
languagePolicies.load()
|
|
@@ -17441,7 +18057,8 @@ async function startBridgeEngine(options) {
|
|
|
17441
18057
|
send: async (chatId, markdown2, options2) => {
|
|
17442
18058
|
if (!streaming) throw new Error("bridge channel is not ready");
|
|
17443
18059
|
await streaming.sendMarkdown(chatId, markdown2, options2);
|
|
17444
|
-
}
|
|
18060
|
+
},
|
|
18061
|
+
sinks: notificationSinks
|
|
17445
18062
|
});
|
|
17446
18063
|
const replyDispatcher = new ReplyDispatcher({
|
|
17447
18064
|
policies: replyPolicies,
|
|
@@ -17732,6 +18349,9 @@ async function startBridgeEngine(options) {
|
|
|
17732
18349
|
approvals,
|
|
17733
18350
|
permissionPolicies,
|
|
17734
18351
|
notificationPreferences,
|
|
18352
|
+
notificationChannels,
|
|
18353
|
+
notificationSinks,
|
|
18354
|
+
faultNotifier: (scope, title, detail) => notificationDispatcher.notifyUrgent(scope, title, detail),
|
|
17735
18355
|
...defaultNotificationPreference ? { defaultNotificationPreference: { ...defaultNotificationPreference, events: [...defaultNotificationPreference.events] } } : {},
|
|
17736
18356
|
replyPolicies,
|
|
17737
18357
|
executionModes,
|
|
@@ -17994,6 +18614,7 @@ function notificationPreferenceFor(value) {
|
|
|
17994
18614
|
return {
|
|
17995
18615
|
events: value === "all" ? ["completed", "failed", "approval"] : ["completed", "failed"],
|
|
17996
18616
|
mentionUserIds: [],
|
|
18617
|
+
sinks: [],
|
|
17997
18618
|
approvalReminderMs: 10 * 6e4
|
|
17998
18619
|
};
|
|
17999
18620
|
}
|