@modelzen/feishu-codex-bridge 0.6.8 → 0.6.9
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 +14 -0
- package/dist/cli.js +736 -96
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -155,6 +155,35 @@ function getMaxConcurrentRuns(cfg) {
|
|
|
155
155
|
function getPendingPolicy(cfg) {
|
|
156
156
|
return cfg.preferences?.pendingPolicy === "queue" ? "queue" : "steer";
|
|
157
157
|
}
|
|
158
|
+
function getCompletionReminderConfig(cfg) {
|
|
159
|
+
const raw = cfg.preferences?.completionReminder;
|
|
160
|
+
const mode = raw?.mode === "manual" || raw?.mode === "long" || raw?.mode === "always" ? raw.mode : "failures";
|
|
161
|
+
const minutes = raw?.longTaskMinutes;
|
|
162
|
+
const longTaskMinutes = typeof minutes !== "number" || !Number.isFinite(minutes) || minutes <= 0 ? COMPLETION_REMINDER_LONG_TASK_DEFAULT_MINUTES : Math.min(
|
|
163
|
+
COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES,
|
|
164
|
+
Math.max(COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES, Math.floor(minutes))
|
|
165
|
+
);
|
|
166
|
+
return { mode, longTaskMinutes };
|
|
167
|
+
}
|
|
168
|
+
function shouldShowCompletionReminderButton(cfg) {
|
|
169
|
+
return getCompletionReminderConfig(cfg).mode === "manual";
|
|
170
|
+
}
|
|
171
|
+
function shouldSendCompletionReminder(cfg, decision) {
|
|
172
|
+
if (decision.outcome === "interrupted" || decision.outcome === "cancelled") return false;
|
|
173
|
+
const reminder = getCompletionReminderConfig(cfg);
|
|
174
|
+
switch (reminder.mode) {
|
|
175
|
+
case "manual":
|
|
176
|
+
return decision.manuallyRequested === true;
|
|
177
|
+
case "long": {
|
|
178
|
+
const elapsedMs = Number.isFinite(decision.elapsedMs) ? Math.max(0, decision.elapsedMs) : 0;
|
|
179
|
+
return elapsedMs >= reminder.longTaskMinutes * 6e4;
|
|
180
|
+
}
|
|
181
|
+
case "always":
|
|
182
|
+
return true;
|
|
183
|
+
case "failures":
|
|
184
|
+
return decision.outcome === "error" || decision.outcome === "idle_timeout";
|
|
185
|
+
}
|
|
186
|
+
}
|
|
158
187
|
function getRunIdleTimeoutMs(cfg) {
|
|
159
188
|
const raw = cfg.preferences?.runIdleTimeoutSeconds;
|
|
160
189
|
if (raw === 0) return void 0;
|
|
@@ -233,10 +262,13 @@ function canEnableCliBridge(cfg) {
|
|
|
233
262
|
function getCommentsConfig(cfg) {
|
|
234
263
|
return cfg.preferences?.comments ?? {};
|
|
235
264
|
}
|
|
236
|
-
var RUN_IDLE_TIMEOUT_MIN_SEC, RUN_IDLE_TIMEOUT_MAX_SEC;
|
|
265
|
+
var COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES, COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES, COMPLETION_REMINDER_LONG_TASK_DEFAULT_MINUTES, RUN_IDLE_TIMEOUT_MIN_SEC, RUN_IDLE_TIMEOUT_MAX_SEC;
|
|
237
266
|
var init_schema = __esm({
|
|
238
267
|
"src/config/schema.ts"() {
|
|
239
268
|
"use strict";
|
|
269
|
+
COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES = 1;
|
|
270
|
+
COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES = 1440;
|
|
271
|
+
COMPLETION_REMINDER_LONG_TASK_DEFAULT_MINUTES = 3;
|
|
240
272
|
RUN_IDLE_TIMEOUT_MIN_SEC = 10;
|
|
241
273
|
RUN_IDLE_TIMEOUT_MAX_SEC = 3600;
|
|
242
274
|
}
|
|
@@ -2550,6 +2582,7 @@ async function pollEventSubscription(appId, appSecret, tenant, opts = {}) {
|
|
|
2550
2582
|
|
|
2551
2583
|
// src/agent/types.ts
|
|
2552
2584
|
var DEFAULT_BACKEND_ID = "codex-appserver";
|
|
2585
|
+
var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
|
|
2553
2586
|
function isGoalTerminal(status) {
|
|
2554
2587
|
return status === "complete" || status === "budgetLimited" || status === "usageLimited" || status === "blocked";
|
|
2555
2588
|
}
|
|
@@ -4159,6 +4192,7 @@ var PushablePrompt = class {
|
|
|
4159
4192
|
function toSdkEffort(e) {
|
|
4160
4193
|
if (!e) return void 0;
|
|
4161
4194
|
if (e === "none" || e === "minimal") return "low";
|
|
4195
|
+
if (e === "ultra") return "max";
|
|
4162
4196
|
return e;
|
|
4163
4197
|
}
|
|
4164
4198
|
var MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
@@ -5532,8 +5566,13 @@ var EFFORT_LABEL = {
|
|
|
5532
5566
|
low: "\u4F4E",
|
|
5533
5567
|
medium: "\u4E2D",
|
|
5534
5568
|
high: "\u9AD8",
|
|
5535
|
-
xhigh: "\u6781\u9AD8"
|
|
5569
|
+
xhigh: "\u6781\u9AD8",
|
|
5570
|
+
max: "\u6700\u9AD8",
|
|
5571
|
+
ultra: "\u8D85\u5F3A"
|
|
5536
5572
|
};
|
|
5573
|
+
function reasoningEffortLabel(effort) {
|
|
5574
|
+
return EFFORT_LABEL[effort] ?? (effort || "\u672A\u77E5");
|
|
5575
|
+
}
|
|
5537
5576
|
function buildModelCard(state) {
|
|
5538
5577
|
const visible = state.models.filter((m) => !m.hidden);
|
|
5539
5578
|
const cur = state.models.find((m) => m.id === state.model);
|
|
@@ -5561,7 +5600,7 @@ function buildModelCard(state) {
|
|
|
5561
5600
|
actionId: MC.effort,
|
|
5562
5601
|
placeholder: "effort",
|
|
5563
5602
|
initial: state.effort,
|
|
5564
|
-
options: efforts.map((e) => ({ label: `effort\uFF1A${
|
|
5603
|
+
options: efforts.map((e) => ({ label: `effort\uFF1A${reasoningEffortLabel(e)}`, value: e }))
|
|
5565
5604
|
})
|
|
5566
5605
|
);
|
|
5567
5606
|
}
|
|
@@ -5779,6 +5818,10 @@ var DM = {
|
|
|
5779
5818
|
// 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
|
|
5780
5819
|
watchdogCustom: "dm.set.watchdog.custom",
|
|
5781
5820
|
watchdogCustomSubmit: "dm.set.watchdog.customSubmit",
|
|
5821
|
+
// 普通群任务结束提醒(与 ☕ CLI Stop 通知无关):四档策略 + 长任务阈值子卡
|
|
5822
|
+
setCompletionReminder: "dm.set.completionReminder",
|
|
5823
|
+
completionReminderCustom: "dm.set.completionReminder.custom",
|
|
5824
|
+
completionReminderCustomSubmit: "dm.set.completionReminder.customSubmit",
|
|
5782
5825
|
setPending: "dm.set.pending",
|
|
5783
5826
|
setConcurrency: "dm.set.concurrency",
|
|
5784
5827
|
// 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
|
|
@@ -6314,6 +6357,7 @@ function settingItem(name, desc, actionId, current, opts) {
|
|
|
6314
6357
|
}
|
|
6315
6358
|
function buildSettingsCard(cfg) {
|
|
6316
6359
|
const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
|
|
6360
|
+
const completionReminder = getCompletionReminderConfig(cfg);
|
|
6317
6361
|
return card(
|
|
6318
6362
|
[
|
|
6319
6363
|
settingSection("\u{1F4E4} \u8F93\u51FA\u5C55\u793A"),
|
|
@@ -6348,6 +6392,23 @@ function buildSettingsCard(cfg) {
|
|
|
6348
6392
|
),
|
|
6349
6393
|
button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
|
|
6350
6394
|
]),
|
|
6395
|
+
...settingItem(
|
|
6396
|
+
"\u{1F514} \u4EFB\u52A1\u7ED3\u675F\u63D0\u9192",
|
|
6397
|
+
"\u7ED3\u675F\u540E\u989D\u5916\u56DE\u590D\u4E00\u6761\u6D88\u606F\u5E76 @ \u53D1\u8D77\u4EBA\u3002\u300C\u4EC5\u624B\u52A8\u300D\u4F1A\u5728\u8FD0\u884C\u5361\u663E\u793A\u63D0\u9192\u6309\u94AE\uFF1B\u5176\u4ED6\u6863\u4F4D\u81EA\u52A8\u5224\u65AD\uFF0C\u4E0D\u663E\u793A\u6309\u94AE\u3002",
|
|
6398
|
+
DM.setCompletionReminder,
|
|
6399
|
+
completionReminder.mode,
|
|
6400
|
+
[
|
|
6401
|
+
{ label: "\u4EC5\u624B\u52A8", value: "manual" },
|
|
6402
|
+
{ label: "\u957F\u4EFB\u52A1", value: "long" },
|
|
6403
|
+
{ label: "\u5931\u8D25\u6216\u8D85\u65F6", value: "failures" },
|
|
6404
|
+
{ label: "\u6BCF\u6B21\u7ED3\u675F", value: "always" }
|
|
6405
|
+
]
|
|
6406
|
+
),
|
|
6407
|
+
...completionReminder.mode === "long" ? [
|
|
6408
|
+
md(`**\u23F3 \u957F\u4EFB\u52A1\u9608\u503C** \xB7 \u5F53\u524D **${completionReminder.longTaskMinutes} \u5206\u949F**`),
|
|
6409
|
+
note("\u4EFB\u52A1\u8017\u65F6\u8FBE\u5230\u8FD9\u4E2A\u9608\u503C\u540E\uFF0C\u7ED3\u675F\u65F6\u624D\u4F1A\u63D0\u9192\u3002"),
|
|
6410
|
+
actions([button("\u4FEE\u6539\u9608\u503C\u2026", { a: DM.completionReminderCustom })])
|
|
6411
|
+
] : [],
|
|
6351
6412
|
...settingItem(
|
|
6352
6413
|
"\u{1F4E5} \u8FD0\u884C\u4E2D\u6765\u65B0\u6D88\u606F",
|
|
6353
6414
|
"\u6B63\u5728\u8DD1\u65F6\u4F60\u53C8\u53D1\u6D88\u606F\uFF1A\u5F15\u5BFC\uFF1D\u63D2\u8FDB\u5F53\u524D\u8F6E\u7EA0\u504F\uFF1B\u6392\u961F\uFF1D\u7B49\u8FD9\u8F6E\u8DD1\u5B8C\u518D\u5904\u7406\u3002",
|
|
@@ -6445,7 +6506,7 @@ function buildCommentSettingsCard(cfg, backendOptions, models, notice) {
|
|
|
6445
6506
|
selectMenu({
|
|
6446
6507
|
name: "effort",
|
|
6447
6508
|
placeholder: "\u9009\u62E9\u63A8\u7406\u5F3A\u5EA6",
|
|
6448
|
-
options: unionEfforts.map((e) => ({ label:
|
|
6509
|
+
options: unionEfforts.map((e) => ({ label: reasoningEffortLabel(e), value: e })),
|
|
6449
6510
|
initial: curEffort
|
|
6450
6511
|
})
|
|
6451
6512
|
);
|
|
@@ -6550,6 +6611,25 @@ function buildWatchdogCustomCard(cfg) {
|
|
|
6550
6611
|
{ header: { title: "\u23F1 \u81EA\u5B9A\u4E49\u8D85\u65F6", template: "blue" } }
|
|
6551
6612
|
);
|
|
6552
6613
|
}
|
|
6614
|
+
function buildCompletionReminderCustomCard(cfg) {
|
|
6615
|
+
const cur = getCompletionReminderConfig(cfg).longTaskMinutes;
|
|
6616
|
+
return card(
|
|
6617
|
+
[
|
|
6618
|
+
md("**\u81EA\u5B9A\u4E49\u957F\u4EFB\u52A1\u9608\u503C**"),
|
|
6619
|
+
note(
|
|
6620
|
+
`\u4EFB\u52A1\u8017\u65F6\u8FBE\u5230\u8FD9\u4E2A\u65F6\u957F\uFF0C\u7ED3\u675F\u65F6\u989D\u5916\u56DE\u590D\u6D88\u606F\u5E76 @ \u53D1\u8D77\u4EBA\u3002\u8303\u56F4 ${COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES}\u2013${COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES} \u5206\u949F\u3002`
|
|
6621
|
+
),
|
|
6622
|
+
form("completion_reminder_custom", [
|
|
6623
|
+
input({ name: "minutes", label: "\u65F6\u957F\uFF08\u5206\u949F\uFF09", placeholder: "\u4F8B\u5982 10", value: String(cur), required: true }),
|
|
6624
|
+
actions([
|
|
6625
|
+
submitButton("\u2705 \u4FDD\u5B58", { a: DM.completionReminderCustomSubmit }, "primary", "submit_completion_reminder")
|
|
6626
|
+
])
|
|
6627
|
+
]),
|
|
6628
|
+
actions([button("\u2B05\uFE0F \u8FD4\u56DE\u8BBE\u7F6E", { a: DM.settings })])
|
|
6629
|
+
],
|
|
6630
|
+
{ header: { title: "\u{1F514} \u957F\u4EFB\u52A1\u9608\u503C", template: "blue" } }
|
|
6631
|
+
);
|
|
6632
|
+
}
|
|
6553
6633
|
function buildGroupSettingsCard(project) {
|
|
6554
6634
|
const kind = project.kind ?? "multi";
|
|
6555
6635
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
@@ -6688,10 +6768,10 @@ function buildPermissionCard(p) {
|
|
|
6688
6768
|
{ header: { title: "\u{1F510} \u6743\u9650", template: "blue" } }
|
|
6689
6769
|
);
|
|
6690
6770
|
}
|
|
6691
|
-
var EFFORT_ORDER =
|
|
6771
|
+
var EFFORT_ORDER = REASONING_EFFORTS;
|
|
6692
6772
|
function modelDefaultSummary(p) {
|
|
6693
6773
|
if (!p.defaultModel) return "\u540E\u7AEF\u9ED8\u8BA4\uFF08\u672A\u8BBE\uFF09";
|
|
6694
|
-
const eff = p.defaultEffort ? ` \xB7 \u5F3A\u5EA6 ${
|
|
6774
|
+
const eff = p.defaultEffort ? ` \xB7 \u5F3A\u5EA6 ${reasoningEffortLabel(p.defaultEffort)}` : "";
|
|
6695
6775
|
return `${p.defaultModel}${eff}`;
|
|
6696
6776
|
}
|
|
6697
6777
|
function buildModelDefaultCard(p, models, ctx, notice) {
|
|
@@ -6742,7 +6822,7 @@ function buildModelDefaultCard(p, models, ctx, notice) {
|
|
|
6742
6822
|
selectMenu({
|
|
6743
6823
|
name: "effort",
|
|
6744
6824
|
placeholder: "\u9009\u62E9\u9ED8\u8BA4\u63A8\u7406\u5F3A\u5EA6",
|
|
6745
|
-
options: unionEfforts.map((e) => ({ label: `\u5F3A\u5EA6\uFF1A${
|
|
6825
|
+
options: unionEfforts.map((e) => ({ label: `\u5F3A\u5EA6\uFF1A${reasoningEffortLabel(e)}`, value: e })),
|
|
6746
6826
|
initial: curEffort
|
|
6747
6827
|
})
|
|
6748
6828
|
);
|
|
@@ -6866,6 +6946,8 @@ function buildAddAllowedCard(projectName, members) {
|
|
|
6866
6946
|
}
|
|
6867
6947
|
|
|
6868
6948
|
// src/admin/ops.ts
|
|
6949
|
+
init_schema();
|
|
6950
|
+
init_store();
|
|
6869
6951
|
var AdminWriteError = class extends Error {
|
|
6870
6952
|
code = "ADMIN_WRITE_REJECTED";
|
|
6871
6953
|
constructor(reason) {
|
|
@@ -6873,7 +6955,26 @@ var AdminWriteError = class extends Error {
|
|
|
6873
6955
|
this.name = "AdminWriteError";
|
|
6874
6956
|
}
|
|
6875
6957
|
};
|
|
6958
|
+
function createAppPreferencesWriter(opts) {
|
|
6959
|
+
let chain = Promise.resolve();
|
|
6960
|
+
const persist = opts.persistConfig ?? saveConfig;
|
|
6961
|
+
return (mutate) => {
|
|
6962
|
+
const run = chain.then(async () => {
|
|
6963
|
+
const preferences = { ...opts.cfg.preferences ?? {} };
|
|
6964
|
+
mutate(preferences);
|
|
6965
|
+
await persist({ ...opts.cfg, preferences });
|
|
6966
|
+
opts.cfg.preferences = preferences;
|
|
6967
|
+
return preferences;
|
|
6968
|
+
});
|
|
6969
|
+
chain = run.then(
|
|
6970
|
+
() => void 0,
|
|
6971
|
+
() => void 0
|
|
6972
|
+
);
|
|
6973
|
+
return run;
|
|
6974
|
+
};
|
|
6975
|
+
}
|
|
6876
6976
|
var TIERS = ["qa", "write", "full"];
|
|
6977
|
+
var COMPLETION_REMINDER_MODES = ["manual", "long", "failures", "always"];
|
|
6877
6978
|
function validateBackendSwitch(opts) {
|
|
6878
6979
|
if (!opts.registered.includes(opts.target)) {
|
|
6879
6980
|
return `\u672A\u77E5\u540E\u7AEF\u300C${opts.target}\u300D\uFF08\u53EF\u7528\uFF1A${opts.registered.join("\u3001")}\uFF09`;
|
|
@@ -6972,9 +7073,30 @@ async function performSetModelDefault(opts) {
|
|
|
6972
7073
|
await updateProject(opts.projectName, { defaultModel: opts.model, defaultEffort: opts.effort });
|
|
6973
7074
|
return { ok: true, project: await freshOr(opts.projectName, { ...p, defaultModel: opts.model, defaultEffort: opts.effort }) };
|
|
6974
7075
|
}
|
|
7076
|
+
async function performSetCompletionReminder(opts) {
|
|
7077
|
+
if (!COMPLETION_REMINDER_MODES.includes(opts.mode)) {
|
|
7078
|
+
return { ok: false, reason: `\u672A\u77E5\u5B8C\u6210\u63D0\u9192\u7B56\u7565\u300C${String(opts.mode)}\u300D` };
|
|
7079
|
+
}
|
|
7080
|
+
if (opts.longTaskMinutes !== void 0 && (!Number.isInteger(opts.longTaskMinutes) || opts.longTaskMinutes < COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES || opts.longTaskMinutes > COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES)) {
|
|
7081
|
+
return {
|
|
7082
|
+
ok: false,
|
|
7083
|
+
reason: `\u957F\u4EFB\u52A1\u9608\u503C\u5FC5\u987B\u662F ${COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES}\u2013${COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES} \u4E4B\u95F4\u7684\u6574\u6570\u5206\u949F`
|
|
7084
|
+
};
|
|
7085
|
+
}
|
|
7086
|
+
const writePreferences = opts.writePreferences ?? createAppPreferencesWriter({ cfg: opts.cfg, persistConfig: opts.persistConfig });
|
|
7087
|
+
await writePreferences((preferences) => {
|
|
7088
|
+
const current = getCompletionReminderConfig({ ...opts.cfg, preferences });
|
|
7089
|
+
preferences.completionReminder = {
|
|
7090
|
+
mode: opts.mode,
|
|
7091
|
+
longTaskMinutes: opts.longTaskMinutes ?? current.longTaskMinutes
|
|
7092
|
+
};
|
|
7093
|
+
});
|
|
7094
|
+
return { ok: true, completionReminder: getCompletionReminderConfig(opts.cfg) };
|
|
7095
|
+
}
|
|
6975
7096
|
function createAdminWriteExecutor(deps) {
|
|
7097
|
+
const writePreferences = deps.writePreferences ?? (deps.cfg ? createAppPreferencesWriter({ cfg: deps.cfg, persistConfig: deps.persistConfig }) : void 0);
|
|
6976
7098
|
return async (op) => {
|
|
6977
|
-
const outcome = await runAdminWriteOp(op, deps);
|
|
7099
|
+
const outcome = await runAdminWriteOp(op, { ...deps, writePreferences });
|
|
6978
7100
|
if (!outcome.ok) throw new AdminWriteError(outcome.reason);
|
|
6979
7101
|
};
|
|
6980
7102
|
}
|
|
@@ -6998,12 +7120,20 @@ async function runAdminWriteOp(op, deps) {
|
|
|
6998
7120
|
on: op.on,
|
|
6999
7121
|
evictLiveSessionsForChat: deps.evictLiveSessionsForChat
|
|
7000
7122
|
});
|
|
7123
|
+
case "setCompletionReminder":
|
|
7124
|
+
if (!deps.cfg) return { ok: false, reason: "bot \u8FD0\u884C\u914D\u7F6E\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u5373\u65F6\u66F4\u65B0\u5B8C\u6210\u63D0\u9192" };
|
|
7125
|
+
return performSetCompletionReminder({
|
|
7126
|
+
cfg: deps.cfg,
|
|
7127
|
+
mode: op.mode,
|
|
7128
|
+
longTaskMinutes: op.longTaskMinutes,
|
|
7129
|
+
persistConfig: deps.persistConfig,
|
|
7130
|
+
writePreferences: deps.writePreferences
|
|
7131
|
+
});
|
|
7001
7132
|
}
|
|
7002
7133
|
}
|
|
7003
7134
|
|
|
7004
7135
|
// src/bot/handle-message.ts
|
|
7005
7136
|
init_schema();
|
|
7006
|
-
init_store();
|
|
7007
7137
|
|
|
7008
7138
|
// src/card/dispatcher.ts
|
|
7009
7139
|
init_logger();
|
|
@@ -7618,6 +7748,8 @@ function gaugeEl(state) {
|
|
|
7618
7748
|
}
|
|
7619
7749
|
var RC = {
|
|
7620
7750
|
stop: "run.stop",
|
|
7751
|
+
/** manual completion-reminder mode: notify this turn's requester when it ends. */
|
|
7752
|
+
remind: "run.remind",
|
|
7621
7753
|
/** goal-only: clear the goal but let the in-flight turn finish (no auto-continue). */
|
|
7622
7754
|
endGoal: "goal.end"
|
|
7623
7755
|
};
|
|
@@ -7672,8 +7804,16 @@ function renderRunning(state, rc) {
|
|
|
7672
7804
|
)
|
|
7673
7805
|
);
|
|
7674
7806
|
}
|
|
7675
|
-
} else if (rc.cardKey
|
|
7676
|
-
|
|
7807
|
+
} else if (rc.cardKey) {
|
|
7808
|
+
if (rc.completionReminder === "requested") {
|
|
7809
|
+
elements.push(noteMd("_\u{1F514} \u672C\u8F6E\u7ED3\u675F\u540E\u4F1A\u63D0\u9192\u53D1\u8D77\u4EBA_"));
|
|
7810
|
+
}
|
|
7811
|
+
const controls = [];
|
|
7812
|
+
if (!rc.hideStop) controls.push(button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger"));
|
|
7813
|
+
if (rc.completionReminder === "available") {
|
|
7814
|
+
controls.push(button("\u{1F514} \u5B8C\u6210\u540E\u63D0\u9192\u6211", { a: RC.remind, m: rc.cardKey }, "default"));
|
|
7815
|
+
}
|
|
7816
|
+
if (controls.length > 0) elements.push(actions(controls, CONTROLS_EID));
|
|
7677
7817
|
}
|
|
7678
7818
|
return elements;
|
|
7679
7819
|
}
|
|
@@ -7787,7 +7927,14 @@ function buildQueuedCard(qc) {
|
|
|
7787
7927
|
md(`\u23F3 \u6392\u961F\u4E2D\uFF08\u7B2C **${qc.position ?? 1}** \u4F4D\uFF09`),
|
|
7788
7928
|
noteMd("\u5168\u5C40\u5E76\u53D1\u6C60\u5DF2\u6EE1\uFF08\u6240\u6709\u7FA4/\u8BDD\u9898\u5171\u4EAB\uFF09\uFF0C\u8F6E\u5230\u540E\u81EA\u52A8\u5F00\u59CB\u3002")
|
|
7789
7929
|
];
|
|
7790
|
-
if (qc.
|
|
7930
|
+
if (qc.completionReminder === "requested") els.push(noteMd("_\u{1F514} \u672C\u8F6E\u7ED3\u675F\u540E\u4F1A\u63D0\u9192\u53D1\u8D77\u4EBA_"));
|
|
7931
|
+
if (qc.cardKey) {
|
|
7932
|
+
const controls = [button("\u23F9 \u53D6\u6D88", { a: RC.stop, m: qc.cardKey }, "danger")];
|
|
7933
|
+
if (qc.completionReminder === "available") {
|
|
7934
|
+
controls.push(button("\u{1F514} \u5B8C\u6210\u540E\u63D0\u9192\u6211", { a: RC.remind, m: qc.cardKey }, "default"));
|
|
7935
|
+
}
|
|
7936
|
+
els.push(actions(controls, CONTROLS_EID));
|
|
7937
|
+
}
|
|
7791
7938
|
return card(els, { summary: "\u6392\u961F\u4E2D" });
|
|
7792
7939
|
}
|
|
7793
7940
|
function* groupBlocks(blocks) {
|
|
@@ -7869,7 +8016,9 @@ var EFFORT_TIER = {
|
|
|
7869
8016
|
low: { label: "\u4F4E", color: "yellow" },
|
|
7870
8017
|
medium: { label: "\u4E2D", color: "green" },
|
|
7871
8018
|
high: { label: "\u9AD8", color: "violet" },
|
|
7872
|
-
xhigh: { label: "\u6781\u9AD8", color: "purple" }
|
|
8019
|
+
xhigh: { label: "\u6781\u9AD8", color: "purple" },
|
|
8020
|
+
max: { label: "\u6700\u9AD8", color: "purple" },
|
|
8021
|
+
ultra: { label: "\u8D85\u5F3A", color: "purple" }
|
|
7873
8022
|
};
|
|
7874
8023
|
function modelEffortMd(model, effort) {
|
|
7875
8024
|
if (!effort) return model;
|
|
@@ -8014,6 +8163,16 @@ var RunCardStream = class {
|
|
|
8014
8163
|
lastAnswerText = "";
|
|
8015
8164
|
// Per-chat pacer shared with the chat's other streams (set in create()).
|
|
8016
8165
|
pacer = null;
|
|
8166
|
+
// Forced whole-card writes (button/settings repaint, queue → run flip,
|
|
8167
|
+
// terminal frame) must never race one another. In particular, an async
|
|
8168
|
+
// completion-reminder repaint that started just before turn completion must
|
|
8169
|
+
// land BEFORE the terminal frame, not finish late and put the card back into
|
|
8170
|
+
// its running layout. This tail is deliberately separate from the coalesced
|
|
8171
|
+
// pump: event consumption remains non-blocking, and the caller drains that
|
|
8172
|
+
// pump before finalizing.
|
|
8173
|
+
forcedUpdateTail = Promise.resolve();
|
|
8174
|
+
/** Once terminal finalization starts, reminder/settings repaint is stale. */
|
|
8175
|
+
liveUpdatesFrozen = false;
|
|
8017
8176
|
get messageId() {
|
|
8018
8177
|
return this._messageId;
|
|
8019
8178
|
}
|
|
@@ -8205,8 +8364,10 @@ var RunCardStream = class {
|
|
|
8205
8364
|
return false;
|
|
8206
8365
|
}
|
|
8207
8366
|
}
|
|
8208
|
-
/** Forced whole-card replace for structural
|
|
8209
|
-
*
|
|
8367
|
+
/** Forced whole-card replace for structural updates. Calls are serialized in
|
|
8368
|
+
* invocation order so concurrent callback repaints cannot complete out of
|
|
8369
|
+
* order. A terminal card built with streaming off clears the typewriter
|
|
8370
|
+
* cursor.
|
|
8210
8371
|
*
|
|
8211
8372
|
* The terminal frame MUST land — losing it leaves the card "streaming"
|
|
8212
8373
|
* forever (cursor + dead ⏹) while the run is over and `runsByCard` already
|
|
@@ -8214,10 +8375,43 @@ var RunCardStream = class {
|
|
|
8214
8375
|
* with exponential backoff (1s/2s/4s, {@link TERMINAL_RL_RETRIES} retries);
|
|
8215
8376
|
* anything else — typically 200810 "card in ongoing interaction" when the
|
|
8216
8377
|
* update fires inside a ⏹ click's 3s window — waits out the window and
|
|
8217
|
-
* retries once.
|
|
8218
|
-
|
|
8219
|
-
|
|
8378
|
+
* retries once. Returns whether the requested frame is observably on the
|
|
8379
|
+
* entity, so terminal callers can emit a truthful fallback notification. */
|
|
8380
|
+
updateCard(channel, fullCard) {
|
|
8381
|
+
return this.enqueueForcedUpdate(channel, fullCard);
|
|
8382
|
+
}
|
|
8383
|
+
/**
|
|
8384
|
+
* Repaint a still-live card (currently used by the completion-reminder
|
|
8385
|
+
* control). Once {@link finalizeCard} has synchronously frozen live repaints,
|
|
8386
|
+
* late callbacks become a no-op. Repaints accepted before the freeze share
|
|
8387
|
+
* the forced-update tail and therefore finish before the terminal frame.
|
|
8388
|
+
*/
|
|
8389
|
+
updateLiveCard(channel, fullCard) {
|
|
8390
|
+
if (this.liveUpdatesFrozen) return Promise.resolve(false);
|
|
8391
|
+
return this.enqueueForcedUpdate(channel, fullCard);
|
|
8392
|
+
}
|
|
8393
|
+
/**
|
|
8394
|
+
* Freeze live repaints synchronously, then enqueue the terminal whole-card
|
|
8395
|
+
* frame after every already-accepted forced update. Future intentional
|
|
8396
|
+
* non-live updates (for example demoting an old terminal card's settings
|
|
8397
|
+
* control) may still use {@link updateCard}.
|
|
8398
|
+
*/
|
|
8399
|
+
finalizeCard(channel, fullCard) {
|
|
8400
|
+
this.liveUpdatesFrozen = true;
|
|
8401
|
+
return this.enqueueForcedUpdate(channel, fullCard);
|
|
8402
|
+
}
|
|
8403
|
+
enqueueForcedUpdate(channel, fullCard) {
|
|
8404
|
+
if (!this.cardId) return Promise.resolve(false);
|
|
8220
8405
|
const data = JSON.stringify(fullCard);
|
|
8406
|
+
const task = this.forcedUpdateTail.then(() => this.pushForcedUpdate(channel, data));
|
|
8407
|
+
this.forcedUpdateTail = task.then(
|
|
8408
|
+
() => void 0,
|
|
8409
|
+
() => void 0
|
|
8410
|
+
);
|
|
8411
|
+
return task;
|
|
8412
|
+
}
|
|
8413
|
+
async pushForcedUpdate(channel, data) {
|
|
8414
|
+
if (!this.cardId) return false;
|
|
8221
8415
|
const push = async () => {
|
|
8222
8416
|
await channel.rawClient.cardkit.v1.card.update({
|
|
8223
8417
|
path: { card_id: this.cardId },
|
|
@@ -8229,13 +8423,13 @@ var RunCardStream = class {
|
|
|
8229
8423
|
await this.pacer?.wait();
|
|
8230
8424
|
try {
|
|
8231
8425
|
await push();
|
|
8232
|
-
return;
|
|
8426
|
+
return true;
|
|
8233
8427
|
} catch (err) {
|
|
8234
8428
|
const rl = isRateLimited(err);
|
|
8235
8429
|
if (rl) this.pacer?.penalize();
|
|
8236
8430
|
if (i >= (rl ? TERMINAL_RL_RETRIES : 1)) {
|
|
8237
8431
|
log.fail("card", err, { phase: "run-update-retry", cardId: this.cardId, seq: this.seq });
|
|
8238
|
-
return;
|
|
8432
|
+
return false;
|
|
8239
8433
|
}
|
|
8240
8434
|
log.fail("card", err, { phase: "run-update", cardId: this.cardId, seq: this.seq, retry: true, rateLimited: rl });
|
|
8241
8435
|
await new Promise((r) => setTimeout(r, rl ? RL_BACKOFF_BASE_MS * 2 ** i : 3200));
|
|
@@ -8583,6 +8777,81 @@ function withCodexHooksFeature(text) {
|
|
|
8583
8777
|
return [...lines, ""].join("\n");
|
|
8584
8778
|
}
|
|
8585
8779
|
|
|
8780
|
+
// src/card/completion-reminder.ts
|
|
8781
|
+
function formatCompletionElapsed(elapsedMs) {
|
|
8782
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1e3));
|
|
8783
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
8784
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
8785
|
+
const seconds = totalSeconds % 60;
|
|
8786
|
+
const parts = [];
|
|
8787
|
+
if (hours > 0) parts.push(`${hours} \u5C0F\u65F6`);
|
|
8788
|
+
if (minutes > 0) parts.push(`${minutes} \u5206`);
|
|
8789
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds} \u79D2`);
|
|
8790
|
+
return parts.join(" ");
|
|
8791
|
+
}
|
|
8792
|
+
function buildCompletionReminderContent(input2) {
|
|
8793
|
+
const task = compactSummary(input2.summary);
|
|
8794
|
+
const elapsed = formatCompletionElapsed(input2.elapsedMs);
|
|
8795
|
+
const headline = input2.outcome === "done" ? ` \u2705\u300C${task}\u300D\u5DF2\u5B8C\u6210 \xB7 \u7528\u65F6 ${elapsed}` : input2.outcome === "idle_timeout" ? ` \u23F1\u300C${task}\u300D\u54CD\u5E94\u8D85\u65F6 \xB7 \u7B49\u5F85 ${elapsed}` : ` \u26A0\uFE0F\u300C${task}\u300D\u6267\u884C\u5931\u8D25 \xB7 \u7528\u65F6 ${elapsed}`;
|
|
8796
|
+
const detail = input2.cardUpdated ? input2.outcome === "done" ? "\u7ED3\u679C\u5728\u4E0A\u65B9\u5361\u7247\u3002" : "\u8BE6\u60C5\u5728\u4E0A\u65B9\u5361\u7247\u3002" : "\u6700\u7EC8\u5361\u7247\u66F4\u65B0\u5931\u8D25\uFF0C\u8BF7\u67E5\u770B\u4E0A\u65B9\u6D41\u5F0F\u5185\u5BB9\u6216\u91CD\u65B0\u53D1\u8D77\u4EFB\u52A1\u3002";
|
|
8797
|
+
return JSON.stringify({
|
|
8798
|
+
zh_cn: {
|
|
8799
|
+
title: "",
|
|
8800
|
+
content: [
|
|
8801
|
+
[
|
|
8802
|
+
{ tag: "at", user_id: input2.requesterOpenId },
|
|
8803
|
+
{ tag: "text", text: headline }
|
|
8804
|
+
],
|
|
8805
|
+
[{ tag: "text", text: detail }]
|
|
8806
|
+
]
|
|
8807
|
+
}
|
|
8808
|
+
});
|
|
8809
|
+
}
|
|
8810
|
+
function compactSummary(summary) {
|
|
8811
|
+
const clean = (summary ?? "").replace(/\s+/g, " ").trim();
|
|
8812
|
+
if (!clean) return "\u672C\u8F6E\u4EFB\u52A1";
|
|
8813
|
+
return clean.length > 32 ? `${clean.slice(0, 31)}\u2026` : clean;
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8816
|
+
// src/bot/completion-reminder.ts
|
|
8817
|
+
init_schema();
|
|
8818
|
+
init_logger();
|
|
8819
|
+
async function sendCompletionReminderReply(deps, input2) {
|
|
8820
|
+
if (!input2.requesterOpenId || input2.outcome !== "done" && input2.outcome !== "error" && input2.outcome !== "idle_timeout") {
|
|
8821
|
+
return "skipped";
|
|
8822
|
+
}
|
|
8823
|
+
const elapsedMs = Math.max(0, (deps.now?.() ?? Date.now()) - input2.requestedAt);
|
|
8824
|
+
const policyMatch = shouldSendCompletionReminder(deps.cfg, {
|
|
8825
|
+
outcome: input2.outcome,
|
|
8826
|
+
elapsedMs,
|
|
8827
|
+
manuallyRequested: input2.manuallyRequested
|
|
8828
|
+
});
|
|
8829
|
+
if (input2.cardUpdated && !policyMatch) return "skipped";
|
|
8830
|
+
if (deps.dedupe.seen(input2.cardMsgId)) return "skipped";
|
|
8831
|
+
const content = buildCompletionReminderContent({
|
|
8832
|
+
requesterOpenId: input2.requesterOpenId,
|
|
8833
|
+
outcome: input2.outcome,
|
|
8834
|
+
elapsedMs,
|
|
8835
|
+
summary: input2.summary,
|
|
8836
|
+
cardUpdated: input2.cardUpdated
|
|
8837
|
+
});
|
|
8838
|
+
try {
|
|
8839
|
+
await deps.channel.rawClient.im.v1.message.reply({
|
|
8840
|
+
path: { message_id: input2.cardMsgId },
|
|
8841
|
+
data: { msg_type: "post", content, reply_in_thread: input2.replyInThread }
|
|
8842
|
+
});
|
|
8843
|
+
log.info("card", "completion-reminder", {
|
|
8844
|
+
terminal: input2.outcome,
|
|
8845
|
+
elapsedMs,
|
|
8846
|
+
cardUpdated: input2.cardUpdated
|
|
8847
|
+
});
|
|
8848
|
+
return "sent";
|
|
8849
|
+
} catch (err) {
|
|
8850
|
+
log.fail("card", err, { phase: "completion-reminder", terminal: input2.outcome });
|
|
8851
|
+
return "failed";
|
|
8852
|
+
}
|
|
8853
|
+
}
|
|
8854
|
+
|
|
8586
8855
|
// src/service/update.ts
|
|
8587
8856
|
init_paths();
|
|
8588
8857
|
import { closeSync, existsSync as existsSync9, openSync as openSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2, writeSync } from "fs";
|
|
@@ -9889,7 +10158,15 @@ function heatmapElements(p, today) {
|
|
|
9889
10158
|
return [md("\u{1F4C8} **\u6BCF\u65E5 Token \u7528\u91CF**"), heatmapChartEl(p.dailyBuckets, today)];
|
|
9890
10159
|
}
|
|
9891
10160
|
function effortLabel(effort) {
|
|
9892
|
-
const m = {
|
|
10161
|
+
const m = {
|
|
10162
|
+
minimal: "\u6781\u4F4E",
|
|
10163
|
+
low: "\u4F4E",
|
|
10164
|
+
medium: "\u4E2D",
|
|
10165
|
+
high: "\u9AD8",
|
|
10166
|
+
xhigh: "\u8D85\u9AD8",
|
|
10167
|
+
max: "\u6700\u9AD8",
|
|
10168
|
+
ultra: "\u8D85\u5F3A"
|
|
10169
|
+
};
|
|
9893
10170
|
return m[effort] ?? effort;
|
|
9894
10171
|
}
|
|
9895
10172
|
function insightsElements(p) {
|
|
@@ -10130,6 +10407,7 @@ init_paths();
|
|
|
10130
10407
|
init_logger();
|
|
10131
10408
|
import { mkdir as mkdir11 } from "fs/promises";
|
|
10132
10409
|
import { existsSync as existsSync10 } from "fs";
|
|
10410
|
+
import { homedir as homedir7 } from "os";
|
|
10133
10411
|
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7 } from "path";
|
|
10134
10412
|
|
|
10135
10413
|
// src/project/announcement.ts
|
|
@@ -10319,22 +10597,38 @@ function assertBackendUsable(backend, mode) {
|
|
|
10319
10597
|
}
|
|
10320
10598
|
throw new Error(`\u6240\u9009\u540E\u7AEF\u300C${backend}\u300D\u5F53\u524D\u4E0D\u53EF\u7528\uFF08\u672A\u4E0B\u8F7D\u6216\u4E0D\u652F\u6301\u8BE5\u6743\u9650\u6863\uFF09\uFF0C\u8BF7\u56DE\u5361\u7247\u91CD\u65B0\u9009\u62E9`);
|
|
10321
10599
|
}
|
|
10322
|
-
async function resolveCwd(name, existingPath) {
|
|
10600
|
+
async function resolveCwd(name, existingPath, configuredProjectsRootDir) {
|
|
10323
10601
|
if (existingPath) {
|
|
10324
10602
|
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve7(existingPath);
|
|
10325
10603
|
if (!existsSync10(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
10326
10604
|
return { cwd: cwd2, blank: false };
|
|
10327
10605
|
}
|
|
10328
|
-
const cwd = join17(
|
|
10606
|
+
const cwd = join17(resolveProjectsRootDir(configuredProjectsRootDir), name);
|
|
10329
10607
|
await mkdir11(cwd, { recursive: true });
|
|
10330
10608
|
return { cwd, blank: true };
|
|
10331
10609
|
}
|
|
10610
|
+
function resolveProjectsRootDir(configured) {
|
|
10611
|
+
if (configured === void 0 || configured === null) return paths.projectsRootDir;
|
|
10612
|
+
if (typeof configured !== "string") {
|
|
10613
|
+
throw new Error("\u914D\u7F6E preferences.projectsRootDir \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
10614
|
+
}
|
|
10615
|
+
const value = configured.trim();
|
|
10616
|
+
if (!value) return paths.projectsRootDir;
|
|
10617
|
+
if (value === "~") return homedir7();
|
|
10618
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
10619
|
+
return resolve7(homedir7(), value.slice(2));
|
|
10620
|
+
}
|
|
10621
|
+
if (!isAbsolute2(value)) {
|
|
10622
|
+
throw new Error("\u914D\u7F6E preferences.projectsRootDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u6216\u4EE5 ~ \u5F00\u5934");
|
|
10623
|
+
}
|
|
10624
|
+
return resolve7(value);
|
|
10625
|
+
}
|
|
10332
10626
|
async function createProject(channel, input2) {
|
|
10333
10627
|
const name = input2.name.trim();
|
|
10334
10628
|
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
10335
10629
|
if (await getProjectByName(name)) throw new Error(`\u9879\u76EE\u540D\u300C${name}\u300D\u5DF2\u5B58\u5728\uFF0C\u6362\u4E2A\u540D\u6216\u7528 /projects \u770B\u5DF2\u6709\u7684`);
|
|
10336
10630
|
assertBackendUsable(input2.backend, input2.mode ?? "full");
|
|
10337
|
-
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
10631
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath, input2.projectsRootDir);
|
|
10338
10632
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
10339
10633
|
params: { user_id_type: "open_id" },
|
|
10340
10634
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
@@ -10371,7 +10665,7 @@ async function joinExistingGroup(channel, input2) {
|
|
|
10371
10665
|
const bound = await getProjectByChatId(input2.chatId);
|
|
10372
10666
|
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
10373
10667
|
assertBackendUsable(input2.backend, input2.mode ?? "qa");
|
|
10374
|
-
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
10668
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath, input2.projectsRootDir);
|
|
10375
10669
|
const project = {
|
|
10376
10670
|
name,
|
|
10377
10671
|
chatId: input2.chatId,
|
|
@@ -11505,7 +11799,6 @@ function selectValue(formValue, name) {
|
|
|
11505
11799
|
function asTier(v) {
|
|
11506
11800
|
return v === "qa" || v === "write" || v === "full" ? v : void 0;
|
|
11507
11801
|
}
|
|
11508
|
-
var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
11509
11802
|
function asEffort(v) {
|
|
11510
11803
|
return v !== void 0 && REASONING_EFFORTS.includes(v) ? v : void 0;
|
|
11511
11804
|
}
|
|
@@ -11532,6 +11825,22 @@ function safeBackendId(formValue) {
|
|
|
11532
11825
|
const v = selectValue(formValue, "backend");
|
|
11533
11826
|
return v && backendIds().includes(v) ? v : void 0;
|
|
11534
11827
|
}
|
|
11828
|
+
function activateQueuedTurn(state, turn) {
|
|
11829
|
+
state.requesterOpenId = turn.requesterOpenId;
|
|
11830
|
+
state.completionReminderRequested = false;
|
|
11831
|
+
}
|
|
11832
|
+
function isCompletionReminderRequester(operatorOpenId, requesterOpenId) {
|
|
11833
|
+
return Boolean(operatorOpenId && requesterOpenId && operatorOpenId === requesterOpenId);
|
|
11834
|
+
}
|
|
11835
|
+
function settleOrdinaryTurnRender(render, input2) {
|
|
11836
|
+
if (input2.interrupted) render.interrupt();
|
|
11837
|
+
else if (input2.timedOut) render.timeout(input2.idleTimeoutSeconds);
|
|
11838
|
+
else if (input2.procDead && render.terminal() === "running") {
|
|
11839
|
+
render.apply({ type: "error", message: "agent \u8FDB\u7A0B\u5F02\u5E38\u9000\u51FA\uFF0C\u8BF7\u91CD\u53D1\u672C\u6761\u6D88\u606F", willRetry: false });
|
|
11840
|
+
} else {
|
|
11841
|
+
render.finalize();
|
|
11842
|
+
}
|
|
11843
|
+
}
|
|
11535
11844
|
function parseGoalTrigger(text) {
|
|
11536
11845
|
if (!/(^|\s)\/goal(?=\s|$)/i.test(text)) return null;
|
|
11537
11846
|
const objective = text.replace(/(^|\s)\/goal(?=\s|$)/gi, " ").replace(/\s+/g, " ").trim();
|
|
@@ -11628,14 +11937,21 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11628
11937
|
const commentInstrUsed = /* @__PURE__ */ new Map();
|
|
11629
11938
|
const sema = new Semaphore(getMaxConcurrentRuns(cfg));
|
|
11630
11939
|
const currentIdleMs = () => getRunIdleTimeoutMs(cfg) ?? 0;
|
|
11940
|
+
const writePreferences = createAppPreferencesWriter({ cfg });
|
|
11631
11941
|
const resumePending = /* @__PURE__ */ new Map();
|
|
11632
11942
|
const modelPending = /* @__PURE__ */ new Map();
|
|
11633
11943
|
const runsByCard = /* @__PURE__ */ new Map();
|
|
11634
11944
|
const runCards = /* @__PURE__ */ new Map();
|
|
11635
11945
|
const runStreams = /* @__PURE__ */ new Map();
|
|
11946
|
+
const completionReminderRefreshers = /* @__PURE__ */ new Map();
|
|
11947
|
+
const refreshCompletionReminderCards = () => {
|
|
11948
|
+
for (const refresh of completionReminderRefreshers.values()) refresh();
|
|
11949
|
+
};
|
|
11950
|
+
const completionReminderSent = new RecentIdCache(4096, 24 * 60 * 6e4);
|
|
11636
11951
|
const lastRunCard = /* @__PURE__ */ new Map();
|
|
11637
11952
|
const lastUsage = /* @__PURE__ */ new Map();
|
|
11638
11953
|
const seenInbound = new RecentIdCache();
|
|
11954
|
+
const completionReminderView = (state) => shouldShowCompletionReminderButton(cfg) ? state.completionReminderRequested ? "requested" : "available" : void 0;
|
|
11639
11955
|
const listModels = (be = backend) => be.listModels();
|
|
11640
11956
|
async function addReaction(messageId, emoji) {
|
|
11641
11957
|
try {
|
|
@@ -11943,7 +12259,12 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11943
12259
|
}
|
|
11944
12260
|
}
|
|
11945
12261
|
}
|
|
11946
|
-
cur.queue.push({
|
|
12262
|
+
cur.queue.push({
|
|
12263
|
+
input: { text: woven, images },
|
|
12264
|
+
requesterOpenId: msg.senderId,
|
|
12265
|
+
requestedAt: msg.createTime || Date.now(),
|
|
12266
|
+
summary: stripFileTokens(text).slice(0, 80) || void 0
|
|
12267
|
+
});
|
|
11947
12268
|
log.info("intake", "queued", { depth: cur.queue.length });
|
|
11948
12269
|
return;
|
|
11949
12270
|
}
|
|
@@ -11968,7 +12289,12 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11968
12289
|
void replyGoalBusy(msg, flat);
|
|
11969
12290
|
return;
|
|
11970
12291
|
}
|
|
11971
|
-
existing.queue.push({
|
|
12292
|
+
existing.queue.push({
|
|
12293
|
+
input: { text, images: preloadedImages },
|
|
12294
|
+
requesterOpenId: msg.senderId,
|
|
12295
|
+
requestedAt: msg.createTime || Date.now(),
|
|
12296
|
+
summary: stripFileTokens(summaryText2 ?? text).slice(0, 80) || void 0
|
|
12297
|
+
});
|
|
11972
12298
|
log.info("intake", "queued", { depth: existing.queue.length });
|
|
11973
12299
|
return;
|
|
11974
12300
|
}
|
|
@@ -12044,7 +12370,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12044
12370
|
firstText,
|
|
12045
12371
|
images,
|
|
12046
12372
|
knownThreadId: sessionKey,
|
|
12373
|
+
summary: stripFileTokens(summaryText2 ?? text).slice(0, 80) || "(\u672C\u8F6E\u4EFB\u52A1)",
|
|
12047
12374
|
requesterOpenId: msg.senderId,
|
|
12375
|
+
requestedAt: msg.createTime || tIntake,
|
|
12048
12376
|
// 编织完成 → turn/start 之间不再读盘:首轮直接用预取的会话记录
|
|
12049
12377
|
// (prior=undefined 即确知是全新会话,刚 upsert 的记录还没有 model)。
|
|
12050
12378
|
firstRec: prior ?? null,
|
|
@@ -12163,6 +12491,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12163
12491
|
cwd,
|
|
12164
12492
|
summary: stripFileTokens(text).slice(0, 80) || "(\u7A7A)",
|
|
12165
12493
|
requesterOpenId: msg.senderId,
|
|
12494
|
+
requestedAt: msg.createTime || tIntake,
|
|
12166
12495
|
roleSuffix: perm.roleSuffix,
|
|
12167
12496
|
backendId: be.id,
|
|
12168
12497
|
timing: { tResolve: tResolveDone - tIntake, tWeave: Date.now() - tIntake }
|
|
@@ -12527,6 +12856,35 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12527
12856
|
}
|
|
12528
12857
|
st.interrupt?.();
|
|
12529
12858
|
log.info("card", "action", { actionId: "run.stop", stopped: Boolean(st.interrupt) });
|
|
12859
|
+
}).on(RC.remind, ({ evt, value }) => {
|
|
12860
|
+
const key = typeof value.m === "string" ? value.m : evt.messageId;
|
|
12861
|
+
if (!runAllowed(evt)) return;
|
|
12862
|
+
const st = runsByCard.get(key);
|
|
12863
|
+
if (!st) {
|
|
12864
|
+
if (!runControlNotes.seen(`remind-ended:${key}:${evt.operator?.openId ?? ""}`)) {
|
|
12865
|
+
void channel.send(evt.chatId, { markdown: "\u2139\uFE0F \u8BE5\u4EFB\u52A1\u5DF2\u7ED3\u675F\uFF0C\u65E0\u9700\u518D\u5F00\u542F\u5B8C\u6210\u63D0\u9192\u3002" }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
12866
|
+
}
|
|
12867
|
+
log.info("card", "action", { actionId: RC.remind, ended: true });
|
|
12868
|
+
return;
|
|
12869
|
+
}
|
|
12870
|
+
const op = evt.operator?.openId;
|
|
12871
|
+
if (!isCompletionReminderRequester(op, st.requesterOpenId)) {
|
|
12872
|
+
if (!runControlNotes.seen(`remind-deny:${key}:${op ?? ""}`)) {
|
|
12873
|
+
void channel.send(evt.chatId, { markdown: "\u26A0\uFE0F \u4EC5\u672C\u8F6E\u53D1\u8D77\u4EBA\u53EF\u5F00\u542F\u5B8C\u6210\u63D0\u9192\u3002" }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
12874
|
+
}
|
|
12875
|
+
log.info("card", "action", { actionId: RC.remind, denied: true });
|
|
12876
|
+
return;
|
|
12877
|
+
}
|
|
12878
|
+
if (!shouldShowCompletionReminderButton(cfg)) {
|
|
12879
|
+
completionReminderRefreshers.get(key)?.();
|
|
12880
|
+
log.info("card", "action", { actionId: RC.remind, stale: true });
|
|
12881
|
+
return;
|
|
12882
|
+
}
|
|
12883
|
+
if (!st.completionReminderRequested) {
|
|
12884
|
+
st.completionReminderRequested = true;
|
|
12885
|
+
completionReminderRefreshers.get(key)?.();
|
|
12886
|
+
}
|
|
12887
|
+
log.info("card", "action", { actionId: RC.remind, requested: true });
|
|
12530
12888
|
}).on(RC.endGoal, ({ evt, value }) => {
|
|
12531
12889
|
const key = typeof value.m === "string" ? value.m : evt.messageId;
|
|
12532
12890
|
if (!runAllowed(evt)) return;
|
|
@@ -12554,12 +12912,49 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12554
12912
|
return m;
|
|
12555
12913
|
};
|
|
12556
12914
|
function applyPref(evt, mut, opts) {
|
|
12557
|
-
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12558
|
-
const
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12915
|
+
if (!dmAdmin(evt.operator?.openId)) return Promise.resolve(false);
|
|
12916
|
+
const saved = writePreferences(mut).then(
|
|
12917
|
+
() => true,
|
|
12918
|
+
(err) => {
|
|
12919
|
+
log.fail("console", err, { phase: "save-config" });
|
|
12920
|
+
return false;
|
|
12921
|
+
}
|
|
12922
|
+
);
|
|
12923
|
+
if (opts?.render !== false) {
|
|
12924
|
+
void patch(evt, async () => {
|
|
12925
|
+
await saved;
|
|
12926
|
+
return renderSettings();
|
|
12927
|
+
});
|
|
12928
|
+
}
|
|
12929
|
+
return saved;
|
|
12930
|
+
}
|
|
12931
|
+
let cliEnabledTransition = Promise.resolve();
|
|
12932
|
+
function setCliBridgeEnabled(evt, enabled) {
|
|
12933
|
+
const run = cliEnabledTransition.then(async () => {
|
|
12934
|
+
if (getCliBridgePreferences(cfg).enabled === enabled) return;
|
|
12935
|
+
try {
|
|
12936
|
+
if (enabled) await cliBridge?.start?.();
|
|
12937
|
+
else await cliBridge?.shutdown?.();
|
|
12938
|
+
const saved = await applyPref(
|
|
12939
|
+
evt,
|
|
12940
|
+
(p) => {
|
|
12941
|
+
p.cliBridge = { ...p.cliBridge ?? {}, enabled };
|
|
12942
|
+
},
|
|
12943
|
+
{ render: false }
|
|
12944
|
+
);
|
|
12945
|
+
if (!saved) {
|
|
12946
|
+
if (enabled) await cliBridge?.shutdown?.();
|
|
12947
|
+
else await cliBridge?.start?.();
|
|
12948
|
+
}
|
|
12949
|
+
} catch (err) {
|
|
12950
|
+
log.fail("cli-bridge", err, { phase: enabled ? "enable" : "disable" });
|
|
12951
|
+
}
|
|
12952
|
+
});
|
|
12953
|
+
cliEnabledTransition = run.then(
|
|
12954
|
+
() => void 0,
|
|
12955
|
+
() => void 0
|
|
12956
|
+
);
|
|
12957
|
+
return run;
|
|
12563
12958
|
}
|
|
12564
12959
|
let cliHookStatuses;
|
|
12565
12960
|
function renderSettings() {
|
|
@@ -12581,20 +12976,36 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12581
12976
|
function commentBackendOptions() {
|
|
12582
12977
|
return visibleCatalog().filter((e) => e.id === DEFAULT_BACKEND_ID || isBackendEntryInstalled(e)).map((e) => ({ id: e.id, label: e.displayName }));
|
|
12583
12978
|
}
|
|
12979
|
+
let commentSettingsRenderGeneration = 0;
|
|
12584
12980
|
async function renderCommentSettings(notice) {
|
|
12585
|
-
|
|
12586
|
-
|
|
12587
|
-
|
|
12981
|
+
++commentSettingsRenderGeneration;
|
|
12982
|
+
for (; ; ) {
|
|
12983
|
+
const generation = commentSettingsRenderGeneration;
|
|
12984
|
+
const comments = getCommentsConfig(cfg);
|
|
12985
|
+
const models = await listModels(backendFor(comments.backend)).catch(() => []);
|
|
12986
|
+
if (generation !== commentSettingsRenderGeneration) continue;
|
|
12987
|
+
const latest = getCommentsConfig(cfg);
|
|
12988
|
+
if (latest.backend !== comments.backend || latest.model !== comments.model || latest.effort !== comments.effort) {
|
|
12989
|
+
continue;
|
|
12990
|
+
}
|
|
12991
|
+
const snapshot = {
|
|
12992
|
+
...cfg,
|
|
12993
|
+
preferences: { ...cfg.preferences ?? {}, comments: { ...comments } }
|
|
12994
|
+
};
|
|
12995
|
+
return buildCommentSettingsCard(snapshot, commentBackendOptions(), models, notice);
|
|
12996
|
+
}
|
|
12588
12997
|
}
|
|
12589
12998
|
function applyCommentsPref(evt, mut) {
|
|
12590
12999
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12591
|
-
const
|
|
12592
|
-
|
|
12593
|
-
|
|
12594
|
-
|
|
12595
|
-
|
|
12596
|
-
void
|
|
12597
|
-
|
|
13000
|
+
const saved = writePreferences((prefs) => {
|
|
13001
|
+
const comments = { ...prefs.comments ?? {} };
|
|
13002
|
+
mut(comments);
|
|
13003
|
+
prefs.comments = comments;
|
|
13004
|
+
}).catch((err) => log.fail("console", err, { phase: "save-config" }));
|
|
13005
|
+
void patch(evt, async () => {
|
|
13006
|
+
await saved;
|
|
13007
|
+
return renderCommentSettings();
|
|
13008
|
+
});
|
|
12598
13009
|
}
|
|
12599
13010
|
const freshMenu = (evt) => {
|
|
12600
13011
|
patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
|
|
@@ -12699,7 +13110,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12699
13110
|
else if (!op) result = buildNewProjectFormCard({ name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD", backends: backends2 });
|
|
12700
13111
|
else {
|
|
12701
13112
|
try {
|
|
12702
|
-
const p = await createProject(channel, {
|
|
13113
|
+
const p = await createProject(channel, {
|
|
13114
|
+
name,
|
|
13115
|
+
ownerOpenId: op,
|
|
13116
|
+
existingPath: cwdIn || void 0,
|
|
13117
|
+
projectsRootDir: cfg.preferences?.projectsRootDir,
|
|
13118
|
+
kind,
|
|
13119
|
+
backend: backend2
|
|
13120
|
+
});
|
|
12703
13121
|
log.info("console", "new-project", { name: p.name, blank: p.blank, backend: p.backend });
|
|
12704
13122
|
result = buildNewProjectDoneCard(p);
|
|
12705
13123
|
} catch (err) {
|
|
@@ -12727,7 +13145,16 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12727
13145
|
else if (!op) result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD", backends: backends2 });
|
|
12728
13146
|
else {
|
|
12729
13147
|
try {
|
|
12730
|
-
const p = await joinExistingGroup(channel, {
|
|
13148
|
+
const p = await joinExistingGroup(channel, {
|
|
13149
|
+
name,
|
|
13150
|
+
chatId,
|
|
13151
|
+
addedBy: op,
|
|
13152
|
+
existingPath: cwdIn || void 0,
|
|
13153
|
+
projectsRootDir: cfg.preferences?.projectsRootDir,
|
|
13154
|
+
kind,
|
|
13155
|
+
backend: backend2,
|
|
13156
|
+
mode: bindModeFor(backend2)
|
|
13157
|
+
});
|
|
12731
13158
|
log.info("console", "join-group", { name: p.name, blank: p.blank, backend: p.backend, mode: p.mode });
|
|
12732
13159
|
result = buildNewProjectDoneCard(p);
|
|
12733
13160
|
} catch (err) {
|
|
@@ -12746,26 +13173,15 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12746
13173
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, renderSettings);
|
|
12747
13174
|
}).on(DM.coffeeSettings, async ({ evt }) => {
|
|
12748
13175
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, () => renderCoffeeSettings(true));
|
|
12749
|
-
}).on(CLI.toggleEnabled,
|
|
13176
|
+
}).on(CLI.toggleEnabled, ({ evt, value }) => {
|
|
12750
13177
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12751
13178
|
const enabled = value.v === "on";
|
|
12752
13179
|
if (enabled && !canEnableCliBridge(cfg).ok) return;
|
|
12753
|
-
|
|
12754
|
-
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
}, { render: false });
|
|
12759
|
-
} else {
|
|
12760
|
-
applyPref(evt, (p) => {
|
|
12761
|
-
p.cliBridge = { ...p.cliBridge ?? {}, enabled: false };
|
|
12762
|
-
}, { render: false });
|
|
12763
|
-
await cliBridge?.shutdown?.();
|
|
12764
|
-
}
|
|
12765
|
-
} catch (err) {
|
|
12766
|
-
log.fail("cli-bridge", err, { phase: enabled ? "enable" : "disable" });
|
|
12767
|
-
}
|
|
12768
|
-
void patch(evt, renderCoffeeSettings);
|
|
13180
|
+
const transition = setCliBridgeEnabled(evt, enabled);
|
|
13181
|
+
void patch(evt, async () => {
|
|
13182
|
+
await transition;
|
|
13183
|
+
return renderCoffeeSettings();
|
|
13184
|
+
});
|
|
12769
13185
|
}).on(CLI.repairHooks, async ({ evt }) => {
|
|
12770
13186
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12771
13187
|
try {
|
|
@@ -12780,27 +13196,36 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12780
13196
|
}).on(CLI.setNotifyScope, ({ evt, value }) => {
|
|
12781
13197
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12782
13198
|
const scope = value.v === "bound_projects" || value.v === "none" ? value.v : "all";
|
|
12783
|
-
applyPref(evt, (p) => {
|
|
13199
|
+
const saved = applyPref(evt, (p) => {
|
|
12784
13200
|
p.cliBridge = { ...p.cliBridge ?? {}, notifyScope: scope };
|
|
12785
13201
|
}, { render: false });
|
|
12786
|
-
void patch(evt,
|
|
13202
|
+
void patch(evt, async () => {
|
|
13203
|
+
await saved;
|
|
13204
|
+
return renderCoffeeSettings();
|
|
13205
|
+
});
|
|
12787
13206
|
}).on(CLI.toggleAgent, ({ evt, value }) => {
|
|
12788
13207
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12789
13208
|
const agent = value.agent === "codex" ? "codex" : "claude";
|
|
12790
13209
|
const on = value.v === "on";
|
|
12791
|
-
applyPref(evt, (p) => {
|
|
13210
|
+
const saved = applyPref(evt, (p) => {
|
|
12792
13211
|
const cur = p.cliBridge ?? {};
|
|
12793
13212
|
p.cliBridge = { ...cur, agents: { ...cur.agents ?? {}, [agent]: on } };
|
|
12794
13213
|
}, { render: false });
|
|
12795
|
-
void patch(evt,
|
|
13214
|
+
void patch(evt, async () => {
|
|
13215
|
+
await saved;
|
|
13216
|
+
return renderCoffeeSettings();
|
|
13217
|
+
});
|
|
12796
13218
|
}).on(CLI.toggleKeepAwake, ({ evt, value }) => {
|
|
12797
13219
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12798
13220
|
const on = value.v === "on";
|
|
12799
|
-
applyPref(evt, (p) => {
|
|
13221
|
+
const saved = applyPref(evt, (p) => {
|
|
12800
13222
|
const cur = p.cliBridge ?? {};
|
|
12801
13223
|
p.cliBridge = { ...cur, keepAwake: { ...cur.keepAwake ?? {}, enabled: on } };
|
|
12802
13224
|
}, { render: false });
|
|
12803
|
-
void patch(evt,
|
|
13225
|
+
void patch(evt, async () => {
|
|
13226
|
+
await saved;
|
|
13227
|
+
return renderCoffeeSettings();
|
|
13228
|
+
});
|
|
12804
13229
|
}).on(DM.doctor, async ({ evt }) => {
|
|
12805
13230
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12806
13231
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
|
|
@@ -12969,6 +13394,52 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
12969
13394
|
}
|
|
12970
13395
|
const sec = n === 0 ? 0 : Math.min(Math.max(Math.floor(n), RUN_IDLE_TIMEOUT_MIN_SEC), RUN_IDLE_TIMEOUT_MAX_SEC);
|
|
12971
13396
|
applyPref(evt, (p) => p.runIdleTimeoutSeconds = sec);
|
|
13397
|
+
}).on(DM.setCompletionReminder, ({ evt, value }) => {
|
|
13398
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
13399
|
+
const mode = value.v;
|
|
13400
|
+
if (mode !== "manual" && mode !== "long" && mode !== "failures" && mode !== "always") return;
|
|
13401
|
+
const saved = performSetCompletionReminder({
|
|
13402
|
+
cfg,
|
|
13403
|
+
mode,
|
|
13404
|
+
writePreferences
|
|
13405
|
+
}).then(
|
|
13406
|
+
(result) => {
|
|
13407
|
+
if (!result.ok) log.warn("console", "completion-reminder-rejected", { reason: result.reason });
|
|
13408
|
+
else refreshCompletionReminderCards();
|
|
13409
|
+
},
|
|
13410
|
+
(err) => log.fail("console", err, { phase: "save-config" })
|
|
13411
|
+
);
|
|
13412
|
+
void patch(evt, async () => {
|
|
13413
|
+
await saved;
|
|
13414
|
+
return renderSettings();
|
|
13415
|
+
});
|
|
13416
|
+
}).on(DM.completionReminderCustom, ({ evt }) => {
|
|
13417
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
13418
|
+
void patch(evt, buildCompletionReminderCustomCard(cfg));
|
|
13419
|
+
}).on(DM.completionReminderCustomSubmit, ({ evt, formValue }) => {
|
|
13420
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
13421
|
+
const raw = String(formValue?.minutes ?? "").trim();
|
|
13422
|
+
const minutes = Number(raw);
|
|
13423
|
+
if (!Number.isInteger(minutes) || minutes < COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES || minutes > COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES) {
|
|
13424
|
+
void patch(evt, buildCompletionReminderCustomCard(cfg));
|
|
13425
|
+
return;
|
|
13426
|
+
}
|
|
13427
|
+
const saved = performSetCompletionReminder({
|
|
13428
|
+
cfg,
|
|
13429
|
+
mode: "long",
|
|
13430
|
+
longTaskMinutes: minutes,
|
|
13431
|
+
writePreferences
|
|
13432
|
+
}).then(
|
|
13433
|
+
(result) => {
|
|
13434
|
+
if (!result.ok) log.warn("console", "completion-reminder-rejected", { reason: result.reason });
|
|
13435
|
+
else refreshCompletionReminderCards();
|
|
13436
|
+
},
|
|
13437
|
+
(err) => log.fail("console", err, { phase: "save-config" })
|
|
13438
|
+
);
|
|
13439
|
+
void patch(evt, async () => {
|
|
13440
|
+
await saved;
|
|
13441
|
+
return renderSettings();
|
|
13442
|
+
});
|
|
12972
13443
|
}).on(DM.setPending, ({ evt, value }) => {
|
|
12973
13444
|
if (value.v === "steer" || value.v === "queue") applyPref(evt, (p) => p.pendingPolicy = value.v);
|
|
12974
13445
|
}).on(DM.setConcurrency, ({ evt, value }) => {
|
|
@@ -13124,11 +13595,12 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13124
13595
|
log.info("console", "admin-add", { picked: id?.slice(-6) ?? null });
|
|
13125
13596
|
void (async () => {
|
|
13126
13597
|
if (id) {
|
|
13127
|
-
|
|
13128
|
-
|
|
13129
|
-
|
|
13130
|
-
|
|
13131
|
-
|
|
13598
|
+
await writePreferences((preferences) => {
|
|
13599
|
+
const access = { ...preferences.access ?? {} };
|
|
13600
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
13601
|
+
access.admins = Array.from(/* @__PURE__ */ new Set([...access.admins ?? [], id]));
|
|
13602
|
+
preferences.access = access;
|
|
13603
|
+
}).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
13132
13604
|
}
|
|
13133
13605
|
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
13134
13606
|
const next = buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
@@ -13139,11 +13611,12 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13139
13611
|
const id = typeof value.u === "string" ? value.u : "";
|
|
13140
13612
|
patch(evt, async () => {
|
|
13141
13613
|
if (id && id !== resolveOwner(cfg)) {
|
|
13142
|
-
|
|
13143
|
-
|
|
13144
|
-
|
|
13145
|
-
|
|
13146
|
-
|
|
13614
|
+
await writePreferences((preferences) => {
|
|
13615
|
+
const access = { ...preferences.access ?? {} };
|
|
13616
|
+
access.ownerOpenId ??= resolveOwner(cfg);
|
|
13617
|
+
access.admins = (access.admins ?? []).filter((x) => x !== id);
|
|
13618
|
+
preferences.access = access;
|
|
13619
|
+
}).catch((e) => log.fail("console", e, { phase: "save-config" }));
|
|
13147
13620
|
}
|
|
13148
13621
|
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
13149
13622
|
return buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
@@ -13368,22 +13841,36 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13368
13841
|
settleUpdate(evt.messageId, buildResumeErrorCard(state, err instanceof Error ? err.message : String(err)));
|
|
13369
13842
|
}
|
|
13370
13843
|
}
|
|
13844
|
+
async function sendCompletionReminder(input2) {
|
|
13845
|
+
await sendCompletionReminderReply({ channel, cfg, dedupe: completionReminderSent }, input2);
|
|
13846
|
+
}
|
|
13371
13847
|
async function acquireRunSlot(opts, state, activeKey, reaction) {
|
|
13372
13848
|
if (sema.hasFree()) return { release: await sema.acquire() };
|
|
13373
13849
|
const stream2 = new RunCardStream();
|
|
13374
13850
|
let msgId;
|
|
13851
|
+
const queueCard = (input2) => buildQueuedCard({
|
|
13852
|
+
...input2,
|
|
13853
|
+
...!state.isGoal ? { completionReminder: completionReminderView(state) } : {}
|
|
13854
|
+
});
|
|
13375
13855
|
const q = sema.enqueue((pos) => {
|
|
13376
|
-
if (msgId) stream2.streamCoalesced(channel,
|
|
13856
|
+
if (msgId) stream2.streamCoalesced(channel, queueCard({ position: pos, cardKey: msgId }), null);
|
|
13377
13857
|
});
|
|
13378
13858
|
try {
|
|
13379
|
-
msgId = await stream2.create(channel, opts.chatId,
|
|
13859
|
+
msgId = await stream2.create(channel, opts.chatId, queueCard({ position: q.position() }), {
|
|
13380
13860
|
replyTo: opts.replyTo,
|
|
13381
13861
|
replyInThread: opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId)
|
|
13382
13862
|
});
|
|
13383
13863
|
const pos = q.position();
|
|
13384
|
-
if (pos > 0) await stream2.updateCard(channel,
|
|
13864
|
+
if (pos > 0) await stream2.updateCard(channel, queueCard({ position: pos, cardKey: msgId }));
|
|
13385
13865
|
runsByCard.set(msgId, state);
|
|
13386
13866
|
runStreams.set(msgId, stream2);
|
|
13867
|
+
if (!state.isGoal) {
|
|
13868
|
+
const key = msgId;
|
|
13869
|
+
completionReminderRefreshers.set(key, () => {
|
|
13870
|
+
const currentPos = q.position();
|
|
13871
|
+
if (currentPos > 0) void stream2.updateLiveCard(channel, queueCard({ position: currentPos, cardKey: key }));
|
|
13872
|
+
});
|
|
13873
|
+
}
|
|
13387
13874
|
} catch (err) {
|
|
13388
13875
|
log.fail("card", err, { phase: "queued-card" });
|
|
13389
13876
|
}
|
|
@@ -13396,7 +13883,8 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13396
13883
|
if (msgId) {
|
|
13397
13884
|
runsByCard.delete(msgId);
|
|
13398
13885
|
runStreams.delete(msgId);
|
|
13399
|
-
|
|
13886
|
+
completionReminderRefreshers.delete(msgId);
|
|
13887
|
+
void stream2.updateCard(channel, queueCard({ cancelled: true, dropped: state.queue.length }));
|
|
13400
13888
|
}
|
|
13401
13889
|
reaction?.done();
|
|
13402
13890
|
log.info("card", "action", { actionId: "run.stop", queuedCancel: true });
|
|
@@ -13451,10 +13939,17 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13451
13939
|
let intake = opts.timing;
|
|
13452
13940
|
let firstRec = opts.firstRec;
|
|
13453
13941
|
try {
|
|
13454
|
-
let
|
|
13942
|
+
let currentTurn = {
|
|
13943
|
+
input: { text: opts.firstText, images: opts.images },
|
|
13944
|
+
requesterOpenId: opts.requesterOpenId,
|
|
13945
|
+
requestedAt: opts.requestedAt ?? Date.now(),
|
|
13946
|
+
summary: opts.summary
|
|
13947
|
+
};
|
|
13455
13948
|
let replyTo = opts.replyTo;
|
|
13456
13949
|
let replyInThread = opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId);
|
|
13457
13950
|
for (; ; ) {
|
|
13951
|
+
const turnInput = currentTurn.input;
|
|
13952
|
+
state.requesterOpenId = currentTurn.requesterOpenId;
|
|
13458
13953
|
const rec = firstRec !== void 0 ? firstRec ?? void 0 : topicThreadId ? await getSession(topicThreadId) : void 0;
|
|
13459
13954
|
firstRec = void 0;
|
|
13460
13955
|
const turnModel = rec?.model ?? opts.model;
|
|
@@ -13468,8 +13963,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13468
13963
|
let cardMsgId;
|
|
13469
13964
|
const rc = {
|
|
13470
13965
|
rs: render.snapshot(),
|
|
13471
|
-
requesterOpenId:
|
|
13966
|
+
requesterOpenId: currentTurn.requesterOpenId,
|
|
13472
13967
|
showTools: render.showTools,
|
|
13968
|
+
completionReminder: completionReminderView(state),
|
|
13473
13969
|
// 模型显示档位:footnote 本轮 model·推理强度;always 档终态卡也保留。
|
|
13474
13970
|
...modelDisp !== "off" && turnModel ? { model: turnModel, effort: turnEffort, modelOnTerminal: modelDisp === "always" } : {}
|
|
13475
13971
|
};
|
|
@@ -13513,6 +14009,11 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13513
14009
|
rc.cardKey = cardMsgId;
|
|
13514
14010
|
runsByCard.set(cardMsgId, state);
|
|
13515
14011
|
runStreams.set(cardMsgId, stream2);
|
|
14012
|
+
completionReminderRefreshers.set(cardMsgId, () => {
|
|
14013
|
+
rc.completionReminder = completionReminderView(state);
|
|
14014
|
+
void stream2.updateLiveCard(channel, buildRunCard(rc));
|
|
14015
|
+
});
|
|
14016
|
+
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
13516
14017
|
await adoptThreadId(cardMsgId);
|
|
13517
14018
|
if (!firstCardSent) {
|
|
13518
14019
|
firstCardSent = true;
|
|
@@ -13570,6 +14071,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13570
14071
|
}
|
|
13571
14072
|
render.apply(ev);
|
|
13572
14073
|
rc.rs = render.snapshot();
|
|
14074
|
+
rc.completionReminder = completionReminderView(state);
|
|
13573
14075
|
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
13574
14076
|
}
|
|
13575
14077
|
const doneAt = Date.now();
|
|
@@ -13578,12 +14080,15 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13578
14080
|
state.interrupt = void 0;
|
|
13579
14081
|
const interrupted = stopper.interrupted();
|
|
13580
14082
|
const killed = timedOut || interrupted && stopper.forced();
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
14083
|
+
const procDead = !killed && !opts.thread.isAlive();
|
|
14084
|
+
settleOrdinaryTurnRender(render, {
|
|
14085
|
+
interrupted,
|
|
14086
|
+
timedOut,
|
|
14087
|
+
idleTimeoutSeconds: Math.round(idleMs / 1e3),
|
|
14088
|
+
procDead
|
|
14089
|
+
});
|
|
13584
14090
|
rc.rs = render.snapshot();
|
|
13585
14091
|
if (interrupted) log.info("agent", "interrupt", { graceful: !stopper.forced(), threadId: topicThreadId ?? null });
|
|
13586
|
-
const procDead = !killed && !opts.thread.isAlive();
|
|
13587
14092
|
if (killed || procDead) {
|
|
13588
14093
|
void opts.thread.close().catch(() => void 0);
|
|
13589
14094
|
if (topicThreadId) sessions.delete(topicThreadId);
|
|
@@ -13601,7 +14106,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13601
14106
|
if (imgSources.length > 0) {
|
|
13602
14107
|
rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
|
|
13603
14108
|
}
|
|
13604
|
-
|
|
14109
|
+
const manuallyRequested = Boolean(state.completionReminderRequested);
|
|
14110
|
+
completionReminderRefreshers.delete(cardMsgId);
|
|
14111
|
+
const terminalCardUpdated = await stream2.finalizeCard(channel, buildRunCard(rc));
|
|
13605
14112
|
{
|
|
13606
14113
|
const terminalAt = Date.now();
|
|
13607
14114
|
const st = stream2.stats();
|
|
@@ -13641,6 +14148,16 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13641
14148
|
touchSession(topicThreadId);
|
|
13642
14149
|
await patchSession(topicThreadId, { updatedAt: Date.now() });
|
|
13643
14150
|
}
|
|
14151
|
+
await sendCompletionReminder({
|
|
14152
|
+
cardMsgId: finalMsgId,
|
|
14153
|
+
requesterOpenId: currentTurn.requesterOpenId,
|
|
14154
|
+
outcome: rc.rs.terminal === "running" ? "done" : rc.rs.terminal,
|
|
14155
|
+
requestedAt: currentTurn.requestedAt,
|
|
14156
|
+
manuallyRequested,
|
|
14157
|
+
summary: currentTurn.summary,
|
|
14158
|
+
cardUpdated: terminalCardUpdated,
|
|
14159
|
+
replyInThread: !opts.flat
|
|
14160
|
+
});
|
|
13644
14161
|
replyTo = finalMsgId;
|
|
13645
14162
|
replyInThread = !opts.flat;
|
|
13646
14163
|
log.info("card", "final", { terminal: render.terminal() });
|
|
@@ -13656,14 +14173,18 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13656
14173
|
break;
|
|
13657
14174
|
}
|
|
13658
14175
|
if (state.queue.length === 0) break;
|
|
13659
|
-
|
|
14176
|
+
currentTurn = state.queue.shift();
|
|
14177
|
+
activateQueuedTurn(state, currentTurn);
|
|
13660
14178
|
}
|
|
13661
14179
|
} catch (err) {
|
|
13662
14180
|
log.fail("intake", err);
|
|
13663
14181
|
await channel.send(opts.chatId, { markdown: `\u274C ${err instanceof Error ? err.message : String(err)}` }, { replyTo: opts.replyTo, replyInThread: !opts.flat }).catch(() => void 0);
|
|
13664
14182
|
} finally {
|
|
13665
14183
|
active.delete(activeKey);
|
|
13666
|
-
if (curCardKey)
|
|
14184
|
+
if (curCardKey) {
|
|
14185
|
+
runsByCard.delete(curCardKey);
|
|
14186
|
+
completionReminderRefreshers.delete(curCardKey);
|
|
14187
|
+
}
|
|
13667
14188
|
if (activeKey.startsWith("pending:")) {
|
|
13668
14189
|
void opts.thread.close().catch(() => void 0);
|
|
13669
14190
|
log.warn("intake", "unadopted-thread-closed", { activeKey });
|
|
@@ -14242,7 +14763,11 @@ ${facts}`;
|
|
|
14242
14763
|
log.info("bridge", "shutdown", { closed: live.length });
|
|
14243
14764
|
}
|
|
14244
14765
|
void backend.listModels().catch((err) => log.fail("agent", err, { phase: "models-prewarm" }));
|
|
14245
|
-
const
|
|
14766
|
+
const executeAdminWrite = createAdminWriteExecutor({ cfg, backendFor, evictLiveSessionsForChat, writePreferences });
|
|
14767
|
+
const adminExecute = async (op) => {
|
|
14768
|
+
await executeAdminWrite(op);
|
|
14769
|
+
if (op.kind === "setCompletionReminder") refreshCompletionReminderCards();
|
|
14770
|
+
};
|
|
14246
14771
|
return { onMessage, onComment, onBotAddedToChat, onBotRemovedFromChat, onReaction, onBotMenu, dispatcher, adminExecute, shutdown };
|
|
14247
14772
|
}
|
|
14248
14773
|
async function getThreadId(channel, messageId, attempts = 1) {
|
|
@@ -14445,7 +14970,6 @@ init_paths();
|
|
|
14445
14970
|
import { readFile as readFile14, rm as rm7 } from "fs/promises";
|
|
14446
14971
|
init_schema();
|
|
14447
14972
|
init_store();
|
|
14448
|
-
init_schema();
|
|
14449
14973
|
|
|
14450
14974
|
// src/bot/register-bot.ts
|
|
14451
14975
|
init_store();
|
|
@@ -14628,6 +15152,13 @@ function createAdminService(deps = {}) {
|
|
|
14628
15152
|
const live = await deps.liveStatus?.(botId).catch(() => void 0);
|
|
14629
15153
|
return live ?? lockFileRunState(botId);
|
|
14630
15154
|
}
|
|
15155
|
+
async function completionReminderFor(botId) {
|
|
15156
|
+
try {
|
|
15157
|
+
return getCompletionReminderConfig(await loadConfig(botPaths(botId).configFile));
|
|
15158
|
+
} catch {
|
|
15159
|
+
return getCompletionReminderConfig({});
|
|
15160
|
+
}
|
|
15161
|
+
}
|
|
14631
15162
|
function executeWrite(botId, action, op) {
|
|
14632
15163
|
if (!deps.executeWrite) throw new NotWiredYetError(action);
|
|
14633
15164
|
return deps.executeWrite(botId, op);
|
|
@@ -14638,7 +15169,7 @@ function createAdminService(deps = {}) {
|
|
|
14638
15169
|
const configured = reg.bots.some((b) => b.active !== void 0);
|
|
14639
15170
|
const out = [];
|
|
14640
15171
|
for (const b of reg.bots) {
|
|
14641
|
-
const run = await runState(b.appId);
|
|
15172
|
+
const [run, completionReminder] = await Promise.all([runState(b.appId), completionReminderFor(b.appId)]);
|
|
14642
15173
|
out.push({
|
|
14643
15174
|
name: b.name,
|
|
14644
15175
|
appId: b.appId,
|
|
@@ -14650,7 +15181,8 @@ function createAdminService(deps = {}) {
|
|
|
14650
15181
|
running: run.running,
|
|
14651
15182
|
pid: run.pid,
|
|
14652
15183
|
startedAt: run.startedAt,
|
|
14653
|
-
connection: run.connection
|
|
15184
|
+
connection: run.connection,
|
|
15185
|
+
completionReminder
|
|
14654
15186
|
});
|
|
14655
15187
|
}
|
|
14656
15188
|
return out;
|
|
@@ -14679,6 +15211,13 @@ function createAdminService(deps = {}) {
|
|
|
14679
15211
|
async setAutoCompact(botId, projectName, on) {
|
|
14680
15212
|
await executeWrite(botId, "\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u5F00\u5173", { kind: "setAutoCompact", project: projectName, on });
|
|
14681
15213
|
},
|
|
15214
|
+
async setCompletionReminder(botId, value) {
|
|
15215
|
+
await executeWrite(botId, "\u{1F514} \u5B8C\u6210\u63D0\u9192", {
|
|
15216
|
+
kind: "setCompletionReminder",
|
|
15217
|
+
mode: value.mode,
|
|
15218
|
+
longTaskMinutes: value.longTaskMinutes
|
|
15219
|
+
});
|
|
15220
|
+
},
|
|
14682
15221
|
doctorBackends() {
|
|
14683
15222
|
return probeAllBackends();
|
|
14684
15223
|
},
|
|
@@ -15166,6 +15705,7 @@ init_logger();
|
|
|
15166
15705
|
|
|
15167
15706
|
// src/web/server.ts
|
|
15168
15707
|
init_paths();
|
|
15708
|
+
init_schema();
|
|
15169
15709
|
import { createServer } from "http";
|
|
15170
15710
|
import { randomUUID as randomUUID7, timingSafeEqual } from "crypto";
|
|
15171
15711
|
import { mkdirSync as mkdirSync3, watch } from "fs";
|
|
@@ -15750,6 +16290,11 @@ var UI_HTML = `<!doctype html>
|
|
|
15750
16290
|
.drawer-mask.open { display: block; }
|
|
15751
16291
|
.drawer h3 { margin: 0 0 4px; font-size: 16px; }
|
|
15752
16292
|
.opt-row { display: flex; gap: 8px; margin: 6px 0 2px; flex-wrap: wrap; }
|
|
16293
|
+
.compact-input {
|
|
16294
|
+
width: 84px; border: 1px solid var(--border-2); border-radius: 8px; padding: 7px 9px;
|
|
16295
|
+
font: 13px var(--mono); background: #0c0e0c; color: var(--text);
|
|
16296
|
+
}
|
|
16297
|
+
.compact-input:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
15753
16298
|
.backend-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; }
|
|
15754
16299
|
.backend-row .grow { flex: 1; min-width: 0; }
|
|
15755
16300
|
.bk-group { margin: 6px 0 2px; }
|
|
@@ -17226,6 +17771,9 @@ ${UI_PURE_JS}
|
|
|
17226
17771
|
left.appendChild(projCard);
|
|
17227
17772
|
renderProjects(projList, pcount, b);
|
|
17228
17773
|
|
|
17774
|
+
// \u{1F514} \u666E\u901A\u4EFB\u52A1\u7ED3\u675F\u63D0\u9192\uFF08\u6BCF bot \u72EC\u7ACB\uFF09\u3002\u9009\u62E9\u5373\u4FDD\u5B58\uFF1B\u4EC5 long \u989D\u5916\u5C55\u793A\u5206\u949F\u9608\u503C\u3002
|
|
17775
|
+
renderCompletionReminderCard(right, b);
|
|
17776
|
+
|
|
17229
17777
|
cols.appendChild(left);
|
|
17230
17778
|
cols.appendChild(right);
|
|
17231
17779
|
root.appendChild(cols);
|
|
@@ -17297,6 +17845,58 @@ ${UI_PURE_JS}
|
|
|
17297
17845
|
});
|
|
17298
17846
|
}
|
|
17299
17847
|
|
|
17848
|
+
function renderCompletionReminderCard(root, b) {
|
|
17849
|
+
var reminder = b.completionReminder || { mode: 'failures', longTaskMinutes: 3 };
|
|
17850
|
+
var card = el('div', 'card');
|
|
17851
|
+
card.appendChild(el('h2', null, '\u{1F514} \u5B8C\u6210\u63D0\u9192'));
|
|
17852
|
+
card.appendChild(el('div', 'note', '\u4EFB\u52A1\u7ED3\u675F\u540E\u662F\u5426\u53E6\u53D1\u4E00\u6761\u6D88\u606F @ \u53D1\u8D77\u4EBA\u3002\u6BCF\u4E2A\u673A\u5668\u4EBA\u72EC\u7ACB\u8BBE\u7F6E\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\u3002'));
|
|
17853
|
+
var modes = [
|
|
17854
|
+
{ label: '\u4EC5\u624B\u52A8', value: 'manual' },
|
|
17855
|
+
{ label: '\u957F\u4EFB\u52A1', value: 'long' },
|
|
17856
|
+
{ label: '\u5931\u8D25\u6216\u8D85\u65F6', value: 'failures' },
|
|
17857
|
+
{ label: '\u6BCF\u6B21\u7ED3\u675F', value: 'always' },
|
|
17858
|
+
];
|
|
17859
|
+
card.appendChild(optButtons(modes, reminder.mode, function (mode) {
|
|
17860
|
+
postWrite('/api/bots/' + encodeURIComponent(b.appId) + '/completion-reminder', {
|
|
17861
|
+
mode: mode,
|
|
17862
|
+
longTaskMinutes: reminder.longTaskMinutes,
|
|
17863
|
+
});
|
|
17864
|
+
}));
|
|
17865
|
+
|
|
17866
|
+
var descriptions = {
|
|
17867
|
+
manual: '\u8FD0\u884C\u5361\u663E\u793A\u300C\u5B8C\u6210\u540E\u63D0\u9192\u6211\u300D\uFF0C\u53EA\u6709\u672C\u8F6E\u53D1\u8D77\u4EBA\u70B9\u8FC7\u624D\u901A\u77E5\u3002',
|
|
17868
|
+
long: '\u4EFB\u52A1\u8017\u65F6\u8FBE\u5230\u9608\u503C\u540E\u901A\u77E5\uFF1B\u8FD0\u884C\u5361\u4E0D\u663E\u793A\u63D0\u9192\u6309\u94AE\u3002',
|
|
17869
|
+
failures: '\u4EC5\u4EFB\u52A1\u5931\u8D25\u6216\u5047\u6B7B\u8D85\u65F6\u65F6\u901A\u77E5\uFF1B\u8FD0\u884C\u5361\u4E0D\u663E\u793A\u63D0\u9192\u6309\u94AE\u3002',
|
|
17870
|
+
always: '\u6BCF\u6B21\u6B63\u5E38\u7ED3\u675F\u3001\u5931\u8D25\u6216\u8D85\u65F6\u90FD\u901A\u77E5\uFF1B\u8FD0\u884C\u5361\u4E0D\u663E\u793A\u63D0\u9192\u6309\u94AE\u3002',
|
|
17871
|
+
};
|
|
17872
|
+
card.appendChild(el('div', 'note', descriptions[reminder.mode] || descriptions.failures));
|
|
17873
|
+
|
|
17874
|
+
if (reminder.mode === 'long') {
|
|
17875
|
+
var threshold = el('div', 'statline');
|
|
17876
|
+
threshold.appendChild(el('span', null, '\u957F\u4EFB\u52A1\u9608\u503C'));
|
|
17877
|
+
var input = el('input', 'compact-input');
|
|
17878
|
+
input.type = 'number'; input.min = '1'; input.max = '1440'; input.step = '1';
|
|
17879
|
+
input.value = String(reminder.longTaskMinutes);
|
|
17880
|
+
input.setAttribute('aria-label', '\u957F\u4EFB\u52A1\u9608\u503C\uFF08\u5206\u949F\uFF09');
|
|
17881
|
+
threshold.appendChild(input);
|
|
17882
|
+
threshold.appendChild(el('span', 'note', '\u5206\u949F\uFF081\u20131440\uFF09'));
|
|
17883
|
+
var save = el('button', 'btn primary sm', '\u4FDD\u5B58\u9608\u503C');
|
|
17884
|
+
save.onclick = function () {
|
|
17885
|
+
var minutes = Number(input.value);
|
|
17886
|
+
if (!Number.isInteger(minutes) || minutes < 1 || minutes > 1440) {
|
|
17887
|
+
toast('\u274C \u957F\u4EFB\u52A1\u9608\u503C\u8BF7\u8F93\u5165 1\u20131440 \u7684\u6574\u6570\u5206\u949F');
|
|
17888
|
+
return;
|
|
17889
|
+
}
|
|
17890
|
+
postWrite('/api/bots/' + encodeURIComponent(b.appId) + '/completion-reminder', {
|
|
17891
|
+
mode: 'long', longTaskMinutes: minutes,
|
|
17892
|
+
});
|
|
17893
|
+
};
|
|
17894
|
+
threshold.appendChild(save);
|
|
17895
|
+
card.appendChild(threshold);
|
|
17896
|
+
}
|
|
17897
|
+
root.appendChild(card);
|
|
17898
|
+
}
|
|
17899
|
+
|
|
17300
17900
|
function renderProjects(box, countEl, b) {
|
|
17301
17901
|
box.textContent = '';
|
|
17302
17902
|
var projects = (b && b.projects) || [];
|
|
@@ -17917,6 +18517,7 @@ var LOGO_PNG = Buffer.from(LOGO_PNG_BASE64, "base64");
|
|
|
17917
18517
|
var DEFAULT_WEB_PORT = 51847;
|
|
17918
18518
|
var COOKIE_NAME = "fcb_console_token";
|
|
17919
18519
|
var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
|
|
18520
|
+
var COMPLETION_REMINDER_MODES2 = ["manual", "long", "failures", "always"];
|
|
17920
18521
|
function createWebServer(opts) {
|
|
17921
18522
|
const token = opts.token ?? randomUUID7();
|
|
17922
18523
|
const html = opts.html ?? UI_HTML;
|
|
@@ -18092,6 +18693,45 @@ function createWebServer(opts) {
|
|
|
18092
18693
|
sendJson(res, 200, status);
|
|
18093
18694
|
return;
|
|
18094
18695
|
}
|
|
18696
|
+
const completionReminderMatch = /^\/api\/bots\/([^/]+)\/completion-reminder$/.exec(pathName);
|
|
18697
|
+
if (req.method === "POST" && completionReminderMatch) {
|
|
18698
|
+
let body;
|
|
18699
|
+
try {
|
|
18700
|
+
body = await readJsonBody(req);
|
|
18701
|
+
} catch {
|
|
18702
|
+
sendJson(res, 400, { error: "bad_body", message: "\u8BF7\u6C42\u4F53\u5FC5\u987B\u662F JSON" });
|
|
18703
|
+
return;
|
|
18704
|
+
}
|
|
18705
|
+
const mode = body.mode;
|
|
18706
|
+
const minutes = body.longTaskMinutes;
|
|
18707
|
+
if (!COMPLETION_REMINDER_MODES2.includes(mode)) {
|
|
18708
|
+
sendJson(res, 400, { error: "invalid_input", message: "mode \u5FC5\u987B\u662F manual / long / failures / always" });
|
|
18709
|
+
return;
|
|
18710
|
+
}
|
|
18711
|
+
if (typeof minutes !== "number" || !Number.isInteger(minutes) || minutes < COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES || minutes > COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES) {
|
|
18712
|
+
sendJson(res, 400, {
|
|
18713
|
+
error: "invalid_input",
|
|
18714
|
+
message: `longTaskMinutes \u5FC5\u987B\u662F ${COMPLETION_REMINDER_LONG_TASK_MIN_MINUTES}\u2013${COMPLETION_REMINDER_LONG_TASK_MAX_MINUTES} \u4E4B\u95F4\u7684\u6574\u6570`
|
|
18715
|
+
});
|
|
18716
|
+
return;
|
|
18717
|
+
}
|
|
18718
|
+
try {
|
|
18719
|
+
await opts.service.setCompletionReminder(decodeURIComponent(completionReminderMatch[1]), {
|
|
18720
|
+
mode,
|
|
18721
|
+
longTaskMinutes: minutes
|
|
18722
|
+
});
|
|
18723
|
+
sendJson(res, 200, { ok: true, completionReminder: { mode, longTaskMinutes: minutes } });
|
|
18724
|
+
} catch (err) {
|
|
18725
|
+
if (err instanceof NotWiredYetError) {
|
|
18726
|
+
sendJson(res, 501, { error: "not_wired_yet", message: err.message });
|
|
18727
|
+
} else if (err instanceof AdminWriteError) {
|
|
18728
|
+
sendJson(res, 409, { error: "write_rejected", message: err.message });
|
|
18729
|
+
} else {
|
|
18730
|
+
throw err;
|
|
18731
|
+
}
|
|
18732
|
+
}
|
|
18733
|
+
return;
|
|
18734
|
+
}
|
|
18095
18735
|
const botMatch = /^\/api\/bots\/([^/]+)$/.exec(pathName);
|
|
18096
18736
|
if (req.method === "PATCH" && botMatch) {
|
|
18097
18737
|
const appId = decodeURIComponent(botMatch[1]);
|