@modelzen/feishu-codex-bridge 0.6.7 → 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 +810 -126
- 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
|
}
|
|
@@ -4111,6 +4144,7 @@ ${b.thinking}` : b.thinking;
|
|
|
4111
4144
|
|
|
4112
4145
|
// src/agent/claude-agent/thread.ts
|
|
4113
4146
|
init_logger();
|
|
4147
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
4114
4148
|
var COMPACT_TIMEOUT_MS2 = 12e4;
|
|
4115
4149
|
var ABORT_ESCALATE_MS = 4e3;
|
|
4116
4150
|
var Inbox = class {
|
|
@@ -4158,15 +4192,53 @@ var PushablePrompt = class {
|
|
|
4158
4192
|
function toSdkEffort(e) {
|
|
4159
4193
|
if (!e) return void 0;
|
|
4160
4194
|
if (e === "none" || e === "minimal") return "low";
|
|
4195
|
+
if (e === "ultra") return "max";
|
|
4161
4196
|
return e;
|
|
4162
4197
|
}
|
|
4163
|
-
|
|
4198
|
+
var MAX_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
4199
|
+
function sniffImageType(b) {
|
|
4200
|
+
if (b.length >= 8 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) return "image/png";
|
|
4201
|
+
if (b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255) return "image/jpeg";
|
|
4202
|
+
if (b.length >= 6 && b[0] === 71 && b[1] === 73 && b[2] === 70 && b[3] === 56) return "image/gif";
|
|
4203
|
+
if (b.length >= 12 && b.toString("ascii", 0, 4) === "RIFF" && b.toString("ascii", 8, 12) === "WEBP") return "image/webp";
|
|
4204
|
+
return void 0;
|
|
4205
|
+
}
|
|
4206
|
+
async function toImageBlock(path) {
|
|
4207
|
+
let bytes;
|
|
4208
|
+
try {
|
|
4209
|
+
bytes = await readFile5(path);
|
|
4210
|
+
} catch (err) {
|
|
4211
|
+
log.warn("agent", "image-read-failed", { backend: "claude-agent", err: String(err) });
|
|
4212
|
+
return void 0;
|
|
4213
|
+
}
|
|
4214
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES) {
|
|
4215
|
+
log.warn("agent", "image-skip-size", { backend: "claude-agent", bytes: bytes.byteLength });
|
|
4216
|
+
return void 0;
|
|
4217
|
+
}
|
|
4218
|
+
const mediaType = sniffImageType(bytes);
|
|
4219
|
+
if (!mediaType) {
|
|
4220
|
+
log.info("agent", "image-skip-unsupported", { backend: "claude-agent", head: bytes.toString("hex", 0, 4) });
|
|
4221
|
+
return void 0;
|
|
4222
|
+
}
|
|
4223
|
+
return { type: "image", source: { type: "base64", media_type: mediaType, data: bytes.toString("base64") } };
|
|
4224
|
+
}
|
|
4225
|
+
function textUserMessage(text) {
|
|
4226
|
+
return { type: "user", message: { role: "user", content: text }, parent_tool_use_id: null };
|
|
4227
|
+
}
|
|
4228
|
+
async function toUserMessage(input2) {
|
|
4164
4229
|
const text = input2.text ?? "";
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4230
|
+
const paths2 = input2.images ?? [];
|
|
4231
|
+
if (paths2.length === 0) return textUserMessage(text);
|
|
4232
|
+
const blocks = [];
|
|
4233
|
+
if (text) blocks.push({ type: "text", text });
|
|
4234
|
+
for (const path of paths2) {
|
|
4235
|
+
const block = await toImageBlock(path);
|
|
4236
|
+
if (block) blocks.push(block);
|
|
4237
|
+
}
|
|
4238
|
+
const imageCount = blocks.filter((b) => b.type === "image").length;
|
|
4239
|
+
if (imageCount === 0) return textUserMessage(text);
|
|
4240
|
+
log.info("agent", "images-attached", { backend: "claude-agent", count: imageCount, of: paths2.length });
|
|
4241
|
+
return { type: "user", message: { role: "user", content: blocks }, parent_tool_use_id: null };
|
|
4170
4242
|
}
|
|
4171
4243
|
function goalPrompt(objective) {
|
|
4172
4244
|
return [
|
|
@@ -4253,7 +4325,13 @@ var ClaudeAgentThread = class {
|
|
|
4253
4325
|
const inbox = new Inbox();
|
|
4254
4326
|
const mySink = (item) => inbox.push(item);
|
|
4255
4327
|
this.sink = mySink;
|
|
4256
|
-
|
|
4328
|
+
void toUserMessage(input2).then(
|
|
4329
|
+
(m) => this.input.push(m),
|
|
4330
|
+
(err) => {
|
|
4331
|
+
log.fail("agent", err, { backend: "claude-agent", phase: "build-user-message" });
|
|
4332
|
+
this.input.push(textUserMessage(input2.text ?? ""));
|
|
4333
|
+
}
|
|
4334
|
+
);
|
|
4257
4335
|
const self = this;
|
|
4258
4336
|
async function* gen() {
|
|
4259
4337
|
yield { type: "turn_started", turnId };
|
|
@@ -4334,7 +4412,7 @@ var ClaudeAgentThread = class {
|
|
|
4334
4412
|
const inbox = new Inbox();
|
|
4335
4413
|
const mySink = (item) => inbox.push(item);
|
|
4336
4414
|
this.sink = mySink;
|
|
4337
|
-
this.input.push(
|
|
4415
|
+
this.input.push(textUserMessage(goalPrompt(objective)));
|
|
4338
4416
|
const self = this;
|
|
4339
4417
|
async function* gen() {
|
|
4340
4418
|
yield { type: "goal_update", status: "active", objective, tokensUsed: 0, timeUsedSeconds: 0, tokenBudget: null };
|
|
@@ -4430,7 +4508,7 @@ var ClaudeAgentThread = class {
|
|
|
4430
4508
|
});
|
|
4431
4509
|
let compacted = false;
|
|
4432
4510
|
try {
|
|
4433
|
-
this.input.push(
|
|
4511
|
+
this.input.push(textUserMessage("/compact"));
|
|
4434
4512
|
while (true) {
|
|
4435
4513
|
const step = await Promise.race([inbox.next(), timeout]);
|
|
4436
4514
|
if (step === "timeout") throw new Error(`\u538B\u7F29\u8D85\u65F6\uFF08Claude \u672A\u5728 ${COMPACT_TIMEOUT_MS2 / 1e3}s \u5185\u5B8C\u6210\uFF09`);
|
|
@@ -4683,7 +4761,7 @@ async function effectiveDefaultBackend(opts) {
|
|
|
4683
4761
|
}
|
|
4684
4762
|
|
|
4685
4763
|
// src/agent/installer.ts
|
|
4686
|
-
import { mkdir as mkdir4, rm as rm2, readFile as
|
|
4764
|
+
import { mkdir as mkdir4, rm as rm2, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
|
|
4687
4765
|
import { existsSync as existsSync4 } from "fs";
|
|
4688
4766
|
import { join as join8 } from "path";
|
|
4689
4767
|
init_paths();
|
|
@@ -4872,7 +4950,7 @@ async function rollback(bareName) {
|
|
|
4872
4950
|
async function removeBackendsDep(bareName) {
|
|
4873
4951
|
const pkgFile = join8(paths.backendsDir, "package.json");
|
|
4874
4952
|
try {
|
|
4875
|
-
const raw = await
|
|
4953
|
+
const raw = await readFile6(pkgFile, "utf8");
|
|
4876
4954
|
const json = JSON.parse(raw);
|
|
4877
4955
|
if (json.dependencies && bareName in json.dependencies) {
|
|
4878
4956
|
delete json.dependencies[bareName];
|
|
@@ -5371,7 +5449,7 @@ init_schema();
|
|
|
5371
5449
|
|
|
5372
5450
|
// src/project/registry.ts
|
|
5373
5451
|
init_paths();
|
|
5374
|
-
import { mkdir as mkdir5, readFile as
|
|
5452
|
+
import { mkdir as mkdir5, readFile as readFile7, rename as rename4, writeFile as writeFile5 } from "fs/promises";
|
|
5375
5453
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
5376
5454
|
import { dirname as dirname5 } from "path";
|
|
5377
5455
|
function defaultNoMention(p) {
|
|
@@ -5398,7 +5476,7 @@ async function read() {
|
|
|
5398
5476
|
}
|
|
5399
5477
|
async function listProjectsIn(file) {
|
|
5400
5478
|
try {
|
|
5401
|
-
const text = await
|
|
5479
|
+
const text = await readFile7(file, "utf8");
|
|
5402
5480
|
const parsed = JSON.parse(text);
|
|
5403
5481
|
return Array.isArray(parsed.projects) ? parsed.projects : [];
|
|
5404
5482
|
} catch (err) {
|
|
@@ -5488,8 +5566,13 @@ var EFFORT_LABEL = {
|
|
|
5488
5566
|
low: "\u4F4E",
|
|
5489
5567
|
medium: "\u4E2D",
|
|
5490
5568
|
high: "\u9AD8",
|
|
5491
|
-
xhigh: "\u6781\u9AD8"
|
|
5569
|
+
xhigh: "\u6781\u9AD8",
|
|
5570
|
+
max: "\u6700\u9AD8",
|
|
5571
|
+
ultra: "\u8D85\u5F3A"
|
|
5492
5572
|
};
|
|
5573
|
+
function reasoningEffortLabel(effort) {
|
|
5574
|
+
return EFFORT_LABEL[effort] ?? (effort || "\u672A\u77E5");
|
|
5575
|
+
}
|
|
5493
5576
|
function buildModelCard(state) {
|
|
5494
5577
|
const visible = state.models.filter((m) => !m.hidden);
|
|
5495
5578
|
const cur = state.models.find((m) => m.id === state.model);
|
|
@@ -5517,7 +5600,7 @@ function buildModelCard(state) {
|
|
|
5517
5600
|
actionId: MC.effort,
|
|
5518
5601
|
placeholder: "effort",
|
|
5519
5602
|
initial: state.effort,
|
|
5520
|
-
options: efforts.map((e) => ({ label: `effort\uFF1A${
|
|
5603
|
+
options: efforts.map((e) => ({ label: `effort\uFF1A${reasoningEffortLabel(e)}`, value: e }))
|
|
5521
5604
|
})
|
|
5522
5605
|
);
|
|
5523
5606
|
}
|
|
@@ -5735,6 +5818,10 @@ var DM = {
|
|
|
5735
5818
|
// 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
|
|
5736
5819
|
watchdogCustom: "dm.set.watchdog.custom",
|
|
5737
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",
|
|
5738
5825
|
setPending: "dm.set.pending",
|
|
5739
5826
|
setConcurrency: "dm.set.concurrency",
|
|
5740
5827
|
// 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
|
|
@@ -6270,6 +6357,7 @@ function settingItem(name, desc, actionId, current, opts) {
|
|
|
6270
6357
|
}
|
|
6271
6358
|
function buildSettingsCard(cfg) {
|
|
6272
6359
|
const watchdogSec = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
|
|
6360
|
+
const completionReminder = getCompletionReminderConfig(cfg);
|
|
6273
6361
|
return card(
|
|
6274
6362
|
[
|
|
6275
6363
|
settingSection("\u{1F4E4} \u8F93\u51FA\u5C55\u793A"),
|
|
@@ -6304,6 +6392,23 @@ function buildSettingsCard(cfg) {
|
|
|
6304
6392
|
),
|
|
6305
6393
|
button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
|
|
6306
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
|
+
] : [],
|
|
6307
6412
|
...settingItem(
|
|
6308
6413
|
"\u{1F4E5} \u8FD0\u884C\u4E2D\u6765\u65B0\u6D88\u606F",
|
|
6309
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",
|
|
@@ -6401,7 +6506,7 @@ function buildCommentSettingsCard(cfg, backendOptions, models, notice) {
|
|
|
6401
6506
|
selectMenu({
|
|
6402
6507
|
name: "effort",
|
|
6403
6508
|
placeholder: "\u9009\u62E9\u63A8\u7406\u5F3A\u5EA6",
|
|
6404
|
-
options: unionEfforts.map((e) => ({ label:
|
|
6509
|
+
options: unionEfforts.map((e) => ({ label: reasoningEffortLabel(e), value: e })),
|
|
6405
6510
|
initial: curEffort
|
|
6406
6511
|
})
|
|
6407
6512
|
);
|
|
@@ -6506,6 +6611,25 @@ function buildWatchdogCustomCard(cfg) {
|
|
|
6506
6611
|
{ header: { title: "\u23F1 \u81EA\u5B9A\u4E49\u8D85\u65F6", template: "blue" } }
|
|
6507
6612
|
);
|
|
6508
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
|
+
}
|
|
6509
6633
|
function buildGroupSettingsCard(project) {
|
|
6510
6634
|
const kind = project.kind ?? "multi";
|
|
6511
6635
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
@@ -6644,10 +6768,10 @@ function buildPermissionCard(p) {
|
|
|
6644
6768
|
{ header: { title: "\u{1F510} \u6743\u9650", template: "blue" } }
|
|
6645
6769
|
);
|
|
6646
6770
|
}
|
|
6647
|
-
var EFFORT_ORDER =
|
|
6771
|
+
var EFFORT_ORDER = REASONING_EFFORTS;
|
|
6648
6772
|
function modelDefaultSummary(p) {
|
|
6649
6773
|
if (!p.defaultModel) return "\u540E\u7AEF\u9ED8\u8BA4\uFF08\u672A\u8BBE\uFF09";
|
|
6650
|
-
const eff = p.defaultEffort ? ` \xB7 \u5F3A\u5EA6 ${
|
|
6774
|
+
const eff = p.defaultEffort ? ` \xB7 \u5F3A\u5EA6 ${reasoningEffortLabel(p.defaultEffort)}` : "";
|
|
6651
6775
|
return `${p.defaultModel}${eff}`;
|
|
6652
6776
|
}
|
|
6653
6777
|
function buildModelDefaultCard(p, models, ctx, notice) {
|
|
@@ -6698,7 +6822,7 @@ function buildModelDefaultCard(p, models, ctx, notice) {
|
|
|
6698
6822
|
selectMenu({
|
|
6699
6823
|
name: "effort",
|
|
6700
6824
|
placeholder: "\u9009\u62E9\u9ED8\u8BA4\u63A8\u7406\u5F3A\u5EA6",
|
|
6701
|
-
options: unionEfforts.map((e) => ({ label: `\u5F3A\u5EA6\uFF1A${
|
|
6825
|
+
options: unionEfforts.map((e) => ({ label: `\u5F3A\u5EA6\uFF1A${reasoningEffortLabel(e)}`, value: e })),
|
|
6702
6826
|
initial: curEffort
|
|
6703
6827
|
})
|
|
6704
6828
|
);
|
|
@@ -6822,6 +6946,8 @@ function buildAddAllowedCard(projectName, members) {
|
|
|
6822
6946
|
}
|
|
6823
6947
|
|
|
6824
6948
|
// src/admin/ops.ts
|
|
6949
|
+
init_schema();
|
|
6950
|
+
init_store();
|
|
6825
6951
|
var AdminWriteError = class extends Error {
|
|
6826
6952
|
code = "ADMIN_WRITE_REJECTED";
|
|
6827
6953
|
constructor(reason) {
|
|
@@ -6829,7 +6955,26 @@ var AdminWriteError = class extends Error {
|
|
|
6829
6955
|
this.name = "AdminWriteError";
|
|
6830
6956
|
}
|
|
6831
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
|
+
}
|
|
6832
6976
|
var TIERS = ["qa", "write", "full"];
|
|
6977
|
+
var COMPLETION_REMINDER_MODES = ["manual", "long", "failures", "always"];
|
|
6833
6978
|
function validateBackendSwitch(opts) {
|
|
6834
6979
|
if (!opts.registered.includes(opts.target)) {
|
|
6835
6980
|
return `\u672A\u77E5\u540E\u7AEF\u300C${opts.target}\u300D\uFF08\u53EF\u7528\uFF1A${opts.registered.join("\u3001")}\uFF09`;
|
|
@@ -6928,9 +7073,30 @@ async function performSetModelDefault(opts) {
|
|
|
6928
7073
|
await updateProject(opts.projectName, { defaultModel: opts.model, defaultEffort: opts.effort });
|
|
6929
7074
|
return { ok: true, project: await freshOr(opts.projectName, { ...p, defaultModel: opts.model, defaultEffort: opts.effort }) };
|
|
6930
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
|
+
}
|
|
6931
7096
|
function createAdminWriteExecutor(deps) {
|
|
7097
|
+
const writePreferences = deps.writePreferences ?? (deps.cfg ? createAppPreferencesWriter({ cfg: deps.cfg, persistConfig: deps.persistConfig }) : void 0);
|
|
6932
7098
|
return async (op) => {
|
|
6933
|
-
const outcome = await runAdminWriteOp(op, deps);
|
|
7099
|
+
const outcome = await runAdminWriteOp(op, { ...deps, writePreferences });
|
|
6934
7100
|
if (!outcome.ok) throw new AdminWriteError(outcome.reason);
|
|
6935
7101
|
};
|
|
6936
7102
|
}
|
|
@@ -6954,12 +7120,20 @@ async function runAdminWriteOp(op, deps) {
|
|
|
6954
7120
|
on: op.on,
|
|
6955
7121
|
evictLiveSessionsForChat: deps.evictLiveSessionsForChat
|
|
6956
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
|
+
});
|
|
6957
7132
|
}
|
|
6958
7133
|
}
|
|
6959
7134
|
|
|
6960
7135
|
// src/bot/handle-message.ts
|
|
6961
7136
|
init_schema();
|
|
6962
|
-
init_store();
|
|
6963
7137
|
|
|
6964
7138
|
// src/card/dispatcher.ts
|
|
6965
7139
|
init_logger();
|
|
@@ -7574,6 +7748,8 @@ function gaugeEl(state) {
|
|
|
7574
7748
|
}
|
|
7575
7749
|
var RC = {
|
|
7576
7750
|
stop: "run.stop",
|
|
7751
|
+
/** manual completion-reminder mode: notify this turn's requester when it ends. */
|
|
7752
|
+
remind: "run.remind",
|
|
7577
7753
|
/** goal-only: clear the goal but let the in-flight turn finish (no auto-continue). */
|
|
7578
7754
|
endGoal: "goal.end"
|
|
7579
7755
|
};
|
|
@@ -7628,8 +7804,16 @@ function renderRunning(state, rc) {
|
|
|
7628
7804
|
)
|
|
7629
7805
|
);
|
|
7630
7806
|
}
|
|
7631
|
-
} else if (rc.cardKey
|
|
7632
|
-
|
|
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));
|
|
7633
7817
|
}
|
|
7634
7818
|
return elements;
|
|
7635
7819
|
}
|
|
@@ -7743,7 +7927,14 @@ function buildQueuedCard(qc) {
|
|
|
7743
7927
|
md(`\u23F3 \u6392\u961F\u4E2D\uFF08\u7B2C **${qc.position ?? 1}** \u4F4D\uFF09`),
|
|
7744
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")
|
|
7745
7929
|
];
|
|
7746
|
-
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
|
+
}
|
|
7747
7938
|
return card(els, { summary: "\u6392\u961F\u4E2D" });
|
|
7748
7939
|
}
|
|
7749
7940
|
function* groupBlocks(blocks) {
|
|
@@ -7825,7 +8016,9 @@ var EFFORT_TIER = {
|
|
|
7825
8016
|
low: { label: "\u4F4E", color: "yellow" },
|
|
7826
8017
|
medium: { label: "\u4E2D", color: "green" },
|
|
7827
8018
|
high: { label: "\u9AD8", color: "violet" },
|
|
7828
|
-
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" }
|
|
7829
8022
|
};
|
|
7830
8023
|
function modelEffortMd(model, effort) {
|
|
7831
8024
|
if (!effort) return model;
|
|
@@ -7970,6 +8163,16 @@ var RunCardStream = class {
|
|
|
7970
8163
|
lastAnswerText = "";
|
|
7971
8164
|
// Per-chat pacer shared with the chat's other streams (set in create()).
|
|
7972
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;
|
|
7973
8176
|
get messageId() {
|
|
7974
8177
|
return this._messageId;
|
|
7975
8178
|
}
|
|
@@ -8161,8 +8364,10 @@ var RunCardStream = class {
|
|
|
8161
8364
|
return false;
|
|
8162
8365
|
}
|
|
8163
8366
|
}
|
|
8164
|
-
/** Forced whole-card replace for structural
|
|
8165
|
-
*
|
|
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.
|
|
8166
8371
|
*
|
|
8167
8372
|
* The terminal frame MUST land — losing it leaves the card "streaming"
|
|
8168
8373
|
* forever (cursor + dead ⏹) while the run is over and `runsByCard` already
|
|
@@ -8170,10 +8375,43 @@ var RunCardStream = class {
|
|
|
8170
8375
|
* with exponential backoff (1s/2s/4s, {@link TERMINAL_RL_RETRIES} retries);
|
|
8171
8376
|
* anything else — typically 200810 "card in ongoing interaction" when the
|
|
8172
8377
|
* update fires inside a ⏹ click's 3s window — waits out the window and
|
|
8173
|
-
* retries once.
|
|
8174
|
-
|
|
8175
|
-
|
|
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);
|
|
8176
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;
|
|
8177
8415
|
const push = async () => {
|
|
8178
8416
|
await channel.rawClient.cardkit.v1.card.update({
|
|
8179
8417
|
path: { card_id: this.cardId },
|
|
@@ -8185,13 +8423,13 @@ var RunCardStream = class {
|
|
|
8185
8423
|
await this.pacer?.wait();
|
|
8186
8424
|
try {
|
|
8187
8425
|
await push();
|
|
8188
|
-
return;
|
|
8426
|
+
return true;
|
|
8189
8427
|
} catch (err) {
|
|
8190
8428
|
const rl = isRateLimited(err);
|
|
8191
8429
|
if (rl) this.pacer?.penalize();
|
|
8192
8430
|
if (i >= (rl ? TERMINAL_RL_RETRIES : 1)) {
|
|
8193
8431
|
log.fail("card", err, { phase: "run-update-retry", cardId: this.cardId, seq: this.seq });
|
|
8194
|
-
return;
|
|
8432
|
+
return false;
|
|
8195
8433
|
}
|
|
8196
8434
|
log.fail("card", err, { phase: "run-update", cardId: this.cardId, seq: this.seq, retry: true, rateLimited: rl });
|
|
8197
8435
|
await new Promise((r) => setTimeout(r, rl ? RL_BACKOFF_BASE_MS * 2 ** i : 3200));
|
|
@@ -8221,7 +8459,7 @@ function structureSig(card2, eid) {
|
|
|
8221
8459
|
|
|
8222
8460
|
// src/card/outbound-images.ts
|
|
8223
8461
|
init_logger();
|
|
8224
|
-
import { readFile as
|
|
8462
|
+
import { readFile as readFile8, stat as stat2 } from "fs/promises";
|
|
8225
8463
|
import { extname as extname2, isAbsolute, resolve as resolve2, sep } from "path";
|
|
8226
8464
|
var MAX_IMAGES = 9;
|
|
8227
8465
|
var MAX_BYTES = 10 * 1024 * 1024;
|
|
@@ -8308,7 +8546,7 @@ async function loadLocal(src, cwd) {
|
|
|
8308
8546
|
log.warn("outbound", "image-size", { size, src: src.slice(0, 80) });
|
|
8309
8547
|
return { cacheKey: `local:${abs}:${size}` };
|
|
8310
8548
|
}
|
|
8311
|
-
const buffer = await
|
|
8549
|
+
const buffer = await readFile8(abs);
|
|
8312
8550
|
return { buffer, cacheKey: `local:${abs}:${mtimeMs}:${size}` };
|
|
8313
8551
|
}
|
|
8314
8552
|
async function loadRemote(url) {
|
|
@@ -8361,7 +8599,7 @@ init_logger();
|
|
|
8361
8599
|
init_cards2();
|
|
8362
8600
|
|
|
8363
8601
|
// src/cli-bridge/hooks.ts
|
|
8364
|
-
import { mkdir as mkdir6, readFile as
|
|
8602
|
+
import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
|
|
8365
8603
|
import { homedir as homedir3 } from "os";
|
|
8366
8604
|
import { join as join10, resolve as resolve3 } from "path";
|
|
8367
8605
|
var AGENT2LARK_MARKER = "agent2lark-hook";
|
|
@@ -8382,7 +8620,7 @@ async function inspectCliBridgeHooks(opts = {}) {
|
|
|
8382
8620
|
const codex = await readJson(join10(home, ".codex", "hooks.json"));
|
|
8383
8621
|
let codexStatus = inspectAgent("codex", codex, [...CODEX_EVENTS]);
|
|
8384
8622
|
if (codexStatus.status === "installed") {
|
|
8385
|
-
const toml = await
|
|
8623
|
+
const toml = await readFile9(join10(home, ".codex", "config.toml"), "utf8").catch(() => "");
|
|
8386
8624
|
if (!hasCodexHooksFeature(toml)) {
|
|
8387
8625
|
codexStatus = {
|
|
8388
8626
|
agent: "codex",
|
|
@@ -8410,7 +8648,7 @@ async function installCliBridgeHooks(opts) {
|
|
|
8410
8648
|
json.hooks = installAgentGroups(json.hooks, "codex", [...CODEX_EVENTS], opts.command);
|
|
8411
8649
|
await writeJson(file, json);
|
|
8412
8650
|
const tomlPath = join10(home, ".codex", "config.toml");
|
|
8413
|
-
const existing = await
|
|
8651
|
+
const existing = await readFile9(tomlPath, "utf8").catch(() => "");
|
|
8414
8652
|
await mkdir6(join10(home, ".codex"), { recursive: true });
|
|
8415
8653
|
await writeFile6(tomlPath, withCodexHooksFeature(existing), "utf8");
|
|
8416
8654
|
}
|
|
@@ -8419,7 +8657,7 @@ function resolveHome(homeDir) {
|
|
|
8419
8657
|
return homeDir ?? homedir3();
|
|
8420
8658
|
}
|
|
8421
8659
|
async function readJson(path) {
|
|
8422
|
-
const text = await
|
|
8660
|
+
const text = await readFile9(path, "utf8").catch(() => "{}");
|
|
8423
8661
|
const parsed = JSON.parse(text || "{}");
|
|
8424
8662
|
return typeof parsed === "object" && parsed ? parsed : {};
|
|
8425
8663
|
}
|
|
@@ -8539,6 +8777,81 @@ function withCodexHooksFeature(text) {
|
|
|
8539
8777
|
return [...lines, ""].join("\n");
|
|
8540
8778
|
}
|
|
8541
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
|
+
|
|
8542
8855
|
// src/service/update.ts
|
|
8543
8856
|
init_paths();
|
|
8544
8857
|
import { closeSync, existsSync as existsSync9, openSync as openSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2, writeSync } from "fs";
|
|
@@ -8556,7 +8869,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
8556
8869
|
// src/service/common.ts
|
|
8557
8870
|
init_paths();
|
|
8558
8871
|
import { appendFileSync, createReadStream, statSync as statSync2 } from "fs";
|
|
8559
|
-
import { appendFile, mkdir as mkdir7, readFile as
|
|
8872
|
+
import { appendFile, mkdir as mkdir7, readFile as readFile10 } from "fs/promises";
|
|
8560
8873
|
import { dirname as dirname6, join as join11, resolve as resolve4 } from "path";
|
|
8561
8874
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8562
8875
|
function serviceStdoutPath() {
|
|
@@ -8625,7 +8938,7 @@ function fileSize(file) {
|
|
|
8625
8938
|
}
|
|
8626
8939
|
async function lastLines(file, n) {
|
|
8627
8940
|
try {
|
|
8628
|
-
const text = await
|
|
8941
|
+
const text = await readFile10(file, "utf8");
|
|
8629
8942
|
return text.split("\n").slice(-n - 1).join("\n").trimEnd();
|
|
8630
8943
|
} catch {
|
|
8631
8944
|
return "";
|
|
@@ -9435,7 +9748,7 @@ async function restartDaemon() {
|
|
|
9435
9748
|
|
|
9436
9749
|
// src/agent/codex-appserver/usage.ts
|
|
9437
9750
|
init_logger();
|
|
9438
|
-
import { readFile as
|
|
9751
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
9439
9752
|
import { homedir as homedir6 } from "os";
|
|
9440
9753
|
import { join as join16 } from "path";
|
|
9441
9754
|
var DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
|
|
@@ -9453,7 +9766,7 @@ async function readCodexAuth() {
|
|
|
9453
9766
|
for (let i = 0; i < 3; i++) {
|
|
9454
9767
|
let raw;
|
|
9455
9768
|
try {
|
|
9456
|
-
raw = await
|
|
9769
|
+
raw = await readFile11(file, "utf8");
|
|
9457
9770
|
} catch (err) {
|
|
9458
9771
|
throw new UsageError("no-auth", `\u8BFB\u4E0D\u5230 ${file}\uFF1A${err instanceof Error ? err.message : String(err)}`);
|
|
9459
9772
|
}
|
|
@@ -9482,7 +9795,7 @@ function jwtExpMs(token) {
|
|
|
9482
9795
|
}
|
|
9483
9796
|
async function chatgptBaseUrl() {
|
|
9484
9797
|
try {
|
|
9485
|
-
const raw = await
|
|
9798
|
+
const raw = await readFile11(join16(resolveCodexHome(), "config.toml"), "utf8");
|
|
9486
9799
|
for (const line of raw.split("\n")) {
|
|
9487
9800
|
const t = line.trim();
|
|
9488
9801
|
if (t.startsWith("[")) break;
|
|
@@ -9845,7 +10158,15 @@ function heatmapElements(p, today) {
|
|
|
9845
10158
|
return [md("\u{1F4C8} **\u6BCF\u65E5 Token \u7528\u91CF**"), heatmapChartEl(p.dailyBuckets, today)];
|
|
9846
10159
|
}
|
|
9847
10160
|
function effortLabel(effort) {
|
|
9848
|
-
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
|
+
};
|
|
9849
10170
|
return m[effort] ?? effort;
|
|
9850
10171
|
}
|
|
9851
10172
|
function insightsElements(p) {
|
|
@@ -10086,6 +10407,7 @@ init_paths();
|
|
|
10086
10407
|
init_logger();
|
|
10087
10408
|
import { mkdir as mkdir11 } from "fs/promises";
|
|
10088
10409
|
import { existsSync as existsSync10 } from "fs";
|
|
10410
|
+
import { homedir as homedir7 } from "os";
|
|
10089
10411
|
import { isAbsolute as isAbsolute2, join as join17, resolve as resolve7 } from "path";
|
|
10090
10412
|
|
|
10091
10413
|
// src/project/announcement.ts
|
|
@@ -10275,22 +10597,38 @@ function assertBackendUsable(backend, mode) {
|
|
|
10275
10597
|
}
|
|
10276
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`);
|
|
10277
10599
|
}
|
|
10278
|
-
async function resolveCwd(name, existingPath) {
|
|
10600
|
+
async function resolveCwd(name, existingPath, configuredProjectsRootDir) {
|
|
10279
10601
|
if (existingPath) {
|
|
10280
10602
|
const cwd2 = isAbsolute2(existingPath) ? existingPath : resolve7(existingPath);
|
|
10281
10603
|
if (!existsSync10(cwd2)) throw new Error(`\u6587\u4EF6\u5939\u4E0D\u5B58\u5728\uFF1A${cwd2}`);
|
|
10282
10604
|
return { cwd: cwd2, blank: false };
|
|
10283
10605
|
}
|
|
10284
|
-
const cwd = join17(
|
|
10606
|
+
const cwd = join17(resolveProjectsRootDir(configuredProjectsRootDir), name);
|
|
10285
10607
|
await mkdir11(cwd, { recursive: true });
|
|
10286
10608
|
return { cwd, blank: true };
|
|
10287
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
|
+
}
|
|
10288
10626
|
async function createProject(channel, input2) {
|
|
10289
10627
|
const name = input2.name.trim();
|
|
10290
10628
|
if (!name) throw new Error("\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A");
|
|
10291
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`);
|
|
10292
10630
|
assertBackendUsable(input2.backend, input2.mode ?? "full");
|
|
10293
|
-
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
10631
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath, input2.projectsRootDir);
|
|
10294
10632
|
const res = await channel.rawClient.im.v1.chat.create({
|
|
10295
10633
|
params: { user_id_type: "open_id" },
|
|
10296
10634
|
data: { name, user_id_list: [input2.ownerOpenId] }
|
|
@@ -10327,7 +10665,7 @@ async function joinExistingGroup(channel, input2) {
|
|
|
10327
10665
|
const bound = await getProjectByChatId(input2.chatId);
|
|
10328
10666
|
if (bound) throw new Error(`\u8BE5\u7FA4\u5DF2\u7ED1\u5B9A\u4E3A\u9879\u76EE\u300C${bound.name}\u300D`);
|
|
10329
10667
|
assertBackendUsable(input2.backend, input2.mode ?? "qa");
|
|
10330
|
-
const { cwd, blank } = await resolveCwd(name, input2.existingPath);
|
|
10668
|
+
const { cwd, blank } = await resolveCwd(name, input2.existingPath, input2.projectsRootDir);
|
|
10331
10669
|
const project = {
|
|
10332
10670
|
name,
|
|
10333
10671
|
chatId: input2.chatId,
|
|
@@ -10367,7 +10705,7 @@ async function leaveChat(channel, chatId) {
|
|
|
10367
10705
|
|
|
10368
10706
|
// src/bot/session-store.ts
|
|
10369
10707
|
init_paths();
|
|
10370
|
-
import { mkdir as mkdir12, readFile as
|
|
10708
|
+
import { mkdir as mkdir12, readFile as readFile12, rename as rename5, writeFile as writeFile10 } from "fs/promises";
|
|
10371
10709
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
10372
10710
|
import { dirname as dirname12 } from "path";
|
|
10373
10711
|
var FILE_VERSION3 = 2;
|
|
@@ -10386,7 +10724,7 @@ async function read2() {
|
|
|
10386
10724
|
}
|
|
10387
10725
|
async function listSessionsIn(file) {
|
|
10388
10726
|
try {
|
|
10389
|
-
const text = await
|
|
10727
|
+
const text = await readFile12(file, "utf8");
|
|
10390
10728
|
const parsed = JSON.parse(text);
|
|
10391
10729
|
if (!Array.isArray(parsed.sessions)) return [];
|
|
10392
10730
|
return parsed.sessions.map(migrate);
|
|
@@ -11020,7 +11358,7 @@ function weaveSender(text, sender) {
|
|
|
11020
11358
|
|
|
11021
11359
|
// src/bot/comments.ts
|
|
11022
11360
|
init_logger();
|
|
11023
|
-
import { mkdir as mkdir14, readdir as readdir4, readFile as
|
|
11361
|
+
import { mkdir as mkdir14, readdir as readdir4, readFile as readFile13, writeFile as writeFile11 } from "fs/promises";
|
|
11024
11362
|
import { dirname as dirname13, join as join19 } from "path";
|
|
11025
11363
|
var SUPPORTED_FILE_TYPES = /* @__PURE__ */ new Set(["doc", "docx", "sheet", "bitable"]);
|
|
11026
11364
|
var REPLY_MAX_CHARS = 2e3;
|
|
@@ -11149,7 +11487,7 @@ function commentCwd(projectsRoot, fileType, fileToken) {
|
|
|
11149
11487
|
}
|
|
11150
11488
|
async function loadCommentInstructions(masterFile) {
|
|
11151
11489
|
try {
|
|
11152
|
-
const existing = await
|
|
11490
|
+
const existing = await readFile13(masterFile, "utf8");
|
|
11153
11491
|
return existing.trim() ? existing : DEFAULT_COMMENT_INSTRUCTIONS;
|
|
11154
11492
|
} catch {
|
|
11155
11493
|
}
|
|
@@ -11461,7 +11799,6 @@ function selectValue(formValue, name) {
|
|
|
11461
11799
|
function asTier(v) {
|
|
11462
11800
|
return v === "qa" || v === "write" || v === "full" ? v : void 0;
|
|
11463
11801
|
}
|
|
11464
|
-
var REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh"];
|
|
11465
11802
|
function asEffort(v) {
|
|
11466
11803
|
return v !== void 0 && REASONING_EFFORTS.includes(v) ? v : void 0;
|
|
11467
11804
|
}
|
|
@@ -11488,6 +11825,22 @@ function safeBackendId(formValue) {
|
|
|
11488
11825
|
const v = selectValue(formValue, "backend");
|
|
11489
11826
|
return v && backendIds().includes(v) ? v : void 0;
|
|
11490
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
|
+
}
|
|
11491
11844
|
function parseGoalTrigger(text) {
|
|
11492
11845
|
if (!/(^|\s)\/goal(?=\s|$)/i.test(text)) return null;
|
|
11493
11846
|
const objective = text.replace(/(^|\s)\/goal(?=\s|$)/gi, " ").replace(/\s+/g, " ").trim();
|
|
@@ -11584,14 +11937,21 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11584
11937
|
const commentInstrUsed = /* @__PURE__ */ new Map();
|
|
11585
11938
|
const sema = new Semaphore(getMaxConcurrentRuns(cfg));
|
|
11586
11939
|
const currentIdleMs = () => getRunIdleTimeoutMs(cfg) ?? 0;
|
|
11940
|
+
const writePreferences = createAppPreferencesWriter({ cfg });
|
|
11587
11941
|
const resumePending = /* @__PURE__ */ new Map();
|
|
11588
11942
|
const modelPending = /* @__PURE__ */ new Map();
|
|
11589
11943
|
const runsByCard = /* @__PURE__ */ new Map();
|
|
11590
11944
|
const runCards = /* @__PURE__ */ new Map();
|
|
11591
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);
|
|
11592
11951
|
const lastRunCard = /* @__PURE__ */ new Map();
|
|
11593
11952
|
const lastUsage = /* @__PURE__ */ new Map();
|
|
11594
11953
|
const seenInbound = new RecentIdCache();
|
|
11954
|
+
const completionReminderView = (state) => shouldShowCompletionReminderButton(cfg) ? state.completionReminderRequested ? "requested" : "available" : void 0;
|
|
11595
11955
|
const listModels = (be = backend) => be.listModels();
|
|
11596
11956
|
async function addReaction(messageId, emoji) {
|
|
11597
11957
|
try {
|
|
@@ -11899,7 +12259,12 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11899
12259
|
}
|
|
11900
12260
|
}
|
|
11901
12261
|
}
|
|
11902
|
-
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
|
+
});
|
|
11903
12268
|
log.info("intake", "queued", { depth: cur.queue.length });
|
|
11904
12269
|
return;
|
|
11905
12270
|
}
|
|
@@ -11924,7 +12289,12 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
11924
12289
|
void replyGoalBusy(msg, flat);
|
|
11925
12290
|
return;
|
|
11926
12291
|
}
|
|
11927
|
-
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
|
+
});
|
|
11928
12298
|
log.info("intake", "queued", { depth: existing.queue.length });
|
|
11929
12299
|
return;
|
|
11930
12300
|
}
|
|
@@ -12000,7 +12370,9 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12000
12370
|
firstText,
|
|
12001
12371
|
images,
|
|
12002
12372
|
knownThreadId: sessionKey,
|
|
12373
|
+
summary: stripFileTokens(summaryText2 ?? text).slice(0, 80) || "(\u672C\u8F6E\u4EFB\u52A1)",
|
|
12003
12374
|
requesterOpenId: msg.senderId,
|
|
12375
|
+
requestedAt: msg.createTime || tIntake,
|
|
12004
12376
|
// 编织完成 → turn/start 之间不再读盘:首轮直接用预取的会话记录
|
|
12005
12377
|
// (prior=undefined 即确知是全新会话,刚 upsert 的记录还没有 model)。
|
|
12006
12378
|
firstRec: prior ?? null,
|
|
@@ -12119,6 +12491,7 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12119
12491
|
cwd,
|
|
12120
12492
|
summary: stripFileTokens(text).slice(0, 80) || "(\u7A7A)",
|
|
12121
12493
|
requesterOpenId: msg.senderId,
|
|
12494
|
+
requestedAt: msg.createTime || tIntake,
|
|
12122
12495
|
roleSuffix: perm.roleSuffix,
|
|
12123
12496
|
backendId: be.id,
|
|
12124
12497
|
timing: { tResolve: tResolveDone - tIntake, tWeave: Date.now() - tIntake }
|
|
@@ -12483,6 +12856,35 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12483
12856
|
}
|
|
12484
12857
|
st.interrupt?.();
|
|
12485
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 });
|
|
12486
12888
|
}).on(RC.endGoal, ({ evt, value }) => {
|
|
12487
12889
|
const key = typeof value.m === "string" ? value.m : evt.messageId;
|
|
12488
12890
|
if (!runAllowed(evt)) return;
|
|
@@ -12510,12 +12912,49 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12510
12912
|
return m;
|
|
12511
12913
|
};
|
|
12512
12914
|
function applyPref(evt, mut, opts) {
|
|
12513
|
-
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12514
|
-
const
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
|
|
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;
|
|
12519
12958
|
}
|
|
12520
12959
|
let cliHookStatuses;
|
|
12521
12960
|
function renderSettings() {
|
|
@@ -12537,20 +12976,36 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12537
12976
|
function commentBackendOptions() {
|
|
12538
12977
|
return visibleCatalog().filter((e) => e.id === DEFAULT_BACKEND_ID || isBackendEntryInstalled(e)).map((e) => ({ id: e.id, label: e.displayName }));
|
|
12539
12978
|
}
|
|
12979
|
+
let commentSettingsRenderGeneration = 0;
|
|
12540
12980
|
async function renderCommentSettings(notice) {
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
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
|
+
}
|
|
12544
12997
|
}
|
|
12545
12998
|
function applyCommentsPref(evt, mut) {
|
|
12546
12999
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12547
|
-
const
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
void
|
|
12553
|
-
|
|
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
|
+
});
|
|
12554
13009
|
}
|
|
12555
13010
|
const freshMenu = (evt) => {
|
|
12556
13011
|
patch(evt, buildDmMenuCard({ webConsoleUrl: webConsoleUrl(), version: bridgeVersion() }));
|
|
@@ -12655,7 +13110,14 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12655
13110
|
else if (!op) result = buildNewProjectFormCard({ name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD", backends: backends2 });
|
|
12656
13111
|
else {
|
|
12657
13112
|
try {
|
|
12658
|
-
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
|
+
});
|
|
12659
13121
|
log.info("console", "new-project", { name: p.name, blank: p.blank, backend: p.backend });
|
|
12660
13122
|
result = buildNewProjectDoneCard(p);
|
|
12661
13123
|
} catch (err) {
|
|
@@ -12683,7 +13145,16 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12683
13145
|
else if (!op) result = buildJoinGroupFormCard({ chatId, name, cwd: cwdIn, error: "\u65E0\u6CD5\u8BC6\u522B\u64CD\u4F5C\u8005\u8EAB\u4EFD", backends: backends2 });
|
|
12684
13146
|
else {
|
|
12685
13147
|
try {
|
|
12686
|
-
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
|
+
});
|
|
12687
13158
|
log.info("console", "join-group", { name: p.name, blank: p.blank, backend: p.backend, mode: p.mode });
|
|
12688
13159
|
result = buildNewProjectDoneCard(p);
|
|
12689
13160
|
} catch (err) {
|
|
@@ -12702,26 +13173,15 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12702
13173
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, renderSettings);
|
|
12703
13174
|
}).on(DM.coffeeSettings, async ({ evt }) => {
|
|
12704
13175
|
if (dmAdmin(evt.operator?.openId)) await patch(evt, () => renderCoffeeSettings(true));
|
|
12705
|
-
}).on(CLI.toggleEnabled,
|
|
13176
|
+
}).on(CLI.toggleEnabled, ({ evt, value }) => {
|
|
12706
13177
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12707
13178
|
const enabled = value.v === "on";
|
|
12708
13179
|
if (enabled && !canEnableCliBridge(cfg).ok) return;
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
}, { render: false });
|
|
12715
|
-
} else {
|
|
12716
|
-
applyPref(evt, (p) => {
|
|
12717
|
-
p.cliBridge = { ...p.cliBridge ?? {}, enabled: false };
|
|
12718
|
-
}, { render: false });
|
|
12719
|
-
await cliBridge?.shutdown?.();
|
|
12720
|
-
}
|
|
12721
|
-
} catch (err) {
|
|
12722
|
-
log.fail("cli-bridge", err, { phase: enabled ? "enable" : "disable" });
|
|
12723
|
-
}
|
|
12724
|
-
void patch(evt, renderCoffeeSettings);
|
|
13180
|
+
const transition = setCliBridgeEnabled(evt, enabled);
|
|
13181
|
+
void patch(evt, async () => {
|
|
13182
|
+
await transition;
|
|
13183
|
+
return renderCoffeeSettings();
|
|
13184
|
+
});
|
|
12725
13185
|
}).on(CLI.repairHooks, async ({ evt }) => {
|
|
12726
13186
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12727
13187
|
try {
|
|
@@ -12736,27 +13196,36 @@ function createOrchestrator(channel, cfg, fallbackCwd, cliBridge) {
|
|
|
12736
13196
|
}).on(CLI.setNotifyScope, ({ evt, value }) => {
|
|
12737
13197
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12738
13198
|
const scope = value.v === "bound_projects" || value.v === "none" ? value.v : "all";
|
|
12739
|
-
applyPref(evt, (p) => {
|
|
13199
|
+
const saved = applyPref(evt, (p) => {
|
|
12740
13200
|
p.cliBridge = { ...p.cliBridge ?? {}, notifyScope: scope };
|
|
12741
13201
|
}, { render: false });
|
|
12742
|
-
void patch(evt,
|
|
13202
|
+
void patch(evt, async () => {
|
|
13203
|
+
await saved;
|
|
13204
|
+
return renderCoffeeSettings();
|
|
13205
|
+
});
|
|
12743
13206
|
}).on(CLI.toggleAgent, ({ evt, value }) => {
|
|
12744
13207
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12745
13208
|
const agent = value.agent === "codex" ? "codex" : "claude";
|
|
12746
13209
|
const on = value.v === "on";
|
|
12747
|
-
applyPref(evt, (p) => {
|
|
13210
|
+
const saved = applyPref(evt, (p) => {
|
|
12748
13211
|
const cur = p.cliBridge ?? {};
|
|
12749
13212
|
p.cliBridge = { ...cur, agents: { ...cur.agents ?? {}, [agent]: on } };
|
|
12750
13213
|
}, { render: false });
|
|
12751
|
-
void patch(evt,
|
|
13214
|
+
void patch(evt, async () => {
|
|
13215
|
+
await saved;
|
|
13216
|
+
return renderCoffeeSettings();
|
|
13217
|
+
});
|
|
12752
13218
|
}).on(CLI.toggleKeepAwake, ({ evt, value }) => {
|
|
12753
13219
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12754
13220
|
const on = value.v === "on";
|
|
12755
|
-
applyPref(evt, (p) => {
|
|
13221
|
+
const saved = applyPref(evt, (p) => {
|
|
12756
13222
|
const cur = p.cliBridge ?? {};
|
|
12757
13223
|
p.cliBridge = { ...cur, keepAwake: { ...cur.keepAwake ?? {}, enabled: on } };
|
|
12758
13224
|
}, { render: false });
|
|
12759
|
-
void patch(evt,
|
|
13225
|
+
void patch(evt, async () => {
|
|
13226
|
+
await saved;
|
|
13227
|
+
return renderCoffeeSettings();
|
|
13228
|
+
});
|
|
12760
13229
|
}).on(DM.doctor, async ({ evt }) => {
|
|
12761
13230
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
12762
13231
|
await sendManagedCard(channel, evt.chatId, buildDoctorCard(await buildDoctorInfo()), evt.messageId).catch(
|
|
@@ -12925,6 +13394,52 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
12925
13394
|
}
|
|
12926
13395
|
const sec = n === 0 ? 0 : Math.min(Math.max(Math.floor(n), RUN_IDLE_TIMEOUT_MIN_SEC), RUN_IDLE_TIMEOUT_MAX_SEC);
|
|
12927
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
|
+
});
|
|
12928
13443
|
}).on(DM.setPending, ({ evt, value }) => {
|
|
12929
13444
|
if (value.v === "steer" || value.v === "queue") applyPref(evt, (p) => p.pendingPolicy = value.v);
|
|
12930
13445
|
}).on(DM.setConcurrency, ({ evt, value }) => {
|
|
@@ -13080,11 +13595,12 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13080
13595
|
log.info("console", "admin-add", { picked: id?.slice(-6) ?? null });
|
|
13081
13596
|
void (async () => {
|
|
13082
13597
|
if (id) {
|
|
13083
|
-
|
|
13084
|
-
|
|
13085
|
-
|
|
13086
|
-
|
|
13087
|
-
|
|
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" }));
|
|
13088
13604
|
}
|
|
13089
13605
|
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
13090
13606
|
const next = buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
@@ -13095,11 +13611,12 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13095
13611
|
const id = typeof value.u === "string" ? value.u : "";
|
|
13096
13612
|
patch(evt, async () => {
|
|
13097
13613
|
if (id && id !== resolveOwner(cfg)) {
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
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" }));
|
|
13103
13620
|
}
|
|
13104
13621
|
const ids = [resolveOwner(cfg), ...cfg.preferences?.access?.admins ?? []];
|
|
13105
13622
|
return buildAdminsCard(cfg, await namesWithOperator(evt, ids));
|
|
@@ -13324,22 +13841,36 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13324
13841
|
settleUpdate(evt.messageId, buildResumeErrorCard(state, err instanceof Error ? err.message : String(err)));
|
|
13325
13842
|
}
|
|
13326
13843
|
}
|
|
13844
|
+
async function sendCompletionReminder(input2) {
|
|
13845
|
+
await sendCompletionReminderReply({ channel, cfg, dedupe: completionReminderSent }, input2);
|
|
13846
|
+
}
|
|
13327
13847
|
async function acquireRunSlot(opts, state, activeKey, reaction) {
|
|
13328
13848
|
if (sema.hasFree()) return { release: await sema.acquire() };
|
|
13329
13849
|
const stream2 = new RunCardStream();
|
|
13330
13850
|
let msgId;
|
|
13851
|
+
const queueCard = (input2) => buildQueuedCard({
|
|
13852
|
+
...input2,
|
|
13853
|
+
...!state.isGoal ? { completionReminder: completionReminderView(state) } : {}
|
|
13854
|
+
});
|
|
13331
13855
|
const q = sema.enqueue((pos) => {
|
|
13332
|
-
if (msgId) stream2.streamCoalesced(channel,
|
|
13856
|
+
if (msgId) stream2.streamCoalesced(channel, queueCard({ position: pos, cardKey: msgId }), null);
|
|
13333
13857
|
});
|
|
13334
13858
|
try {
|
|
13335
|
-
msgId = await stream2.create(channel, opts.chatId,
|
|
13859
|
+
msgId = await stream2.create(channel, opts.chatId, queueCard({ position: q.position() }), {
|
|
13336
13860
|
replyTo: opts.replyTo,
|
|
13337
13861
|
replyInThread: opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId)
|
|
13338
13862
|
});
|
|
13339
13863
|
const pos = q.position();
|
|
13340
|
-
if (pos > 0) await stream2.updateCard(channel,
|
|
13864
|
+
if (pos > 0) await stream2.updateCard(channel, queueCard({ position: pos, cardKey: msgId }));
|
|
13341
13865
|
runsByCard.set(msgId, state);
|
|
13342
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
|
+
}
|
|
13343
13874
|
} catch (err) {
|
|
13344
13875
|
log.fail("card", err, { phase: "queued-card" });
|
|
13345
13876
|
}
|
|
@@ -13352,7 +13883,8 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13352
13883
|
if (msgId) {
|
|
13353
13884
|
runsByCard.delete(msgId);
|
|
13354
13885
|
runStreams.delete(msgId);
|
|
13355
|
-
|
|
13886
|
+
completionReminderRefreshers.delete(msgId);
|
|
13887
|
+
void stream2.updateCard(channel, queueCard({ cancelled: true, dropped: state.queue.length }));
|
|
13356
13888
|
}
|
|
13357
13889
|
reaction?.done();
|
|
13358
13890
|
log.info("card", "action", { actionId: "run.stop", queuedCancel: true });
|
|
@@ -13407,10 +13939,17 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13407
13939
|
let intake = opts.timing;
|
|
13408
13940
|
let firstRec = opts.firstRec;
|
|
13409
13941
|
try {
|
|
13410
|
-
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
|
+
};
|
|
13411
13948
|
let replyTo = opts.replyTo;
|
|
13412
13949
|
let replyInThread = opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId);
|
|
13413
13950
|
for (; ; ) {
|
|
13951
|
+
const turnInput = currentTurn.input;
|
|
13952
|
+
state.requesterOpenId = currentTurn.requesterOpenId;
|
|
13414
13953
|
const rec = firstRec !== void 0 ? firstRec ?? void 0 : topicThreadId ? await getSession(topicThreadId) : void 0;
|
|
13415
13954
|
firstRec = void 0;
|
|
13416
13955
|
const turnModel = rec?.model ?? opts.model;
|
|
@@ -13424,8 +13963,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13424
13963
|
let cardMsgId;
|
|
13425
13964
|
const rc = {
|
|
13426
13965
|
rs: render.snapshot(),
|
|
13427
|
-
requesterOpenId:
|
|
13966
|
+
requesterOpenId: currentTurn.requesterOpenId,
|
|
13428
13967
|
showTools: render.showTools,
|
|
13968
|
+
completionReminder: completionReminderView(state),
|
|
13429
13969
|
// 模型显示档位:footnote 本轮 model·推理强度;always 档终态卡也保留。
|
|
13430
13970
|
...modelDisp !== "off" && turnModel ? { model: turnModel, effort: turnEffort, modelOnTerminal: modelDisp === "always" } : {}
|
|
13431
13971
|
};
|
|
@@ -13469,6 +14009,11 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13469
14009
|
rc.cardKey = cardMsgId;
|
|
13470
14010
|
runsByCard.set(cardMsgId, state);
|
|
13471
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);
|
|
13472
14017
|
await adoptThreadId(cardMsgId);
|
|
13473
14018
|
if (!firstCardSent) {
|
|
13474
14019
|
firstCardSent = true;
|
|
@@ -13526,6 +14071,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13526
14071
|
}
|
|
13527
14072
|
render.apply(ev);
|
|
13528
14073
|
rc.rs = render.snapshot();
|
|
14074
|
+
rc.completionReminder = completionReminderView(state);
|
|
13529
14075
|
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
13530
14076
|
}
|
|
13531
14077
|
const doneAt = Date.now();
|
|
@@ -13534,12 +14080,15 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13534
14080
|
state.interrupt = void 0;
|
|
13535
14081
|
const interrupted = stopper.interrupted();
|
|
13536
14082
|
const killed = timedOut || interrupted && stopper.forced();
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
14083
|
+
const procDead = !killed && !opts.thread.isAlive();
|
|
14084
|
+
settleOrdinaryTurnRender(render, {
|
|
14085
|
+
interrupted,
|
|
14086
|
+
timedOut,
|
|
14087
|
+
idleTimeoutSeconds: Math.round(idleMs / 1e3),
|
|
14088
|
+
procDead
|
|
14089
|
+
});
|
|
13540
14090
|
rc.rs = render.snapshot();
|
|
13541
14091
|
if (interrupted) log.info("agent", "interrupt", { graceful: !stopper.forced(), threadId: topicThreadId ?? null });
|
|
13542
|
-
const procDead = !killed && !opts.thread.isAlive();
|
|
13543
14092
|
if (killed || procDead) {
|
|
13544
14093
|
void opts.thread.close().catch(() => void 0);
|
|
13545
14094
|
if (topicThreadId) sessions.delete(topicThreadId);
|
|
@@ -13557,7 +14106,9 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13557
14106
|
if (imgSources.length > 0) {
|
|
13558
14107
|
rc.images = await uploadOutboundImages(channel, imgSources, opts.cwd ?? fallbackCwd);
|
|
13559
14108
|
}
|
|
13560
|
-
|
|
14109
|
+
const manuallyRequested = Boolean(state.completionReminderRequested);
|
|
14110
|
+
completionReminderRefreshers.delete(cardMsgId);
|
|
14111
|
+
const terminalCardUpdated = await stream2.finalizeCard(channel, buildRunCard(rc));
|
|
13561
14112
|
{
|
|
13562
14113
|
const terminalAt = Date.now();
|
|
13563
14114
|
const st = stream2.stats();
|
|
@@ -13597,6 +14148,16 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13597
14148
|
touchSession(topicThreadId);
|
|
13598
14149
|
await patchSession(topicThreadId, { updatedAt: Date.now() });
|
|
13599
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
|
+
});
|
|
13600
14161
|
replyTo = finalMsgId;
|
|
13601
14162
|
replyInThread = !opts.flat;
|
|
13602
14163
|
log.info("card", "final", { terminal: render.terminal() });
|
|
@@ -13612,14 +14173,18 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
13612
14173
|
break;
|
|
13613
14174
|
}
|
|
13614
14175
|
if (state.queue.length === 0) break;
|
|
13615
|
-
|
|
14176
|
+
currentTurn = state.queue.shift();
|
|
14177
|
+
activateQueuedTurn(state, currentTurn);
|
|
13616
14178
|
}
|
|
13617
14179
|
} catch (err) {
|
|
13618
14180
|
log.fail("intake", err);
|
|
13619
14181
|
await channel.send(opts.chatId, { markdown: `\u274C ${err instanceof Error ? err.message : String(err)}` }, { replyTo: opts.replyTo, replyInThread: !opts.flat }).catch(() => void 0);
|
|
13620
14182
|
} finally {
|
|
13621
14183
|
active.delete(activeKey);
|
|
13622
|
-
if (curCardKey)
|
|
14184
|
+
if (curCardKey) {
|
|
14185
|
+
runsByCard.delete(curCardKey);
|
|
14186
|
+
completionReminderRefreshers.delete(curCardKey);
|
|
14187
|
+
}
|
|
13623
14188
|
if (activeKey.startsWith("pending:")) {
|
|
13624
14189
|
void opts.thread.close().catch(() => void 0);
|
|
13625
14190
|
log.warn("intake", "unadopted-thread-closed", { activeKey });
|
|
@@ -14198,7 +14763,11 @@ ${facts}`;
|
|
|
14198
14763
|
log.info("bridge", "shutdown", { closed: live.length });
|
|
14199
14764
|
}
|
|
14200
14765
|
void backend.listModels().catch((err) => log.fail("agent", err, { phase: "models-prewarm" }));
|
|
14201
|
-
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
|
+
};
|
|
14202
14771
|
return { onMessage, onComment, onBotAddedToChat, onBotRemovedFromChat, onReaction, onBotMenu, dispatcher, adminExecute, shutdown };
|
|
14203
14772
|
}
|
|
14204
14773
|
async function getThreadId(channel, messageId, attempts = 1) {
|
|
@@ -14398,10 +14967,9 @@ function createAdminIpcResponder(execute, send) {
|
|
|
14398
14967
|
// src/admin/service.ts
|
|
14399
14968
|
init_bots();
|
|
14400
14969
|
init_paths();
|
|
14401
|
-
import { readFile as
|
|
14970
|
+
import { readFile as readFile14, rm as rm7 } from "fs/promises";
|
|
14402
14971
|
init_schema();
|
|
14403
14972
|
init_store();
|
|
14404
|
-
init_schema();
|
|
14405
14973
|
|
|
14406
14974
|
// src/bot/register-bot.ts
|
|
14407
14975
|
init_store();
|
|
@@ -14571,7 +15139,7 @@ function createAdminService(deps = {}) {
|
|
|
14571
15139
|
}
|
|
14572
15140
|
async function lockFileRunState(botId) {
|
|
14573
15141
|
try {
|
|
14574
|
-
const raw = await
|
|
15142
|
+
const raw = await readFile14(botPaths(botId).processesFile, "utf8");
|
|
14575
15143
|
const rec = JSON.parse(raw);
|
|
14576
15144
|
if (typeof rec.pid === "number" && isAlive2(rec.pid)) {
|
|
14577
15145
|
return { running: true, pid: rec.pid, startedAt: rec.startedAt };
|
|
@@ -14584,6 +15152,13 @@ function createAdminService(deps = {}) {
|
|
|
14584
15152
|
const live = await deps.liveStatus?.(botId).catch(() => void 0);
|
|
14585
15153
|
return live ?? lockFileRunState(botId);
|
|
14586
15154
|
}
|
|
15155
|
+
async function completionReminderFor(botId) {
|
|
15156
|
+
try {
|
|
15157
|
+
return getCompletionReminderConfig(await loadConfig(botPaths(botId).configFile));
|
|
15158
|
+
} catch {
|
|
15159
|
+
return getCompletionReminderConfig({});
|
|
15160
|
+
}
|
|
15161
|
+
}
|
|
14587
15162
|
function executeWrite(botId, action, op) {
|
|
14588
15163
|
if (!deps.executeWrite) throw new NotWiredYetError(action);
|
|
14589
15164
|
return deps.executeWrite(botId, op);
|
|
@@ -14594,7 +15169,7 @@ function createAdminService(deps = {}) {
|
|
|
14594
15169
|
const configured = reg.bots.some((b) => b.active !== void 0);
|
|
14595
15170
|
const out = [];
|
|
14596
15171
|
for (const b of reg.bots) {
|
|
14597
|
-
const run = await runState(b.appId);
|
|
15172
|
+
const [run, completionReminder] = await Promise.all([runState(b.appId), completionReminderFor(b.appId)]);
|
|
14598
15173
|
out.push({
|
|
14599
15174
|
name: b.name,
|
|
14600
15175
|
appId: b.appId,
|
|
@@ -14606,7 +15181,8 @@ function createAdminService(deps = {}) {
|
|
|
14606
15181
|
running: run.running,
|
|
14607
15182
|
pid: run.pid,
|
|
14608
15183
|
startedAt: run.startedAt,
|
|
14609
|
-
connection: run.connection
|
|
15184
|
+
connection: run.connection,
|
|
15185
|
+
completionReminder
|
|
14610
15186
|
});
|
|
14611
15187
|
}
|
|
14612
15188
|
return out;
|
|
@@ -14635,6 +15211,13 @@ function createAdminService(deps = {}) {
|
|
|
14635
15211
|
async setAutoCompact(botId, projectName, on) {
|
|
14636
15212
|
await executeWrite(botId, "\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u5F00\u5173", { kind: "setAutoCompact", project: projectName, on });
|
|
14637
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
|
+
},
|
|
14638
15221
|
doctorBackends() {
|
|
14639
15222
|
return probeAllBackends();
|
|
14640
15223
|
},
|
|
@@ -15122,6 +15705,7 @@ init_logger();
|
|
|
15122
15705
|
|
|
15123
15706
|
// src/web/server.ts
|
|
15124
15707
|
init_paths();
|
|
15708
|
+
init_schema();
|
|
15125
15709
|
import { createServer } from "http";
|
|
15126
15710
|
import { randomUUID as randomUUID7, timingSafeEqual } from "crypto";
|
|
15127
15711
|
import { mkdirSync as mkdirSync3, watch } from "fs";
|
|
@@ -15706,6 +16290,11 @@ var UI_HTML = `<!doctype html>
|
|
|
15706
16290
|
.drawer-mask.open { display: block; }
|
|
15707
16291
|
.drawer h3 { margin: 0 0 4px; font-size: 16px; }
|
|
15708
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); }
|
|
15709
16298
|
.backend-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; }
|
|
15710
16299
|
.backend-row .grow { flex: 1; min-width: 0; }
|
|
15711
16300
|
.bk-group { margin: 6px 0 2px; }
|
|
@@ -17182,6 +17771,9 @@ ${UI_PURE_JS}
|
|
|
17182
17771
|
left.appendChild(projCard);
|
|
17183
17772
|
renderProjects(projList, pcount, b);
|
|
17184
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
|
+
|
|
17185
17777
|
cols.appendChild(left);
|
|
17186
17778
|
cols.appendChild(right);
|
|
17187
17779
|
root.appendChild(cols);
|
|
@@ -17253,6 +17845,58 @@ ${UI_PURE_JS}
|
|
|
17253
17845
|
});
|
|
17254
17846
|
}
|
|
17255
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
|
+
|
|
17256
17900
|
function renderProjects(box, countEl, b) {
|
|
17257
17901
|
box.textContent = '';
|
|
17258
17902
|
var projects = (b && b.projects) || [];
|
|
@@ -17873,6 +18517,7 @@ var LOGO_PNG = Buffer.from(LOGO_PNG_BASE64, "base64");
|
|
|
17873
18517
|
var DEFAULT_WEB_PORT = 51847;
|
|
17874
18518
|
var COOKIE_NAME = "fcb_console_token";
|
|
17875
18519
|
var SSE_INITIAL_TAIL_BYTES = 16 * 1024;
|
|
18520
|
+
var COMPLETION_REMINDER_MODES2 = ["manual", "long", "failures", "always"];
|
|
17876
18521
|
function createWebServer(opts) {
|
|
17877
18522
|
const token = opts.token ?? randomUUID7();
|
|
17878
18523
|
const html = opts.html ?? UI_HTML;
|
|
@@ -18048,6 +18693,45 @@ function createWebServer(opts) {
|
|
|
18048
18693
|
sendJson(res, 200, status);
|
|
18049
18694
|
return;
|
|
18050
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
|
+
}
|
|
18051
18735
|
const botMatch = /^\/api\/bots\/([^/]+)$/.exec(pathName);
|
|
18052
18736
|
if (req.method === "PATCH" && botMatch) {
|
|
18053
18737
|
const appId = decodeURIComponent(botMatch[1]);
|