@modelzen/feishu-codex-bridge 0.3.7 → 0.3.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/dist/cli.js +506 -128
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -111,11 +111,13 @@ function getMaxConcurrentRuns(cfg) {
|
|
|
111
111
|
function getPendingPolicy(cfg) {
|
|
112
112
|
return cfg.preferences?.pendingPolicy === "queue" ? "queue" : "steer";
|
|
113
113
|
}
|
|
114
|
+
var RUN_IDLE_TIMEOUT_MIN_SEC = 10;
|
|
115
|
+
var RUN_IDLE_TIMEOUT_MAX_SEC = 3600;
|
|
114
116
|
function getRunIdleTimeoutMs(cfg) {
|
|
115
117
|
const raw = cfg.preferences?.runIdleTimeoutSeconds;
|
|
116
118
|
if (raw === 0) return void 0;
|
|
117
119
|
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0) return 12e4;
|
|
118
|
-
const clamped = Math.min(Math.max(Math.floor(raw),
|
|
120
|
+
const clamped = Math.min(Math.max(Math.floor(raw), RUN_IDLE_TIMEOUT_MIN_SEC), RUN_IDLE_TIMEOUT_MAX_SEC);
|
|
119
121
|
return clamped * 1e3;
|
|
120
122
|
}
|
|
121
123
|
function isChatAllowed(cfg, chatId) {
|
|
@@ -598,9 +600,9 @@ async function spawnExecProvider(pc, ref) {
|
|
|
598
600
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
599
601
|
return new Promise((resolve7, reject) => {
|
|
600
602
|
const env = {};
|
|
601
|
-
if (pc.passEnv) for (const
|
|
602
|
-
const v = process.env[
|
|
603
|
-
if (v) env[
|
|
603
|
+
if (pc.passEnv) for (const k2 of pc.passEnv) {
|
|
604
|
+
const v = process.env[k2];
|
|
605
|
+
if (v) env[k2] = v;
|
|
604
606
|
}
|
|
605
607
|
if (pc.env) Object.assign(env, pc.env);
|
|
606
608
|
const child = spawnProcess(pc.command, pc.args ?? [], {
|
|
@@ -946,11 +948,11 @@ function emit(level, phase, event, fields = {}) {
|
|
|
946
948
|
event,
|
|
947
949
|
...ctx
|
|
948
950
|
};
|
|
949
|
-
for (const [
|
|
950
|
-
if (RESERVED_KEYS.has(
|
|
951
|
-
entry[`_${
|
|
951
|
+
for (const [k2, v] of Object.entries(fields)) {
|
|
952
|
+
if (RESERVED_KEYS.has(k2)) {
|
|
953
|
+
entry[`_${k2}`] = v;
|
|
952
954
|
} else {
|
|
953
|
-
entry[
|
|
955
|
+
entry[k2] = v;
|
|
954
956
|
}
|
|
955
957
|
}
|
|
956
958
|
const s = getStream();
|
|
@@ -1001,20 +1003,20 @@ function formatFields(fields) {
|
|
|
1001
1003
|
const keys = Object.keys(fields);
|
|
1002
1004
|
if (keys.length === 0) return "";
|
|
1003
1005
|
const parts = [];
|
|
1004
|
-
for (const
|
|
1005
|
-
const v = fields[
|
|
1006
|
+
for (const k2 of keys) {
|
|
1007
|
+
const v = fields[k2];
|
|
1006
1008
|
if (v === void 0 || v === null) continue;
|
|
1007
|
-
if (
|
|
1009
|
+
if (k2 === "stack") continue;
|
|
1008
1010
|
if (typeof v === "string") {
|
|
1009
|
-
parts.push(`${
|
|
1011
|
+
parts.push(`${k2}=${v.length > 80 ? `${v.slice(0, 80)}\u2026` : v}`);
|
|
1010
1012
|
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
1011
|
-
parts.push(`${
|
|
1013
|
+
parts.push(`${k2}=${v}`);
|
|
1012
1014
|
} else {
|
|
1013
1015
|
try {
|
|
1014
1016
|
const str = JSON.stringify(v);
|
|
1015
|
-
parts.push(`${
|
|
1017
|
+
parts.push(`${k2}=${str.length > 80 ? `${str.slice(0, 80)}\u2026` : str}`);
|
|
1016
1018
|
} catch {
|
|
1017
|
-
parts.push(`${
|
|
1019
|
+
parts.push(`${k2}=?`);
|
|
1018
1020
|
}
|
|
1019
1021
|
}
|
|
1020
1022
|
}
|
|
@@ -1378,6 +1380,14 @@ function mapNotification(n) {
|
|
|
1378
1380
|
return mapItemStart(n.params.item);
|
|
1379
1381
|
case "item/completed":
|
|
1380
1382
|
return mapItemComplete(n.params.item);
|
|
1383
|
+
case "thread/tokenUsage/updated":
|
|
1384
|
+
return {
|
|
1385
|
+
type: "context_usage",
|
|
1386
|
+
usedTokens: n.params.tokenUsage.last.totalTokens,
|
|
1387
|
+
contextWindow: n.params.tokenUsage.modelContextWindow
|
|
1388
|
+
};
|
|
1389
|
+
case "thread/compacted":
|
|
1390
|
+
return { type: "context_compacted" };
|
|
1381
1391
|
case "turn/completed":
|
|
1382
1392
|
return { type: "done", turnId: n.params.turn.id };
|
|
1383
1393
|
case "error":
|
|
@@ -1428,6 +1438,12 @@ function mapItemComplete(item) {
|
|
|
1428
1438
|
|
|
1429
1439
|
// src/agent/codex-appserver/backend.ts
|
|
1430
1440
|
var APPROVAL_POLICY = "never";
|
|
1441
|
+
var AUTO_COMPACT_OFF_LIMIT = 1e9;
|
|
1442
|
+
function withAutoCompact(params, autoCompact) {
|
|
1443
|
+
if (autoCompact !== false) return params;
|
|
1444
|
+
const config = params.config ?? {};
|
|
1445
|
+
return { ...params, config: { ...config, model_auto_compact_token_limit: AUTO_COMPACT_OFF_LIMIT } };
|
|
1446
|
+
}
|
|
1431
1447
|
function sandboxParams(mode, network) {
|
|
1432
1448
|
if ((mode ?? "full") === "full") return { sandbox: "danger-full-access" };
|
|
1433
1449
|
if (process.platform !== "darwin" && process.platform !== "win32") {
|
|
@@ -1465,6 +1481,7 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
|
1465
1481
|
"\u4E0D\u8981\u624B\u5199\u98DE\u4E66\u5361\u7247\u7684 JSON\u3002\u666E\u901A\u95EE\u7B54\u6B63\u5E38\u56DE\u590D\u5373\u53EF\uFF0C\u53EA\u6709\u7528\u6237\u8981\u5361\u7247\u65F6\u624D\u7528 ```feishu-card \u4EE3\u7801\u5757\u3002"
|
|
1466
1482
|
].join("\n");
|
|
1467
1483
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1484
|
+
var COMPACT_TIMEOUT_MS = 12e4;
|
|
1468
1485
|
function withDeadline(p, ms, label) {
|
|
1469
1486
|
return new Promise((resolve7, reject) => {
|
|
1470
1487
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -1546,6 +1563,40 @@ var CodexThread = class {
|
|
|
1546
1563
|
async abort(turnId) {
|
|
1547
1564
|
await this.client.request("turn/interrupt", { threadId: this.codexThreadId, turnId });
|
|
1548
1565
|
}
|
|
1566
|
+
async compact() {
|
|
1567
|
+
let startError;
|
|
1568
|
+
const startFailed = new Promise((resolve7) => {
|
|
1569
|
+
this.client.request("thread/compact/start", { threadId: this.codexThreadId }).then(void 0, (err) => {
|
|
1570
|
+
startError = err instanceof Error ? err : new Error(String(err));
|
|
1571
|
+
log.fail("agent", startError, { phase: "thread/compact/start" });
|
|
1572
|
+
resolve7("start-failed");
|
|
1573
|
+
});
|
|
1574
|
+
});
|
|
1575
|
+
let timer;
|
|
1576
|
+
const timeout = new Promise((resolve7) => {
|
|
1577
|
+
timer = setTimeout(() => resolve7("timeout"), COMPACT_TIMEOUT_MS);
|
|
1578
|
+
});
|
|
1579
|
+
const stream2 = this.client.stream()[Symbol.asyncIterator]();
|
|
1580
|
+
let compacted = false;
|
|
1581
|
+
let usage = null;
|
|
1582
|
+
try {
|
|
1583
|
+
while (true) {
|
|
1584
|
+
const step = await Promise.race([stream2.next(), startFailed, timeout]);
|
|
1585
|
+
if (step === "start-failed") throw startError ?? new Error("thread/compact/start \u8BF7\u6C42\u5931\u8D25");
|
|
1586
|
+
if (step === "timeout") throw new Error(`\u538B\u7F29\u8D85\u65F6\uFF08codex \u672A\u5728 ${COMPACT_TIMEOUT_MS / 1e3}s \u5185\u5B8C\u6210\uFF09`);
|
|
1587
|
+
if (step.done) break;
|
|
1588
|
+
const ev = mapNotification(step.value);
|
|
1589
|
+
if (!ev) continue;
|
|
1590
|
+
if (ev.type === "context_usage") usage = { usedTokens: ev.usedTokens, contextWindow: ev.contextWindow };
|
|
1591
|
+
else if (ev.type === "context_compacted") compacted = true;
|
|
1592
|
+
else if (ev.type === "error" && !ev.willRetry) throw new Error(ev.message);
|
|
1593
|
+
else if (ev.type === "done") break;
|
|
1594
|
+
}
|
|
1595
|
+
} finally {
|
|
1596
|
+
if (timer) clearTimeout(timer);
|
|
1597
|
+
}
|
|
1598
|
+
return { compacted, usage };
|
|
1599
|
+
}
|
|
1549
1600
|
async close() {
|
|
1550
1601
|
await this.client.close();
|
|
1551
1602
|
}
|
|
@@ -1634,7 +1685,7 @@ var CodexAppServerBackend = class {
|
|
|
1634
1685
|
}
|
|
1635
1686
|
}
|
|
1636
1687
|
async startThread(opts) {
|
|
1637
|
-
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1688
|
+
const sandbox = withAutoCompact(sandboxParams(opts.mode, opts.network), opts.autoCompact);
|
|
1638
1689
|
const client = await this.spawn(opts.cwd);
|
|
1639
1690
|
const res = await client.request("thread/start", {
|
|
1640
1691
|
cwd: opts.cwd,
|
|
@@ -1646,7 +1697,7 @@ var CodexAppServerBackend = class {
|
|
|
1646
1697
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
1647
1698
|
}
|
|
1648
1699
|
async resumeThread(opts) {
|
|
1649
|
-
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1700
|
+
const sandbox = withAutoCompact(sandboxParams(opts.mode, opts.network), opts.autoCompact);
|
|
1650
1701
|
const client = await this.spawn(opts.cwd);
|
|
1651
1702
|
const res = await client.request("thread/resume", {
|
|
1652
1703
|
threadId: opts.codexThreadId,
|
|
@@ -1819,39 +1870,53 @@ function stampRenderToken(card2) {
|
|
|
1819
1870
|
}
|
|
1820
1871
|
}
|
|
1821
1872
|
}
|
|
1822
|
-
for (const
|
|
1873
|
+
for (const k2 of Object.keys(obj)) visit(obj[k2]);
|
|
1823
1874
|
};
|
|
1824
1875
|
visit(card2);
|
|
1825
1876
|
}
|
|
1877
|
+
function isCardIdNotReady(err) {
|
|
1878
|
+
const data = err?.response?.data;
|
|
1879
|
+
return data?.code === 230099 || /11310|cardid is invalid/i.test(data?.msg ?? "");
|
|
1880
|
+
}
|
|
1826
1881
|
async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
|
|
1827
1882
|
stampRenderToken(card2);
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1883
|
+
const data = JSON.stringify(card2);
|
|
1884
|
+
const attempt = async () => {
|
|
1885
|
+
const created = await channel.rawClient.cardkit.v1.card.create({ data: { type: "card_json", data } });
|
|
1886
|
+
const cardId = created.data?.card_id;
|
|
1887
|
+
if (!cardId) {
|
|
1888
|
+
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
1889
|
+
}
|
|
1890
|
+
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
1891
|
+
let messageId;
|
|
1892
|
+
if (replyTo) {
|
|
1893
|
+
const sent = await channel.rawClient.im.v1.message.reply({
|
|
1894
|
+
path: { message_id: replyTo },
|
|
1895
|
+
data: { msg_type: "interactive", content, reply_in_thread: replyInThread }
|
|
1896
|
+
});
|
|
1897
|
+
messageId = sent.data?.message_id;
|
|
1898
|
+
} else {
|
|
1899
|
+
const sent = await channel.rawClient.im.v1.message.create({
|
|
1900
|
+
params: { receive_id_type: receiveIdType },
|
|
1901
|
+
data: { receive_id: to, msg_type: "interactive", content }
|
|
1902
|
+
});
|
|
1903
|
+
messageId = sent.data?.message_id;
|
|
1904
|
+
}
|
|
1905
|
+
if (!messageId) {
|
|
1906
|
+
throw new Error("send card-by-reference returned no message_id");
|
|
1907
|
+
}
|
|
1908
|
+
byMessageId.set(messageId, { cardId, sequence: 0 });
|
|
1909
|
+
return { messageId, cardId };
|
|
1910
|
+
};
|
|
1911
|
+
for (let i = 0; ; i++) {
|
|
1912
|
+
try {
|
|
1913
|
+
return await attempt();
|
|
1914
|
+
} catch (err) {
|
|
1915
|
+
if (i >= 2 || !isCardIdNotReady(err)) throw err;
|
|
1916
|
+
log.fail("card", err, { phase: "managed-send", attempt: i, retry: true });
|
|
1917
|
+
await new Promise((r) => setTimeout(r, 400 * (i + 1)));
|
|
1918
|
+
}
|
|
1852
1919
|
}
|
|
1853
|
-
byMessageId.set(messageId, { cardId, sequence: 0 });
|
|
1854
|
-
return { messageId, cardId };
|
|
1855
1920
|
}
|
|
1856
1921
|
async function updateManagedCard(channel, messageId, card2) {
|
|
1857
1922
|
const entry = byMessageId.get(messageId);
|
|
@@ -1981,6 +2046,10 @@ function reduce(state, evt) {
|
|
|
1981
2046
|
});
|
|
1982
2047
|
return { ...state, blocks };
|
|
1983
2048
|
}
|
|
2049
|
+
case "context_usage":
|
|
2050
|
+
return { ...state, usage: { used: evt.usedTokens, window: evt.contextWindow } };
|
|
2051
|
+
// context_compacted is surfaced as a standalone notice by the run loop, not
|
|
2052
|
+
// folded into the card — fall through to the no-op default.
|
|
1984
2053
|
case "error":
|
|
1985
2054
|
return { ...state, terminal: "error", errorMsg: evt.message, footer: null };
|
|
1986
2055
|
case "done":
|
|
@@ -2004,14 +2073,14 @@ function markInterrupted(state) {
|
|
|
2004
2073
|
footer: null
|
|
2005
2074
|
};
|
|
2006
2075
|
}
|
|
2007
|
-
function markIdleTimeout(state,
|
|
2076
|
+
function markIdleTimeout(state, seconds) {
|
|
2008
2077
|
return {
|
|
2009
2078
|
...state,
|
|
2010
2079
|
blocks: closeStreamingText(state.blocks),
|
|
2011
2080
|
reasoningActive: false,
|
|
2012
2081
|
terminal: "idle_timeout",
|
|
2013
2082
|
footer: null,
|
|
2014
|
-
|
|
2083
|
+
idleTimeoutSeconds: seconds
|
|
2015
2084
|
};
|
|
2016
2085
|
}
|
|
2017
2086
|
function finalizeIfRunning(state) {
|
|
@@ -2042,8 +2111,8 @@ var RunRender = class {
|
|
|
2042
2111
|
return this.state.terminal;
|
|
2043
2112
|
}
|
|
2044
2113
|
/** Mark the run as watchdog-killed (idle timeout). */
|
|
2045
|
-
timeout(
|
|
2046
|
-
this.state = markIdleTimeout(this.state,
|
|
2114
|
+
timeout(seconds) {
|
|
2115
|
+
this.state = markIdleTimeout(this.state, seconds);
|
|
2047
2116
|
}
|
|
2048
2117
|
/** Mark the run as user-interrupted (⏹). */
|
|
2049
2118
|
interrupt() {
|
|
@@ -2100,6 +2169,9 @@ function image(imgKey, alt = "") {
|
|
|
2100
2169
|
function note(content) {
|
|
2101
2170
|
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
|
|
2102
2171
|
}
|
|
2172
|
+
function colorNote(content, color) {
|
|
2173
|
+
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: color } };
|
|
2174
|
+
}
|
|
2103
2175
|
function hr() {
|
|
2104
2176
|
return { tag: "hr" };
|
|
2105
2177
|
}
|
|
@@ -2318,7 +2390,12 @@ function talkLine(noMention, tail) {
|
|
|
2318
2390
|
function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
2319
2391
|
const elements = [];
|
|
2320
2392
|
if (scope === "single") {
|
|
2321
|
-
const lines = [
|
|
2393
|
+
const lines = [
|
|
2394
|
+
talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"),
|
|
2395
|
+
"\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6",
|
|
2396
|
+
"\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4",
|
|
2397
|
+
"\xB7 `/compact` \u2192 \u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u91CA\u653E\u7A7A\u95F4\uFF09"
|
|
2398
|
+
];
|
|
2322
2399
|
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2323
2400
|
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2324
2401
|
elements.push(md("\u{1F4AC} **\u5355\u4F1A\u8BDD\u7FA4** \u2014 \u6574\u7FA4\u5C31\u662F\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u4E0A\u4E0B\u6587\u8FDE\u7EED\u3002"), hr(), md(lines.join("\n")));
|
|
@@ -2329,6 +2406,8 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
|
2329
2406
|
md(
|
|
2330
2407
|
`${talkLine(noMention, "\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD")}
|
|
2331
2408
|
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2409
|
+
\xB7 \`/context\` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4
|
|
2410
|
+
\xB7 \`/compact\` \u2192 \u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u91CA\u653E\u7A7A\u95F4\uFF09
|
|
2332
2411
|
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2333
2412
|
),
|
|
2334
2413
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
@@ -2363,7 +2442,9 @@ function buildWelcomeCard(kind, docUrl, noMention = true) {
|
|
|
2363
2442
|
"\xB7 **@\u6211 + \u5185\u5BB9** \u2192 \u5F00\u4E00\u4E2A\u65B0\u8BDD\u9898\u5E76\u5F00\u59CB\uFF08\u6BCF\u8BDD\u9898\u72EC\u7ACB\u4F1A\u8BDD\uFF09\n\xB7 `/resume` \u2192 \u6062\u590D\u5386\u53F2\u4F1A\u8BDD\n\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09"
|
|
2364
2443
|
),
|
|
2365
2444
|
md("\u{1F9F5} **\u8BDD\u9898\u5185**"),
|
|
2366
|
-
md(
|
|
2445
|
+
md(
|
|
2446
|
+
"\xB7 \u76F4\u63A5\u53D1\u6D88\u606F\uFF08\u514D@\uFF09\u2192 \u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD\n\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6\n\xB7 `/context` \xB7 `/compact` \u2192 \u770B / \u538B\u7F29\u4E0A\u4E0B\u6587"
|
|
2447
|
+
),
|
|
2367
2448
|
note("\u4EFB\u610F\u573A\u666F\u53D1 `/help` \u770B\u5F53\u524D\u53EF\u7528\u547D\u4EE4\u3002")
|
|
2368
2449
|
);
|
|
2369
2450
|
}
|
|
@@ -2633,7 +2714,80 @@ function escapeInline2(s) {
|
|
|
2633
2714
|
return s.replace(/\s+/g, " ").trim();
|
|
2634
2715
|
}
|
|
2635
2716
|
|
|
2717
|
+
// src/card/context-gauge.ts
|
|
2718
|
+
var CTX_WARN = 0.7;
|
|
2719
|
+
var CTX_HIGH = 0.85;
|
|
2720
|
+
var CTX_CRIT = 0.95;
|
|
2721
|
+
function ctxTier(frac) {
|
|
2722
|
+
if (frac >= CTX_CRIT) return { level: 3, color: "red", dot: "\u{1F534}", advice: "\u5F3A\u70C8\u5EFA\u8BAE `/compact` \u538B\u7F29" };
|
|
2723
|
+
if (frac >= CTX_HIGH) return { level: 2, color: "orange", dot: "\u{1F7E0}", advice: "\u5EFA\u8BAE `/compact` \u538B\u7F29" };
|
|
2724
|
+
if (frac >= CTX_WARN) return { level: 1, color: "yellow", dot: "\u{1F7E1}", advice: "\u53EF\u8003\u8651 `/compact` \u538B\u7F29" };
|
|
2725
|
+
return { level: 0, color: "green", dot: "\u{1F7E2}", advice: "" };
|
|
2726
|
+
}
|
|
2727
|
+
function ctxPercent(used, window) {
|
|
2728
|
+
if (!window || window <= 0) return null;
|
|
2729
|
+
return Math.max(0, Math.min(100, Math.round(used / window * 100)));
|
|
2730
|
+
}
|
|
2731
|
+
function k(n) {
|
|
2732
|
+
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(Math.max(0, Math.round(n)));
|
|
2733
|
+
}
|
|
2734
|
+
function runCardGauge(used, window) {
|
|
2735
|
+
const pct = ctxPercent(used, window);
|
|
2736
|
+
if (pct === null || !window) return null;
|
|
2737
|
+
const frac = used / window;
|
|
2738
|
+
if (frac < CTX_WARN) return null;
|
|
2739
|
+
const t = ctxTier(frac);
|
|
2740
|
+
return colorNote(`${t.dot} \u4E0A\u4E0B\u6587 ${pct}% \xB7 ${k(used)}/${k(window)} \xB7 ${t.advice}`, t.color);
|
|
2741
|
+
}
|
|
2742
|
+
function buildContextCard(used, window) {
|
|
2743
|
+
const pct = ctxPercent(used, window);
|
|
2744
|
+
if (pct === null) {
|
|
2745
|
+
const line = used > 0 ? `\u{1F9E0} \u5DF2\u7528 ${k(used)} tokens\uFF08\u4E0A\u4E0B\u6587\u7A97\u53E3\u672A\u77E5\uFF09` : "\u{1F9E0} \u8FD8\u6CA1\u6709\u7528\u91CF\u6570\u636E\uFF0C\u8DD1\u4E00\u8F6E\u5BF9\u8BDD\u540E\u518D\u770B `/context`\u3002";
|
|
2746
|
+
return card([note(line)], { summary: "\u4E0A\u4E0B\u6587\u7528\u91CF" });
|
|
2747
|
+
}
|
|
2748
|
+
const t = ctxTier(used / window);
|
|
2749
|
+
const els = [colorNote(`${t.dot} **\u4E0A\u4E0B\u6587 ${pct}%** \xB7 ${k(used)}/${k(window)} tokens`, t.color)];
|
|
2750
|
+
els.push(note(t.level >= 1 ? `${t.advice}\uFF1A\u603B\u7ED3\u65E9\u524D\u5BF9\u8BDD\u3001\u91CA\u653E\u7A7A\u95F4\u3002` : "\u7A7A\u95F4\u5145\u8DB3\uFF0C\u65E0\u9700\u538B\u7F29\u3002"));
|
|
2751
|
+
return card(els, { summary: "\u4E0A\u4E0B\u6587\u7528\u91CF" });
|
|
2752
|
+
}
|
|
2753
|
+
var COMPACT_SPINNER = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
2754
|
+
function buildCompactingCard(tick = 0) {
|
|
2755
|
+
const spin = COMPACT_SPINNER[(tick % COMPACT_SPINNER.length + COMPACT_SPINNER.length) % COMPACT_SPINNER.length];
|
|
2756
|
+
return card([colorNote(`\u{1F5DC}\uFE0F \u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587 ${spin}`, "blue"), note("\u603B\u7ED3\u65E9\u524D\u5BF9\u8BDD\u3001\u91CA\u653E\u7A7A\u95F4\uFF0C\u8BF7\u7A0D\u5019\u3002")], {
|
|
2757
|
+
summary: "\u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587"
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
function buildCompactedCard(usage, before) {
|
|
2761
|
+
const els = [colorNote("\u2705 \u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210", "green")];
|
|
2762
|
+
const pct = usage ? ctxPercent(usage.usedTokens, usage.contextWindow) : null;
|
|
2763
|
+
const dropped = usage != null && before != null && usage.usedTokens < before.used;
|
|
2764
|
+
if (usage && pct !== null && usage.contextWindow && (dropped || before == null)) {
|
|
2765
|
+
const beforePct = before ? ctxPercent(before.used, before.window) : null;
|
|
2766
|
+
const from = dropped && beforePct !== null ? `${beforePct}% \u2192 ` : "";
|
|
2767
|
+
els.push(note(`\u65E9\u524D\u5BF9\u8BDD\u5DF2\u603B\u7ED3\u5F52\u6863\uFF0C\u73B0\u5DF2\u7528 ${from}${pct}%\uFF08${k(usage.usedTokens)}/${k(usage.contextWindow)} tokens\uFF09\u3002`));
|
|
2768
|
+
} else {
|
|
2769
|
+
els.push(note("\u65E9\u524D\u5BF9\u8BDD\u5DF2\u603B\u7ED3\u5F52\u6863\u3001\u817E\u51FA\u7A7A\u95F4\u7EE7\u7EED\uFF1B\u53D1\u4E0B\u4E00\u6761\u6D88\u606F\u540E\uFF0C`/context` \u5373\u53EF\u770B\u5230\u5360\u7528\u4E0B\u964D\u3002"));
|
|
2770
|
+
}
|
|
2771
|
+
return card(els, { summary: "\u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210" });
|
|
2772
|
+
}
|
|
2773
|
+
function buildCompactFailedCard(message) {
|
|
2774
|
+
return card([colorNote(`\u26A0\uFE0F \u538B\u7F29\u5931\u8D25\uFF1A${message}`, "red")], { summary: "\u538B\u7F29\u5931\u8D25" });
|
|
2775
|
+
}
|
|
2776
|
+
function buildAutoCompactCard() {
|
|
2777
|
+
return card(
|
|
2778
|
+
[
|
|
2779
|
+
hr(),
|
|
2780
|
+
colorNote("\u{1F5DC}\uFE0F \u2500\u2500\u2500 \u4E0A\u4E0B\u6587\u5DF2\u81EA\u52A8\u538B\u7F29 \u2500\u2500\u2500", "blue"),
|
|
2781
|
+
note("\u65E9\u524D\u5BF9\u8BDD\u5DF2\u603B\u7ED3\u5F52\u6863\u3001\u817E\u51FA\u7A7A\u95F4\u7EE7\u7EED\uFF1B\u6700\u8FD1\u7684\u4E0A\u4E0B\u6587\u4FDD\u7559\u3002")
|
|
2782
|
+
],
|
|
2783
|
+
{ summary: "\u4E0A\u4E0B\u6587\u5DF2\u81EA\u52A8\u538B\u7F29" }
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2636
2787
|
// src/card/run-card.ts
|
|
2788
|
+
function gaugeEl(state) {
|
|
2789
|
+
return state.usage ? runCardGauge(state.usage.used, state.usage.window) : null;
|
|
2790
|
+
}
|
|
2637
2791
|
var RC = {
|
|
2638
2792
|
stop: "run.stop"
|
|
2639
2793
|
};
|
|
@@ -2666,6 +2820,8 @@ function renderRunning(state, rc) {
|
|
|
2666
2820
|
if (answer) elements.push(mdStream(answer, ANSWER_EID));
|
|
2667
2821
|
if (state.footer) elements.push(footerStatus(state.footer));
|
|
2668
2822
|
if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
|
|
2823
|
+
const gauge = gaugeEl(state);
|
|
2824
|
+
if (gauge) elements.push(gauge);
|
|
2669
2825
|
return elements;
|
|
2670
2826
|
}
|
|
2671
2827
|
function renderTerminal(state, rc) {
|
|
@@ -2691,12 +2847,16 @@ function renderTerminal(state, rc) {
|
|
|
2691
2847
|
if (state.terminal === "interrupted") {
|
|
2692
2848
|
elements.push(noteMd("_\u23F9 \u5DF2\u88AB\u4E2D\u65AD_"));
|
|
2693
2849
|
} else if (state.terminal === "idle_timeout") {
|
|
2694
|
-
|
|
2850
|
+
const s = state.idleTimeoutSeconds ?? 0;
|
|
2851
|
+
const idleLabel = s > 0 && s % 60 === 0 ? `${s / 60} \u5206\u949F` : `${s} \u79D2`;
|
|
2852
|
+
elements.push(noteMd(`_\u23F1 ${idleLabel}\u65E0\u54CD\u5E94\uFF0C\u5DF2\u81EA\u52A8\u7EC8\u6B62_`));
|
|
2695
2853
|
} else if (state.terminal === "error" && state.errorMsg) {
|
|
2696
2854
|
elements.push(noteMd(`\u26A0\uFE0F agent \u5931\u8D25\uFF1A${state.errorMsg}`));
|
|
2697
2855
|
} else if (state.terminal === "done" && !answer) {
|
|
2698
2856
|
elements.push(noteMd("_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_"));
|
|
2699
2857
|
}
|
|
2858
|
+
const gauge = gaugeEl(state);
|
|
2859
|
+
if (gauge) elements.push(gauge);
|
|
2700
2860
|
return elements;
|
|
2701
2861
|
}
|
|
2702
2862
|
function lastTextIndex(blocks) {
|
|
@@ -2910,35 +3070,55 @@ var RunCardStream = class {
|
|
|
2910
3070
|
}
|
|
2911
3071
|
}
|
|
2912
3072
|
/** Create the entity from the initial (running) card and send a message
|
|
2913
|
-
* referencing it by card_id. Returns the carrier message id.
|
|
3073
|
+
* referencing it by card_id. Returns the carrier message id.
|
|
3074
|
+
*
|
|
3075
|
+
* A just-created CardKit entity occasionally hasn't propagated when the message
|
|
3076
|
+
* referencing it is sent — Feishu 400s with 230099 / ErrCode 11310 "cardid is
|
|
3077
|
+
* invalid" and the run card silently fails to appear (this surfaced as
|
|
3078
|
+
* intermittent intake.fail). Same transient, same fix as
|
|
3079
|
+
* {@link ../card/managed#sendManagedCard}: retry the whole create+send with a
|
|
3080
|
+
* short backoff. Only this transient retries — Feishu rejected the message
|
|
3081
|
+
* outright (nothing sent), and a re-created entity that's never referenced is a
|
|
3082
|
+
* harmless orphan, so no duplicate card. */
|
|
2914
3083
|
async create(channel, chatId, initialCard, opts) {
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
const cardId = created.data?.card_id;
|
|
2919
|
-
if (!cardId) {
|
|
2920
|
-
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
2921
|
-
}
|
|
2922
|
-
this.cardId = cardId;
|
|
2923
|
-
this.lastContent = JSON.stringify(initialCard);
|
|
2924
|
-
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
2925
|
-
let messageId;
|
|
2926
|
-
if (opts.replyTo) {
|
|
2927
|
-
const r = await channel.rawClient.im.v1.message.reply({
|
|
2928
|
-
path: { message_id: opts.replyTo },
|
|
2929
|
-
data: { msg_type: "interactive", content, reply_in_thread: opts.replyInThread ?? false }
|
|
3084
|
+
const attempt = async () => {
|
|
3085
|
+
const created = await channel.rawClient.cardkit.v1.card.create({
|
|
3086
|
+
data: { type: "card_json", data: JSON.stringify(initialCard) }
|
|
2930
3087
|
});
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3088
|
+
const cardId = created.data?.card_id;
|
|
3089
|
+
if (!cardId) {
|
|
3090
|
+
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
3091
|
+
}
|
|
3092
|
+
this.cardId = cardId;
|
|
3093
|
+
this.lastContent = JSON.stringify(initialCard);
|
|
3094
|
+
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
3095
|
+
let messageId;
|
|
3096
|
+
if (opts.replyTo) {
|
|
3097
|
+
const r = await channel.rawClient.im.v1.message.reply({
|
|
3098
|
+
path: { message_id: opts.replyTo },
|
|
3099
|
+
data: { msg_type: "interactive", content, reply_in_thread: opts.replyInThread ?? false }
|
|
3100
|
+
});
|
|
3101
|
+
messageId = r.data?.message_id;
|
|
3102
|
+
} else {
|
|
3103
|
+
const r = await channel.rawClient.im.v1.message.create({
|
|
3104
|
+
params: { receive_id_type: "chat_id" },
|
|
3105
|
+
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3106
|
+
});
|
|
3107
|
+
messageId = r.data?.message_id;
|
|
3108
|
+
}
|
|
3109
|
+
if (!messageId) throw new Error("run card send returned no message_id");
|
|
3110
|
+
this._messageId = messageId;
|
|
3111
|
+
return messageId;
|
|
3112
|
+
};
|
|
3113
|
+
for (let i = 0; ; i++) {
|
|
3114
|
+
try {
|
|
3115
|
+
return await attempt();
|
|
3116
|
+
} catch (err) {
|
|
3117
|
+
if (i >= 2 || !isCardIdNotReady(err)) throw err;
|
|
3118
|
+
log.fail("card", err, { phase: "run-stream-create", attempt: i, retry: true });
|
|
3119
|
+
await new Promise((r) => setTimeout(r, 400 * (i + 1)));
|
|
3120
|
+
}
|
|
2938
3121
|
}
|
|
2939
|
-
if (!messageId) throw new Error("run card send returned no message_id");
|
|
2940
|
-
this._messageId = messageId;
|
|
2941
|
-
return messageId;
|
|
2942
3122
|
}
|
|
2943
3123
|
/** Throttled whole-card stream update. Skips identical/too-soon pushes;
|
|
2944
3124
|
* `force` flushes regardless (still de-duped on content). */
|
|
@@ -3222,8 +3402,8 @@ async function updateProject(name, patch) {
|
|
|
3222
3402
|
if (!p) return;
|
|
3223
3403
|
const actual = typeof patch === "function" ? patch(p) : patch;
|
|
3224
3404
|
const target = p;
|
|
3225
|
-
for (const [
|
|
3226
|
-
if (v !== void 0) target[
|
|
3405
|
+
for (const [k2, v] of Object.entries(actual)) {
|
|
3406
|
+
if (v !== void 0) target[k2] = v;
|
|
3227
3407
|
}
|
|
3228
3408
|
await write(projects);
|
|
3229
3409
|
});
|
|
@@ -3266,6 +3446,9 @@ var DM = {
|
|
|
3266
3446
|
rmCancel: "dm.rmCancel",
|
|
3267
3447
|
setTools: "dm.set.tools",
|
|
3268
3448
|
setWatchdog: "dm.set.watchdog",
|
|
3449
|
+
// 假死超时「自定义…」:watchdogCustom 打开输入卡,watchdogCustomSubmit 保存任意秒数
|
|
3450
|
+
watchdogCustom: "dm.set.watchdog.custom",
|
|
3451
|
+
watchdogCustomSubmit: "dm.set.watchdog.customSubmit",
|
|
3269
3452
|
setPending: "dm.set.pending",
|
|
3270
3453
|
setConcurrency: "dm.set.concurrency",
|
|
3271
3454
|
// 权限管理:全局 admins(settings 卡进入)+ 项目响应白名单(项目列表 / 建项目完成卡进入)
|
|
@@ -3279,13 +3462,18 @@ var DM = {
|
|
|
3279
3462
|
rmAllowed: "dm.allow.rm",
|
|
3280
3463
|
// 项目设置容器(项目列表 / 建项目完成卡 进入),以后的项目级设置项往这里加
|
|
3281
3464
|
projectSettings: "dm.projectSettings",
|
|
3465
|
+
// 🧵 话题钻取:项目总览的「🧵 N 话题」按钮 → 该项目话题列表卡
|
|
3466
|
+
projectTopics: "dm.projectTopics",
|
|
3282
3467
|
setNoMentionDm: "dm.proj.noMention",
|
|
3468
|
+
// 🗜️ 自动压缩:项目级开关(同群设置里的那个,DM 里也能改),按钮携带项目名 n
|
|
3469
|
+
setAutoCompactDm: "dm.proj.autoCompact",
|
|
3283
3470
|
// 🔐 权限:codex 沙箱档位(管理员档 + 普通用户档)+ 联网,做成下拉表单(选+提交)
|
|
3284
3471
|
permission: "dm.proj.perm",
|
|
3285
3472
|
permissionSubmit: "dm.proj.perm.submit"
|
|
3286
3473
|
};
|
|
3287
3474
|
var GS = {
|
|
3288
|
-
setNoMention: "gs.noMention"
|
|
3475
|
+
setNoMention: "gs.noMention",
|
|
3476
|
+
setAutoCompact: "gs.autoCompact"
|
|
3289
3477
|
};
|
|
3290
3478
|
function kindLabel(kind) {
|
|
3291
3479
|
return kind === "single" ? "\u{1F4AC} \u5355\u4F1A\u8BDD\u7FA4" : "\u{1F465} \u591A\u8BDD\u9898\u7FA4";
|
|
@@ -3558,6 +3746,7 @@ function buildNewProjectDoneCard(p) {
|
|
|
3558
3746
|
);
|
|
3559
3747
|
return card(elements, { header: { title, template: "green" } });
|
|
3560
3748
|
}
|
|
3749
|
+
var PROJECT_TOPICS_MAX = 50;
|
|
3561
3750
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
3562
3751
|
if (projects.length === 0) {
|
|
3563
3752
|
return card(
|
|
@@ -3567,25 +3756,15 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3567
3756
|
}
|
|
3568
3757
|
const elements = [];
|
|
3569
3758
|
for (const p of projects) {
|
|
3759
|
+
const topicCount = (p.chatId ? sessionsByChat.get(p.chatId) : void 0)?.length ?? 0;
|
|
3760
|
+
const dir = `\u{1F4C2} \`${p.cwd}\`${p.branch && p.branch !== "\u2014" ? ` \u{1F33F} ${p.branch}` : ""}`;
|
|
3761
|
+
const meta = p.chatId ? `${kindLabel(p.kind)}${(p.origin ?? "created") === "joined" ? " \xB7 \u{1F517}\u5DF2\u52A0\u5165" : ""} \xB7 \u514D@\uFF1A${p.noMention ?? defaultNoMention(p) ? "\u5F00" : "\u5173"}` : "\u26A0\uFE0F \u672A\u7ED1\u5B9A\u7FA4";
|
|
3570
3762
|
elements.push(md(`**${p.name}**${p.blank ? " _(\u7A7A\u767D)_" : ""}`));
|
|
3571
|
-
elements.push(note(
|
|
3572
|
-
|
|
3573
|
-
note(
|
|
3574
|
-
p.chatId ? `\u{1F4AC} \u7FA4\uFF1A**${p.name}** \xB7 ${kindLabel(p.kind)}${(p.origin ?? "created") === "joined" ? " \xB7 \u{1F517}\u5DF2\u52A0\u5165" : ""} \xB7 \u514D@\uFF1A${p.noMention ?? defaultNoMention(p) ? "\u5F00" : "\u5173"}` : "\u26A0\uFE0F \u672A\u7ED1\u5B9A\u7FA4"
|
|
3575
|
-
)
|
|
3576
|
-
);
|
|
3577
|
-
const sessions = (p.chatId ? sessionsByChat.get(p.chatId) : void 0) ?? [];
|
|
3578
|
-
if (sessions.length === 0) {
|
|
3579
|
-
elements.push(note("\uFF08\u6682\u65E0\u8BDD\u9898\uFF09"));
|
|
3580
|
-
} else {
|
|
3581
|
-
const sorted = [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
3582
|
-
for (const s of sorted) {
|
|
3583
|
-
const title = (s.summary || "(\u7A7A)").replace(/\s+/g, " ").slice(0, 40);
|
|
3584
|
-
elements.push(note(`\xB7 ${title} \xB7 ${relativeTime(s.updatedAt)}`));
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3763
|
+
elements.push(note(`${dir}
|
|
3764
|
+
${meta}`));
|
|
3587
3765
|
const row = [];
|
|
3588
3766
|
if (p.chatId) row.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId)));
|
|
3767
|
+
row.push(button(`\u{1F9F5} ${topicCount} \u8BDD\u9898`, { a: DM.projectTopics, n: p.name }));
|
|
3589
3768
|
row.push(button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: p.name }));
|
|
3590
3769
|
row.push(button("\u{1F5D1} \u5220\u9664", { a: DM.rmConfirm, n: p.name }, "danger"));
|
|
3591
3770
|
elements.push(actions(row));
|
|
@@ -3595,6 +3774,26 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3595
3774
|
elements.push(actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]));
|
|
3596
3775
|
return card(elements, { header: { title: "\u{1F4C1} \u9879\u76EE\u5217\u8868", template: "wathet" } });
|
|
3597
3776
|
}
|
|
3777
|
+
function buildProjectTopicsCard(project, sessions) {
|
|
3778
|
+
const elements = [md(`**${project.name}** \xB7 \u5171 ${sessions.length} \u4E2A\u8BDD\u9898`)];
|
|
3779
|
+
if (sessions.length === 0) {
|
|
3780
|
+
elements.push(note("\uFF08\u6682\u65E0\u8BDD\u9898\uFF09"));
|
|
3781
|
+
} else {
|
|
3782
|
+
const sorted = [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
3783
|
+
for (const s of sorted.slice(0, PROJECT_TOPICS_MAX)) {
|
|
3784
|
+
const title = (s.summary || "(\u7A7A)").replace(/\s+/g, " ").slice(0, 50);
|
|
3785
|
+
elements.push(note(`\xB7 ${title} \xB7 ${relativeTime(s.updatedAt)}`));
|
|
3786
|
+
}
|
|
3787
|
+
if (sorted.length > PROJECT_TOPICS_MAX) {
|
|
3788
|
+
elements.push(note(`\xB7 \u2026\u8FD8\u6709 ${sorted.length - PROJECT_TOPICS_MAX} \u4E2A\u8BDD\u9898\uFF08\u66F4\u65E9\u7684\u53EF\u5728\u7FA4\u91CC \`/resume\` \u6062\u590D\uFF09`));
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
const nav = [];
|
|
3792
|
+
if (project.chatId) nav.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(project.chatId)));
|
|
3793
|
+
nav.push(button("\u2B05\uFE0F \u9879\u76EE\u5217\u8868", { a: DM.projects }));
|
|
3794
|
+
elements.push(hr(), actions(nav));
|
|
3795
|
+
return card(elements, { header: { title: `\u{1F9F5} \u8BDD\u9898 \xB7 ${project.name}`, template: "wathet" } });
|
|
3796
|
+
}
|
|
3598
3797
|
function buildRmConfirmCard(name, origin) {
|
|
3599
3798
|
const note_ = (origin ?? "created") === "joined" ? "\u4EC5\u89E3\u7ED1\uFF08\u79FB\u9664\u6CE8\u518C\uFF09\uFF0C**\u4E0D\u5220\u4EE3\u7801\u76EE\u5F55**\u3002\u786E\u8BA4\u540E**\u6211\u4F1A\u9000\u51FA\u8BE5\u7FA4**\uFF08\u7FA4\u662F\u4F60\u4EEC\u7684\uFF0C\u4E0D\u4F1A\u89E3\u6563\uFF09\u3002" : "\u4EC5\u89E3\u7ED1\uFF08\u79FB\u9664\u6CE8\u518C + \u64A4\u9500\u7F6E\u9876\u6A2A\u5E45\uFF09\uFF0C**\u4E0D\u5220\u4EE3\u7801\u76EE\u5F55**\u3002\u7FA4\u4E3B\u4F1A\u8F6C\u7ED9\u4F60\uFF0C\u518D\u7531\u4F60\u81EA\u884C\u5728\u98DE\u4E66\u89E3\u6563\u7FA4\u3002";
|
|
3600
3799
|
return card(
|
|
@@ -3624,11 +3823,12 @@ function buildSettingsCard(cfg) {
|
|
|
3624
3823
|
{ label: "\u663E\u793A", value: "on" },
|
|
3625
3824
|
{ label: "\u9690\u85CF", value: "off" }
|
|
3626
3825
|
]),
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3826
|
+
md(`\u23F1 \u5047\u6B7B\u8D85\u65F6\uFF08\u5F53\u524D **${watchdogSec === 0 ? "\u5173\u95ED" : `${watchdogSec} \u79D2`}**\uFF09`),
|
|
3827
|
+
actions([
|
|
3828
|
+
...[0, 120, 300].map(
|
|
3829
|
+
(v) => button(v === 0 ? "\u5173\u95ED" : `${v}\u79D2`, { a: DM.setWatchdog, v: String(v) }, v === watchdogSec ? "primary" : "default")
|
|
3830
|
+
),
|
|
3831
|
+
button("\u81EA\u5B9A\u4E49\u2026", { a: DM.watchdogCustom })
|
|
3632
3832
|
]),
|
|
3633
3833
|
...optionRow("\u{1F4E5} \u8FD0\u884C\u4E2D\u65B0\u6D88\u606F", DM.setPending, getPendingPolicy(cfg), [
|
|
3634
3834
|
{ label: "\u5F15\u5BFC", value: "steer" },
|
|
@@ -3640,16 +3840,34 @@ function buildSettingsCard(cfg) {
|
|
|
3640
3840
|
{ label: "10", value: "10" },
|
|
3641
3841
|
{ label: "20", value: "20" }
|
|
3642
3842
|
]),
|
|
3643
|
-
note("\u26A0\uFE0F \
|
|
3843
|
+
note("\u26A0\uFE0F \u5E76\u53D1\u4E0A\u9650 \u6539\u540E\u9700**\u91CD\u542F**\u751F\u6548\uFF1B\u5176\u4F59\u8BBE\u7F6E\uFF08\u542B\u5047\u6B7B\u8D85\u65F6\uFF09\u5373\u65F6\u751F\u6548\uFF0C\u6240\u6709\u7FA4\u7ACB\u5373\u5957\u7528\u3002"),
|
|
3644
3844
|
hr(),
|
|
3645
3845
|
actions([button("\u{1F46E} \u7BA1\u7406\u5458", { a: DM.admins }), button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })])
|
|
3646
3846
|
],
|
|
3647
3847
|
{ header: { title: "\u2699\uFE0F \u8BBE\u7F6E", template: "blue" } }
|
|
3648
3848
|
);
|
|
3649
3849
|
}
|
|
3850
|
+
function buildWatchdogCustomCard(cfg) {
|
|
3851
|
+
const cur = cfg.preferences?.runIdleTimeoutSeconds ?? 120;
|
|
3852
|
+
return card(
|
|
3853
|
+
[
|
|
3854
|
+
md("**\u81EA\u5B9A\u4E49\u5047\u6B7B\u8D85\u65F6**"),
|
|
3855
|
+
note(
|
|
3856
|
+
`\u591A\u5C11\u79D2\u6CA1\u6709\u4EFB\u4F55\u8F93\u51FA\u5C31\u81EA\u52A8\u7EC8\u6B62\u672C\u8F6E\u3002\u8303\u56F4 ${RUN_IDLE_TIMEOUT_MIN_SEC}\u2013${RUN_IDLE_TIMEOUT_MAX_SEC} \u79D2\uFF1B\u586B 0 \u5173\u95ED\u3002`
|
|
3857
|
+
),
|
|
3858
|
+
form("watchdog_custom", [
|
|
3859
|
+
input({ name: "sec", label: "\u8D85\u65F6\u79D2\u6570", placeholder: "\u4F8B\u5982 600", value: String(cur), required: true }),
|
|
3860
|
+
actions([submitButton("\u2705 \u4FDD\u5B58", { a: DM.watchdogCustomSubmit }, "primary", "submit_watchdog")])
|
|
3861
|
+
]),
|
|
3862
|
+
actions([button("\u2B05\uFE0F \u8FD4\u56DE\u8BBE\u7F6E", { a: DM.settings })])
|
|
3863
|
+
],
|
|
3864
|
+
{ header: { title: "\u23F1 \u81EA\u5B9A\u4E49\u8D85\u65F6", template: "blue" } }
|
|
3865
|
+
);
|
|
3866
|
+
}
|
|
3650
3867
|
function buildGroupSettingsCard(project) {
|
|
3651
3868
|
const kind = project.kind ?? "multi";
|
|
3652
3869
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3870
|
+
const autoCompact = project.autoCompact ?? true;
|
|
3653
3871
|
const scopeNote = kind === "single" ? "\u5F00\u542F\u540E\uFF1A\u672C\u7FA4\u6240\u6709\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\u3002" : "\u5F00\u542F\u540E\uFF1A\u8BDD\u9898\u5185\u7684\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\uFF1B**\u5F00\u65B0\u8BDD\u9898\u4ECD\u9700 @\u6211**\u3002";
|
|
3654
3872
|
return card(
|
|
3655
3873
|
[
|
|
@@ -3660,7 +3878,12 @@ function buildGroupSettingsCard(project) {
|
|
|
3660
3878
|
{ label: "\u5173", value: "off" }
|
|
3661
3879
|
]),
|
|
3662
3880
|
note(scopeNote),
|
|
3663
|
-
note("\u26A0\uFE0F \u514D@ \u9700\u5E94\u7528\u5DF2\u5F00\u901A\u300C\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\u300D(im:message.group_msg)\u6743\u9650\uFF0C\u5426\u5219\u6536\u4E0D\u5230\u975E @ \u6D88\u606F\u3002")
|
|
3881
|
+
note("\u26A0\uFE0F \u514D@ \u9700\u5E94\u7528\u5DF2\u5F00\u901A\u300C\u63A5\u6536\u7FA4\u5185\u6240\u6709\u6D88\u606F\u300D(im:message.group_msg)\u6743\u9650\uFF0C\u5426\u5219\u6536\u4E0D\u5230\u975E @ \u6D88\u606F\u3002"),
|
|
3882
|
+
...optionRow("\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u4E0A\u4E0B\u6587", GS.setAutoCompact, autoCompact ? "on" : "off", [
|
|
3883
|
+
{ label: "\u5F00", value: "on" },
|
|
3884
|
+
{ label: "\u5173", value: "off" }
|
|
3885
|
+
]),
|
|
3886
|
+
note("\u5F00\u542F\u540E\uFF1A\u4E0A\u4E0B\u6587\u63A5\u8FD1\u4E0A\u9650\u65F6 Codex \u81EA\u52A8\u603B\u7ED3\u65E9\u524D\u5BF9\u8BDD\u3001\u91CA\u653E\u7A7A\u95F4\uFF08\u9ED8\u8BA4\u5F00\uFF09\u3002\u6539\u52A8\u4E0B\u4E00\u8F6E\u4F1A\u8BDD\u751F\u6548\u3002")
|
|
3664
3887
|
],
|
|
3665
3888
|
{ header: { title: "\u2699\uFE0F \u7FA4\u8BBE\u7F6E", template: "blue" } }
|
|
3666
3889
|
);
|
|
@@ -3778,6 +4001,7 @@ function buildPermissionCard(p) {
|
|
|
3778
4001
|
function buildProjectSettingsCard(project) {
|
|
3779
4002
|
const kind = project.kind ?? "multi";
|
|
3780
4003
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
4004
|
+
const autoCompact = project.autoCompact ?? true;
|
|
3781
4005
|
return card(
|
|
3782
4006
|
[
|
|
3783
4007
|
md(`**\u9879\u76EE\u8BBE\u7F6E** \xB7 ${project.name}`),
|
|
@@ -3795,6 +4019,13 @@ function buildProjectSettingsCard(project) {
|
|
|
3795
4019
|
kind === "single" ? "\u5F00\u542F\u540E\uFF1A\u672C\u7FA4\u6240\u6709\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u4EA4\u7ED9\u6211\u5904\u7406\u3002" : "\u5F00\u542F\u540E\uFF1A\u8BDD\u9898\u5185\u6D88\u606F(\u4E0D\u7528 @)\u90FD\u5904\u7406\uFF1B**\u5F00\u65B0\u8BDD\u9898\u4ECD\u9700 @\u6211**\u3002"
|
|
3796
4020
|
),
|
|
3797
4021
|
hr(),
|
|
4022
|
+
md("\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u4E0A\u4E0B\u6587"),
|
|
4023
|
+
actions([
|
|
4024
|
+
button("\u5F00", { a: DM.setAutoCompactDm, v: "on", n: project.name }, autoCompact ? "primary" : "default"),
|
|
4025
|
+
button("\u5173", { a: DM.setAutoCompactDm, v: "off", n: project.name }, autoCompact ? "default" : "primary")
|
|
4026
|
+
]),
|
|
4027
|
+
note("\u5F00\u542F\u540E\uFF1A\u4E0A\u4E0B\u6587\u63A5\u8FD1\u4E0A\u9650\u65F6 Codex \u81EA\u52A8\u603B\u7ED3\u65E9\u524D\u5BF9\u8BDD\u3001\u91CA\u653E\u7A7A\u95F4\uFF08\u9ED8\u8BA4\u5F00\uFF09\u3002\u6539\u52A8\u4E0B\u4E00\u8F6E\u4F1A\u8BDD\u751F\u6548\u3002"),
|
|
4028
|
+
hr(),
|
|
3798
4029
|
actions([button("\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", { a: DM.allowlist, n: project.name }, "primary")]),
|
|
3799
4030
|
note("\u8BBE\u7F6E\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex\uFF08\u7A7A = \u6240\u6709\u4EBA\uFF09\u3002"),
|
|
3800
4031
|
hr(),
|
|
@@ -5340,8 +5571,8 @@ async function patchSession(threadId, patch) {
|
|
|
5340
5571
|
const rec = sessions.find((s) => s.threadId === threadId);
|
|
5341
5572
|
if (!rec) return;
|
|
5342
5573
|
const target = rec;
|
|
5343
|
-
for (const [
|
|
5344
|
-
if (v !== void 0) target[
|
|
5574
|
+
for (const [k2, v] of Object.entries(patch)) {
|
|
5575
|
+
if (v !== void 0) target[k2] = v;
|
|
5345
5576
|
}
|
|
5346
5577
|
rec.updatedAt = Date.now();
|
|
5347
5578
|
await write2(sessions);
|
|
@@ -5459,7 +5690,7 @@ function walkForImageKeys(node, out) {
|
|
|
5459
5690
|
}
|
|
5460
5691
|
const obj = node;
|
|
5461
5692
|
if (obj.tag === "img" && typeof obj.image_key === "string") out.push(obj.image_key);
|
|
5462
|
-
for (const
|
|
5693
|
+
for (const k2 of Object.keys(obj)) walkForImageKeys(obj[k2], out);
|
|
5463
5694
|
}
|
|
5464
5695
|
async function downloadOne(channel, ref, index) {
|
|
5465
5696
|
try {
|
|
@@ -6093,13 +6324,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6093
6324
|
const active = /* @__PURE__ */ new Map();
|
|
6094
6325
|
const docLocks = /* @__PURE__ */ new Map();
|
|
6095
6326
|
const sema = new Semaphore(getMaxConcurrentRuns(cfg));
|
|
6096
|
-
const
|
|
6327
|
+
const currentIdleMs = () => getRunIdleTimeoutMs(cfg) ?? 0;
|
|
6097
6328
|
const resumePending = /* @__PURE__ */ new Map();
|
|
6098
6329
|
const modelPending = /* @__PURE__ */ new Map();
|
|
6099
6330
|
const runsByCard = /* @__PURE__ */ new Map();
|
|
6100
6331
|
const runCards = /* @__PURE__ */ new Map();
|
|
6101
6332
|
const runStreams = /* @__PURE__ */ new Map();
|
|
6102
6333
|
const lastRunCard = /* @__PURE__ */ new Map();
|
|
6334
|
+
const lastUsage = /* @__PURE__ */ new Map();
|
|
6103
6335
|
let modelsCache = null;
|
|
6104
6336
|
async function listModels() {
|
|
6105
6337
|
if (!modelsCache) modelsCache = await backend.listModels();
|
|
@@ -6195,6 +6427,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6195
6427
|
await postModelCard(msg, ts.sessionKey);
|
|
6196
6428
|
return;
|
|
6197
6429
|
}
|
|
6430
|
+
if (cmd === "compact") {
|
|
6431
|
+
await runCompact(msg, ts.sessionKey, false, ts);
|
|
6432
|
+
return;
|
|
6433
|
+
}
|
|
6434
|
+
if (cmd === "context") {
|
|
6435
|
+
await postContextCard(msg, ts.sessionKey, false);
|
|
6436
|
+
return;
|
|
6437
|
+
}
|
|
6198
6438
|
handleTurn(msg, text, ts.sessionKey, true, project, ts);
|
|
6199
6439
|
return;
|
|
6200
6440
|
}
|
|
@@ -6208,6 +6448,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6208
6448
|
await postModelCard(msg, ts.sessionKey);
|
|
6209
6449
|
return;
|
|
6210
6450
|
}
|
|
6451
|
+
if (cmd === "compact") {
|
|
6452
|
+
await runCompact(msg, ts.sessionKey, true, ts);
|
|
6453
|
+
return;
|
|
6454
|
+
}
|
|
6455
|
+
if (cmd === "context") {
|
|
6456
|
+
await postContextCard(msg, ts.sessionKey, true);
|
|
6457
|
+
return;
|
|
6458
|
+
}
|
|
6211
6459
|
handleTurn(msg, text, ts.sessionKey, false, project, ts);
|
|
6212
6460
|
return;
|
|
6213
6461
|
}
|
|
@@ -6223,8 +6471,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6223
6471
|
await postGroupSettings(msg, project);
|
|
6224
6472
|
return;
|
|
6225
6473
|
}
|
|
6226
|
-
if (cmd === "model") {
|
|
6227
|
-
await channel.send(msg.chatId, { markdown:
|
|
6474
|
+
if (cmd === "model" || cmd === "compact" || cmd === "context") {
|
|
6475
|
+
await channel.send(msg.chatId, { markdown: `\`/${cmd}\` \u9700\u8981\u5728\u8BDD\u9898\u91CC\u4F7F\u7528\uFF08\u5148 @\u6211 \u5F00\u4E2A\u8BDD\u9898\uFF09\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
6228
6476
|
return;
|
|
6229
6477
|
}
|
|
6230
6478
|
startTopicDirectly(msg, text, project);
|
|
@@ -6232,7 +6480,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6232
6480
|
function parseCommand(text) {
|
|
6233
6481
|
const m = /^\/(\w+)/.exec(text);
|
|
6234
6482
|
const name = m?.[1]?.toLowerCase();
|
|
6235
|
-
return name === "resume" || name === "model" || name === "settings" || name === "help" ? name : null;
|
|
6483
|
+
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" ? name : null;
|
|
6236
6484
|
}
|
|
6237
6485
|
function shouldRespondWithoutMention(project, msg) {
|
|
6238
6486
|
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
@@ -6261,7 +6509,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6261
6509
|
function turnPerm(project, senderId) {
|
|
6262
6510
|
if (!project) return {};
|
|
6263
6511
|
const t = turnTier(project, isAdmin(cfg, senderId));
|
|
6264
|
-
return { mode: t.mode, network: project.network, roleSuffix: t.split ? t.role : void 0 };
|
|
6512
|
+
return { mode: t.mode, network: project.network, autoCompact: project.autoCompact, roleSuffix: t.split ? t.role : void 0 };
|
|
6265
6513
|
}
|
|
6266
6514
|
function turnSession(baseKey, project, senderId) {
|
|
6267
6515
|
const perm = turnPerm(project, senderId);
|
|
@@ -6326,7 +6574,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6326
6574
|
let firstText = preIngested ? text : await ingestContext(msg, text);
|
|
6327
6575
|
const { thread: resolved, recreated } = await resolveThread(sessionKey, msg.chatId, {
|
|
6328
6576
|
mode: perm.mode,
|
|
6329
|
-
network: perm.network
|
|
6577
|
+
network: perm.network,
|
|
6578
|
+
autoCompact: perm.autoCompact
|
|
6330
6579
|
});
|
|
6331
6580
|
let thread = resolved;
|
|
6332
6581
|
const neverSeen = !thread;
|
|
@@ -6334,7 +6583,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6334
6583
|
const prior = neverSeen ? void 0 : await getSession(sessionKey);
|
|
6335
6584
|
if (!thread) {
|
|
6336
6585
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
6337
|
-
thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network });
|
|
6586
|
+
thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network, autoCompact: perm.autoCompact });
|
|
6338
6587
|
sessions.set(sessionKey, thread);
|
|
6339
6588
|
await upsertSession({
|
|
6340
6589
|
threadId: sessionKey,
|
|
@@ -6393,7 +6642,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6393
6642
|
model: rec.model,
|
|
6394
6643
|
effort: rec.effort,
|
|
6395
6644
|
mode: perm?.mode,
|
|
6396
|
-
network: perm?.network
|
|
6645
|
+
network: perm?.network,
|
|
6646
|
+
autoCompact: perm?.autoCompact
|
|
6397
6647
|
});
|
|
6398
6648
|
sessions.set(threadId, resumed);
|
|
6399
6649
|
return { thread: resumed, recreated: false };
|
|
@@ -6406,7 +6656,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6406
6656
|
model: rec.model,
|
|
6407
6657
|
effort: rec.effort,
|
|
6408
6658
|
mode: perm?.mode ?? project?.mode,
|
|
6409
|
-
network: perm?.network ?? project?.network
|
|
6659
|
+
network: perm?.network ?? project?.network,
|
|
6660
|
+
autoCompact: perm?.autoCompact ?? project?.autoCompact
|
|
6410
6661
|
});
|
|
6411
6662
|
sessions.set(threadId, fresh);
|
|
6412
6663
|
await patchSession(threadId, { codexThreadId: fresh.codexThreadId }).catch(() => void 0);
|
|
@@ -6434,7 +6685,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6434
6685
|
const { model, effort } = pickDefault(await listModels());
|
|
6435
6686
|
let thread;
|
|
6436
6687
|
try {
|
|
6437
|
-
thread = await backend.startThread({ cwd, model, effort, mode: perm.mode, network: perm.network });
|
|
6688
|
+
thread = await backend.startThread({ cwd, model, effort, mode: perm.mode, network: perm.network, autoCompact: perm.autoCompact });
|
|
6438
6689
|
} catch (err) {
|
|
6439
6690
|
reaction.done();
|
|
6440
6691
|
log.fail("card", err, { phase: "start-topic" });
|
|
@@ -6508,6 +6759,76 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6508
6759
|
log.info("card", "model", { threadId: sessionKey, model: state.model, effort: state.effort });
|
|
6509
6760
|
});
|
|
6510
6761
|
}
|
|
6762
|
+
async function postContextCard(msg, sessionKey, inThread) {
|
|
6763
|
+
const u = lastUsage.get(sessionKey);
|
|
6764
|
+
await sendManagedCard(channel, msg.chatId, buildContextCard(u?.used ?? 0, u?.window ?? null), msg.messageId, inThread).catch(
|
|
6765
|
+
(err) => log.fail("card", err, { phase: "context" })
|
|
6766
|
+
);
|
|
6767
|
+
}
|
|
6768
|
+
const COMPACT_ANIM_INTERVAL_MS = 800;
|
|
6769
|
+
async function runCompact(msg, sessionKey, inThread, perm) {
|
|
6770
|
+
const reply = (markdown) => channel.send(msg.chatId, { markdown }, { replyTo: msg.messageId, replyInThread: inThread }).then(() => void 0, () => void 0);
|
|
6771
|
+
if (active.get(sessionKey)) {
|
|
6772
|
+
await reply("\u23F3 \u8FD9\u4E00\u8F6E\u8FD8\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D `/compact`\u3002");
|
|
6773
|
+
return;
|
|
6774
|
+
}
|
|
6775
|
+
const { thread } = await resolveThread(sessionKey, msg.chatId, {
|
|
6776
|
+
mode: perm.mode,
|
|
6777
|
+
network: perm.network,
|
|
6778
|
+
autoCompact: perm.autoCompact
|
|
6779
|
+
});
|
|
6780
|
+
if (!thread) {
|
|
6781
|
+
await reply("\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u5F00\u59CB\uFF0C\u5148\u53D1\u6761\u6D88\u606F\u804A\u4E24\u53E5\u518D `/compact`\u3002");
|
|
6782
|
+
return;
|
|
6783
|
+
}
|
|
6784
|
+
let cardMsgId;
|
|
6785
|
+
try {
|
|
6786
|
+
const sent = await sendManagedCard(channel, msg.chatId, buildCompactingCard(0), msg.messageId, inThread);
|
|
6787
|
+
cardMsgId = sent.messageId;
|
|
6788
|
+
} catch (err) {
|
|
6789
|
+
log.fail("card", err, { phase: "compact-start-card" });
|
|
6790
|
+
}
|
|
6791
|
+
let stop = false;
|
|
6792
|
+
const wakers = [];
|
|
6793
|
+
const sleep = (ms) => new Promise((res) => {
|
|
6794
|
+
const t = setTimeout(res, ms);
|
|
6795
|
+
wakers.push(() => {
|
|
6796
|
+
clearTimeout(t);
|
|
6797
|
+
res();
|
|
6798
|
+
});
|
|
6799
|
+
});
|
|
6800
|
+
const anim = (async () => {
|
|
6801
|
+
let tick = 0;
|
|
6802
|
+
while (!stop && cardMsgId) {
|
|
6803
|
+
await sleep(COMPACT_ANIM_INTERVAL_MS);
|
|
6804
|
+
if (stop || !cardMsgId) break;
|
|
6805
|
+
tick++;
|
|
6806
|
+
await updateManagedCard(channel, cardMsgId, buildCompactingCard(tick)).catch(() => void 0);
|
|
6807
|
+
}
|
|
6808
|
+
})();
|
|
6809
|
+
const settle = async (result) => {
|
|
6810
|
+
stop = true;
|
|
6811
|
+
wakers.forEach((w) => w());
|
|
6812
|
+
await anim;
|
|
6813
|
+
if (cardMsgId && await updateManagedCard(channel, cardMsgId, result)) return;
|
|
6814
|
+
await sendManagedCard(channel, msg.chatId, result, msg.messageId, inThread).catch(
|
|
6815
|
+
(err) => log.fail("card", err, { phase: "compact-settle" })
|
|
6816
|
+
);
|
|
6817
|
+
};
|
|
6818
|
+
const before = lastUsage.get(sessionKey) ?? null;
|
|
6819
|
+
try {
|
|
6820
|
+
const { usage } = await thread.compact();
|
|
6821
|
+
if (usage) lastUsage.set(sessionKey, { used: usage.usedTokens, window: usage.contextWindow });
|
|
6822
|
+
else lastUsage.delete(sessionKey);
|
|
6823
|
+
log.info("intake", "compact", { sessionKey, used: usage?.usedTokens ?? null, before: before?.used ?? null });
|
|
6824
|
+
await settle(buildCompactedCard(usage, before));
|
|
6825
|
+
} catch (err) {
|
|
6826
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
6827
|
+
const unsupported = /method not found|-32601|unknown (method|request)/i.test(m);
|
|
6828
|
+
log.fail("intake", err, { phase: "compact" });
|
|
6829
|
+
await settle(buildCompactFailedCard(unsupported ? "\u5F53\u524D codex \u7248\u672C\u4E0D\u652F\u6301 /compact\uFF0C\u8BF7\u5347\u7EA7\u540E\u518D\u8BD5\u3002" : m));
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6511
6832
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
6512
6833
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
6513
6834
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
@@ -6523,24 +6844,26 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6523
6844
|
const settleUpdate = (msgId, c, fallbackChatId) => {
|
|
6524
6845
|
const armedAt = Date.now();
|
|
6525
6846
|
void (async () => {
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6847
|
+
try {
|
|
6848
|
+
await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
|
|
6849
|
+
const card2 = typeof c === "function" ? await c() : c;
|
|
6850
|
+
const ok = await updateManagedCard(channel, msgId, card2);
|
|
6851
|
+
log.info("console", "settle-update", { msgId, ok, waitedMs: Date.now() - armedAt, fallback: !ok && !!fallbackChatId });
|
|
6852
|
+
if (!ok && fallbackChatId) {
|
|
6853
|
+
await sendManagedCard(channel, fallbackChatId, card2);
|
|
6854
|
+
}
|
|
6855
|
+
} catch (err) {
|
|
6856
|
+
log.fail("console", err, { phase: "settle-update", msgId });
|
|
6534
6857
|
}
|
|
6535
6858
|
})();
|
|
6536
6859
|
};
|
|
6537
6860
|
function pruneResumePending() {
|
|
6538
6861
|
const now = Date.now();
|
|
6539
|
-
for (const [
|
|
6862
|
+
for (const [k2, s] of resumePending) if (now - s.createdAt > PENDING_TTL_MS) resumePending.delete(k2);
|
|
6540
6863
|
}
|
|
6541
6864
|
function pruneModelPending() {
|
|
6542
6865
|
const now = Date.now();
|
|
6543
|
-
for (const [
|
|
6866
|
+
for (const [k2, s] of modelPending) if (now - s.createdAt > PENDING_TTL_MS) modelPending.delete(k2);
|
|
6544
6867
|
}
|
|
6545
6868
|
function authPending(map, evt) {
|
|
6546
6869
|
const state = map.get(evt.messageId);
|
|
@@ -6875,6 +7198,18 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
6875
7198
|
}).on(DM.setWatchdog, ({ evt, value }) => {
|
|
6876
7199
|
const n = Number(value.v);
|
|
6877
7200
|
if (Number.isFinite(n)) applyPref(evt, (p) => p.runIdleTimeoutSeconds = n);
|
|
7201
|
+
}).on(DM.watchdogCustom, ({ evt }) => {
|
|
7202
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7203
|
+
void patch(evt, buildWatchdogCustomCard(cfg));
|
|
7204
|
+
}).on(DM.watchdogCustomSubmit, ({ evt, formValue }) => {
|
|
7205
|
+
const raw = String(formValue?.sec ?? "").trim();
|
|
7206
|
+
const n = Number(raw);
|
|
7207
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
7208
|
+
void patch(evt, buildWatchdogCustomCard(cfg));
|
|
7209
|
+
return;
|
|
7210
|
+
}
|
|
7211
|
+
const sec = n === 0 ? 0 : Math.min(Math.max(Math.floor(n), RUN_IDLE_TIMEOUT_MIN_SEC), RUN_IDLE_TIMEOUT_MAX_SEC);
|
|
7212
|
+
applyPref(evt, (p) => p.runIdleTimeoutSeconds = sec);
|
|
6878
7213
|
}).on(DM.setPending, ({ evt, value }) => {
|
|
6879
7214
|
if (value.v === "steer" || value.v === "queue") applyPref(evt, (p) => p.pendingPolicy = value.v);
|
|
6880
7215
|
}).on(DM.setConcurrency, ({ evt, value }) => {
|
|
@@ -6892,6 +7227,19 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
6892
7227
|
}
|
|
6893
7228
|
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", noMention: on });
|
|
6894
7229
|
});
|
|
7230
|
+
}).on(GS.setAutoCompact, ({ evt, value }) => {
|
|
7231
|
+
if (!isAdmin(cfg, evt.operator?.openId ?? "")) return;
|
|
7232
|
+
const on = value.v === "on";
|
|
7233
|
+
patch(evt, async () => {
|
|
7234
|
+
const project = await getProjectByChatId(evt.chatId);
|
|
7235
|
+
if (project) {
|
|
7236
|
+
await updateProject(project.name, { autoCompact: on });
|
|
7237
|
+
await evictLiveSessionsForChat(project.chatId);
|
|
7238
|
+
log.info("console", "group-autocompact", { project: project.name, on });
|
|
7239
|
+
return buildGroupSettingsCard({ ...project, autoCompact: on });
|
|
7240
|
+
}
|
|
7241
|
+
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", autoCompact: on });
|
|
7242
|
+
});
|
|
6895
7243
|
}).on(DM.admins, ({ evt }) => {
|
|
6896
7244
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6897
7245
|
patch(
|
|
@@ -6983,6 +7331,15 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
6983
7331
|
const p = await getProjectByName(name);
|
|
6984
7332
|
return p ? buildProjectSettingsCard(p) : buildDmMenuCard();
|
|
6985
7333
|
});
|
|
7334
|
+
}).on(DM.projectTopics, ({ evt, value }) => {
|
|
7335
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7336
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
7337
|
+
patch(evt, async () => {
|
|
7338
|
+
const p = await getProjectByName(name);
|
|
7339
|
+
if (!p) return buildDmMenuCard();
|
|
7340
|
+
const sessions2 = (await listSessions()).filter((s) => s.chatId === p.chatId);
|
|
7341
|
+
return buildProjectTopicsCard(p, sessions2);
|
|
7342
|
+
});
|
|
6986
7343
|
}).on(DM.setNoMentionDm, ({ evt, value }) => {
|
|
6987
7344
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6988
7345
|
const name = typeof value.n === "string" ? value.n : "";
|
|
@@ -6993,6 +7350,18 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
6993
7350
|
await updateProject(name, { noMention: on });
|
|
6994
7351
|
return buildProjectSettingsCard({ ...p, noMention: on });
|
|
6995
7352
|
});
|
|
7353
|
+
}).on(DM.setAutoCompactDm, ({ evt, value }) => {
|
|
7354
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7355
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
7356
|
+
const on = value.v === "on";
|
|
7357
|
+
patch(evt, async () => {
|
|
7358
|
+
const p = await getProjectByName(name);
|
|
7359
|
+
if (!p) return buildDmMenuCard();
|
|
7360
|
+
await updateProject(name, { autoCompact: on });
|
|
7361
|
+
await evictLiveSessionsForChat(p.chatId);
|
|
7362
|
+
log.info("console", "project-autocompact", { project: name, on });
|
|
7363
|
+
return buildProjectSettingsCard({ ...p, autoCompact: on });
|
|
7364
|
+
});
|
|
6996
7365
|
}).on(DM.permission, ({ evt, value }) => {
|
|
6997
7366
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6998
7367
|
const name = typeof value.n === "string" ? value.n : "";
|
|
@@ -7158,6 +7527,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7158
7527
|
interrupted = true;
|
|
7159
7528
|
resolveStop();
|
|
7160
7529
|
};
|
|
7530
|
+
const idleMs = currentIdleMs();
|
|
7161
7531
|
const guarded = withIdleTimeout(
|
|
7162
7532
|
run.events,
|
|
7163
7533
|
idleMs,
|
|
@@ -7183,6 +7553,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7183
7553
|
}
|
|
7184
7554
|
lastEvAt = tEv;
|
|
7185
7555
|
evCount++;
|
|
7556
|
+
if (et === "context_usage" && topicThreadId) {
|
|
7557
|
+
const cu = ev;
|
|
7558
|
+
lastUsage.set(topicThreadId, { used: cu.usedTokens, window: cu.contextWindow });
|
|
7559
|
+
} else if (et === "context_compacted") {
|
|
7560
|
+
void sendManagedCard(channel, opts.chatId, buildAutoCompactCard(), cardMsgId, !opts.flat).catch(
|
|
7561
|
+
(err) => log.fail("card", err, { phase: "auto-compact-notice" })
|
|
7562
|
+
);
|
|
7563
|
+
}
|
|
7186
7564
|
render.apply(ev);
|
|
7187
7565
|
rc.rs = render.snapshot();
|
|
7188
7566
|
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
@@ -7191,7 +7569,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7191
7569
|
await stream2.drain();
|
|
7192
7570
|
state.interrupt = void 0;
|
|
7193
7571
|
const killed = interrupted || timedOut;
|
|
7194
|
-
if (timedOut) render.timeout(Math.
|
|
7572
|
+
if (timedOut) render.timeout(Math.round(idleMs / 1e3));
|
|
7195
7573
|
else if (interrupted) render.interrupt();
|
|
7196
7574
|
else render.finalize();
|
|
7197
7575
|
rc.rs = render.snapshot();
|
|
@@ -7284,7 +7662,7 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7284
7662
|
const run = thread.runStreamed({ text: prompt }, { model: rec?.model, effort: rec?.effort });
|
|
7285
7663
|
let state = initialState;
|
|
7286
7664
|
let timedOut = false;
|
|
7287
|
-
const guarded = withIdleTimeout(run.events,
|
|
7665
|
+
const guarded = withIdleTimeout(run.events, currentIdleMs(), () => {
|
|
7288
7666
|
timedOut = true;
|
|
7289
7667
|
});
|
|
7290
7668
|
for await (const ev of guarded) state = reduce(state, ev);
|