@modelzen/feishu-codex-bridge 0.3.8 → 0.3.10
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 +826 -157
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -600,9 +600,9 @@ async function spawnExecProvider(pc, ref) {
|
|
|
600
600
|
const providerName = ref.provider ?? DEFAULT_PROVIDER;
|
|
601
601
|
return new Promise((resolve7, reject) => {
|
|
602
602
|
const env = {};
|
|
603
|
-
if (pc.passEnv) for (const
|
|
604
|
-
const v = process.env[
|
|
605
|
-
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;
|
|
606
606
|
}
|
|
607
607
|
if (pc.env) Object.assign(env, pc.env);
|
|
608
608
|
const child = spawnProcess(pc.command, pc.args ?? [], {
|
|
@@ -948,11 +948,11 @@ function emit(level, phase, event, fields = {}) {
|
|
|
948
948
|
event,
|
|
949
949
|
...ctx
|
|
950
950
|
};
|
|
951
|
-
for (const [
|
|
952
|
-
if (RESERVED_KEYS.has(
|
|
953
|
-
entry[`_${
|
|
951
|
+
for (const [k2, v] of Object.entries(fields)) {
|
|
952
|
+
if (RESERVED_KEYS.has(k2)) {
|
|
953
|
+
entry[`_${k2}`] = v;
|
|
954
954
|
} else {
|
|
955
|
-
entry[
|
|
955
|
+
entry[k2] = v;
|
|
956
956
|
}
|
|
957
957
|
}
|
|
958
958
|
const s = getStream();
|
|
@@ -1003,20 +1003,20 @@ function formatFields(fields) {
|
|
|
1003
1003
|
const keys = Object.keys(fields);
|
|
1004
1004
|
if (keys.length === 0) return "";
|
|
1005
1005
|
const parts = [];
|
|
1006
|
-
for (const
|
|
1007
|
-
const v = fields[
|
|
1006
|
+
for (const k2 of keys) {
|
|
1007
|
+
const v = fields[k2];
|
|
1008
1008
|
if (v === void 0 || v === null) continue;
|
|
1009
|
-
if (
|
|
1009
|
+
if (k2 === "stack") continue;
|
|
1010
1010
|
if (typeof v === "string") {
|
|
1011
|
-
parts.push(`${
|
|
1011
|
+
parts.push(`${k2}=${v.length > 80 ? `${v.slice(0, 80)}\u2026` : v}`);
|
|
1012
1012
|
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
1013
|
-
parts.push(`${
|
|
1013
|
+
parts.push(`${k2}=${v}`);
|
|
1014
1014
|
} else {
|
|
1015
1015
|
try {
|
|
1016
1016
|
const str = JSON.stringify(v);
|
|
1017
|
-
parts.push(`${
|
|
1017
|
+
parts.push(`${k2}=${str.length > 80 ? `${str.slice(0, 80)}\u2026` : str}`);
|
|
1018
1018
|
} catch {
|
|
1019
|
-
parts.push(`${
|
|
1019
|
+
parts.push(`${k2}=?`);
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
1022
|
}
|
|
@@ -1189,6 +1189,14 @@ ${rule}`);
|
|
|
1189
1189
|
// src/bot/bridge.ts
|
|
1190
1190
|
import { createLarkChannel, Domain } from "@larksuiteoapi/node-sdk";
|
|
1191
1191
|
|
|
1192
|
+
// src/agent/types.ts
|
|
1193
|
+
function isGoalTerminal(status) {
|
|
1194
|
+
return status === "complete" || status === "budgetLimited" || status === "usageLimited" || status === "blocked";
|
|
1195
|
+
}
|
|
1196
|
+
function isGoalSuccess(status) {
|
|
1197
|
+
return status === "complete";
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1192
1200
|
// src/agent/codex-appserver/app-server-client.ts
|
|
1193
1201
|
var AsyncQueue = class {
|
|
1194
1202
|
items = [];
|
|
@@ -1252,7 +1260,11 @@ var AppServerClient = class {
|
|
|
1252
1260
|
child.on("error", (err) => this.failAllPending(err));
|
|
1253
1261
|
await this.request("initialize", {
|
|
1254
1262
|
clientInfo: { name: this.opts.clientName ?? "feishu-codex-bridge", version: "0.0.1" },
|
|
1255
|
-
|
|
1263
|
+
// experimentalApi opts into experimental JSON-RPC methods + fields — REQUIRED
|
|
1264
|
+
// for the goal RPCs (thread/goal/set|get|clear). Verified against codex 0.139:
|
|
1265
|
+
// without it, thread/goal/set is rejected. The `goals` feature itself is
|
|
1266
|
+
// stable+on by default there, so no experimentalFeature/enablement/set needed.
|
|
1267
|
+
capabilities: { experimentalApi: true, requestAttestation: false }
|
|
1256
1268
|
});
|
|
1257
1269
|
this.notify("initialized");
|
|
1258
1270
|
}
|
|
@@ -1380,8 +1392,28 @@ function mapNotification(n) {
|
|
|
1380
1392
|
return mapItemStart(n.params.item);
|
|
1381
1393
|
case "item/completed":
|
|
1382
1394
|
return mapItemComplete(n.params.item);
|
|
1395
|
+
case "thread/tokenUsage/updated":
|
|
1396
|
+
return {
|
|
1397
|
+
type: "context_usage",
|
|
1398
|
+
usedTokens: n.params.tokenUsage.last.totalTokens,
|
|
1399
|
+
contextWindow: n.params.tokenUsage.modelContextWindow
|
|
1400
|
+
};
|
|
1401
|
+
case "thread/compacted":
|
|
1402
|
+
return { type: "context_compacted" };
|
|
1383
1403
|
case "turn/completed":
|
|
1384
1404
|
return { type: "done", turnId: n.params.turn.id };
|
|
1405
|
+
case "thread/goal/updated": {
|
|
1406
|
+
const g = n.params.goal;
|
|
1407
|
+
return {
|
|
1408
|
+
type: "goal_update",
|
|
1409
|
+
status: g.status,
|
|
1410
|
+
objective: g.objective,
|
|
1411
|
+
tokensUsed: g.tokensUsed,
|
|
1412
|
+
timeUsedSeconds: g.timeUsedSeconds,
|
|
1413
|
+
tokenBudget: g.tokenBudget
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
// thread/goal/cleared — we clear goals ourselves; nothing to surface.
|
|
1385
1417
|
case "error":
|
|
1386
1418
|
return { type: "error", message: n.params.error.message, willRetry: n.params.willRetry };
|
|
1387
1419
|
default:
|
|
@@ -1430,6 +1462,12 @@ function mapItemComplete(item) {
|
|
|
1430
1462
|
|
|
1431
1463
|
// src/agent/codex-appserver/backend.ts
|
|
1432
1464
|
var APPROVAL_POLICY = "never";
|
|
1465
|
+
var AUTO_COMPACT_OFF_LIMIT = 1e9;
|
|
1466
|
+
function withAutoCompact(params, autoCompact) {
|
|
1467
|
+
if (autoCompact !== false) return params;
|
|
1468
|
+
const config = params.config ?? {};
|
|
1469
|
+
return { ...params, config: { ...config, model_auto_compact_token_limit: AUTO_COMPACT_OFF_LIMIT } };
|
|
1470
|
+
}
|
|
1433
1471
|
function sandboxParams(mode, network) {
|
|
1434
1472
|
if ((mode ?? "full") === "full") return { sandbox: "danger-full-access" };
|
|
1435
1473
|
if (process.platform !== "darwin" && process.platform !== "win32") {
|
|
@@ -1467,6 +1505,7 @@ var BRIDGE_DEVELOPER_INSTRUCTIONS = [
|
|
|
1467
1505
|
"\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"
|
|
1468
1506
|
].join("\n");
|
|
1469
1507
|
var READ_HISTORY_TIMEOUT_MS = 2e4;
|
|
1508
|
+
var COMPACT_TIMEOUT_MS = 12e4;
|
|
1470
1509
|
function withDeadline(p, ms, label) {
|
|
1471
1510
|
return new Promise((resolve7, reject) => {
|
|
1472
1511
|
const t = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
|
|
@@ -1538,6 +1577,64 @@ var CodexThread = class {
|
|
|
1538
1577
|
}
|
|
1539
1578
|
return { events: gen(), turnId: () => self.currentTurnId };
|
|
1540
1579
|
}
|
|
1580
|
+
runGoal(objective) {
|
|
1581
|
+
const self = this;
|
|
1582
|
+
this.currentTurnId = void 0;
|
|
1583
|
+
async function* gen() {
|
|
1584
|
+
await self.client.request("thread/goal/clear", { threadId: self.codexThreadId }).catch(() => void 0);
|
|
1585
|
+
let setError;
|
|
1586
|
+
const setFailed = new Promise((resolve7) => {
|
|
1587
|
+
self.client.request("thread/goal/set", { threadId: self.codexThreadId, objective }).then(void 0, (err) => {
|
|
1588
|
+
setError = err instanceof Error ? err : new Error(String(err));
|
|
1589
|
+
log.fail("agent", setError, { phase: "thread/goal/set" });
|
|
1590
|
+
resolve7("set-failed");
|
|
1591
|
+
});
|
|
1592
|
+
});
|
|
1593
|
+
const stream2 = self.client.stream()[Symbol.asyncIterator]();
|
|
1594
|
+
let armed = false;
|
|
1595
|
+
let turnActive = false;
|
|
1596
|
+
let goalDone = false;
|
|
1597
|
+
while (true) {
|
|
1598
|
+
const step = await Promise.race([stream2.next(), setFailed]);
|
|
1599
|
+
if (step === "set-failed") {
|
|
1600
|
+
yield { type: "error", message: setError?.message ?? "thread/goal/set \u8BF7\u6C42\u5931\u8D25", willRetry: false };
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (step.done) return;
|
|
1604
|
+
const ev = mapNotification(step.value);
|
|
1605
|
+
if (!ev) continue;
|
|
1606
|
+
if (ev.type === "turn_started") {
|
|
1607
|
+
self.currentTurnId = ev.turnId;
|
|
1608
|
+
armed = true;
|
|
1609
|
+
turnActive = true;
|
|
1610
|
+
yield ev;
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (ev.type === "done") {
|
|
1614
|
+
turnActive = false;
|
|
1615
|
+
yield ev;
|
|
1616
|
+
if (goalDone) return;
|
|
1617
|
+
continue;
|
|
1618
|
+
}
|
|
1619
|
+
if (ev.type === "goal_update") {
|
|
1620
|
+
if (ev.objective !== objective) continue;
|
|
1621
|
+
if (ev.status === "active" || ev.status === "paused") armed = true;
|
|
1622
|
+
yield ev;
|
|
1623
|
+
if (armed && isGoalTerminal(ev.status)) {
|
|
1624
|
+
if (turnActive) goalDone = true;
|
|
1625
|
+
else return;
|
|
1626
|
+
}
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
yield ev;
|
|
1630
|
+
if (ev.type === "error" && !ev.willRetry) return;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
return { events: gen(), turnId: () => self.currentTurnId };
|
|
1634
|
+
}
|
|
1635
|
+
async clearGoal() {
|
|
1636
|
+
await this.client.request("thread/goal/clear", { threadId: this.codexThreadId });
|
|
1637
|
+
}
|
|
1541
1638
|
async steer(input2, expectedTurnId) {
|
|
1542
1639
|
await this.client.request("turn/steer", {
|
|
1543
1640
|
threadId: this.codexThreadId,
|
|
@@ -1548,6 +1645,40 @@ var CodexThread = class {
|
|
|
1548
1645
|
async abort(turnId) {
|
|
1549
1646
|
await this.client.request("turn/interrupt", { threadId: this.codexThreadId, turnId });
|
|
1550
1647
|
}
|
|
1648
|
+
async compact() {
|
|
1649
|
+
let startError;
|
|
1650
|
+
const startFailed = new Promise((resolve7) => {
|
|
1651
|
+
this.client.request("thread/compact/start", { threadId: this.codexThreadId }).then(void 0, (err) => {
|
|
1652
|
+
startError = err instanceof Error ? err : new Error(String(err));
|
|
1653
|
+
log.fail("agent", startError, { phase: "thread/compact/start" });
|
|
1654
|
+
resolve7("start-failed");
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
let timer;
|
|
1658
|
+
const timeout = new Promise((resolve7) => {
|
|
1659
|
+
timer = setTimeout(() => resolve7("timeout"), COMPACT_TIMEOUT_MS);
|
|
1660
|
+
});
|
|
1661
|
+
const stream2 = this.client.stream()[Symbol.asyncIterator]();
|
|
1662
|
+
let compacted = false;
|
|
1663
|
+
let usage = null;
|
|
1664
|
+
try {
|
|
1665
|
+
while (true) {
|
|
1666
|
+
const step = await Promise.race([stream2.next(), startFailed, timeout]);
|
|
1667
|
+
if (step === "start-failed") throw startError ?? new Error("thread/compact/start \u8BF7\u6C42\u5931\u8D25");
|
|
1668
|
+
if (step === "timeout") throw new Error(`\u538B\u7F29\u8D85\u65F6\uFF08codex \u672A\u5728 ${COMPACT_TIMEOUT_MS / 1e3}s \u5185\u5B8C\u6210\uFF09`);
|
|
1669
|
+
if (step.done) break;
|
|
1670
|
+
const ev = mapNotification(step.value);
|
|
1671
|
+
if (!ev) continue;
|
|
1672
|
+
if (ev.type === "context_usage") usage = { usedTokens: ev.usedTokens, contextWindow: ev.contextWindow };
|
|
1673
|
+
else if (ev.type === "context_compacted") compacted = true;
|
|
1674
|
+
else if (ev.type === "error" && !ev.willRetry) throw new Error(ev.message);
|
|
1675
|
+
else if (ev.type === "done") break;
|
|
1676
|
+
}
|
|
1677
|
+
} finally {
|
|
1678
|
+
if (timer) clearTimeout(timer);
|
|
1679
|
+
}
|
|
1680
|
+
return { compacted, usage };
|
|
1681
|
+
}
|
|
1551
1682
|
async close() {
|
|
1552
1683
|
await this.client.close();
|
|
1553
1684
|
}
|
|
@@ -1636,7 +1767,7 @@ var CodexAppServerBackend = class {
|
|
|
1636
1767
|
}
|
|
1637
1768
|
}
|
|
1638
1769
|
async startThread(opts) {
|
|
1639
|
-
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1770
|
+
const sandbox = withAutoCompact(sandboxParams(opts.mode, opts.network), opts.autoCompact);
|
|
1640
1771
|
const client = await this.spawn(opts.cwd);
|
|
1641
1772
|
const res = await client.request("thread/start", {
|
|
1642
1773
|
cwd: opts.cwd,
|
|
@@ -1648,7 +1779,7 @@ var CodexAppServerBackend = class {
|
|
|
1648
1779
|
return new CodexThread(client, res.thread.id, opts.model, opts.effort);
|
|
1649
1780
|
}
|
|
1650
1781
|
async resumeThread(opts) {
|
|
1651
|
-
const sandbox = sandboxParams(opts.mode, opts.network);
|
|
1782
|
+
const sandbox = withAutoCompact(sandboxParams(opts.mode, opts.network), opts.autoCompact);
|
|
1652
1783
|
const client = await this.spawn(opts.cwd);
|
|
1653
1784
|
const res = await client.request("thread/resume", {
|
|
1654
1785
|
threadId: opts.codexThreadId,
|
|
@@ -1821,39 +1952,53 @@ function stampRenderToken(card2) {
|
|
|
1821
1952
|
}
|
|
1822
1953
|
}
|
|
1823
1954
|
}
|
|
1824
|
-
for (const
|
|
1955
|
+
for (const k2 of Object.keys(obj)) visit(obj[k2]);
|
|
1825
1956
|
};
|
|
1826
1957
|
visit(card2);
|
|
1827
1958
|
}
|
|
1959
|
+
function isCardIdNotReady(err) {
|
|
1960
|
+
const data = err?.response?.data;
|
|
1961
|
+
return data?.code === 230099 || /11310|cardid is invalid/i.test(data?.msg ?? "");
|
|
1962
|
+
}
|
|
1828
1963
|
async function sendManagedCard(channel, to, card2, replyTo, replyInThread = false, receiveIdType = "chat_id") {
|
|
1829
1964
|
stampRenderToken(card2);
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1965
|
+
const data = JSON.stringify(card2);
|
|
1966
|
+
const attempt = async () => {
|
|
1967
|
+
const created = await channel.rawClient.cardkit.v1.card.create({ data: { type: "card_json", data } });
|
|
1968
|
+
const cardId = created.data?.card_id;
|
|
1969
|
+
if (!cardId) {
|
|
1970
|
+
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
1971
|
+
}
|
|
1972
|
+
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
1973
|
+
let messageId;
|
|
1974
|
+
if (replyTo) {
|
|
1975
|
+
const sent = await channel.rawClient.im.v1.message.reply({
|
|
1976
|
+
path: { message_id: replyTo },
|
|
1977
|
+
data: { msg_type: "interactive", content, reply_in_thread: replyInThread }
|
|
1978
|
+
});
|
|
1979
|
+
messageId = sent.data?.message_id;
|
|
1980
|
+
} else {
|
|
1981
|
+
const sent = await channel.rawClient.im.v1.message.create({
|
|
1982
|
+
params: { receive_id_type: receiveIdType },
|
|
1983
|
+
data: { receive_id: to, msg_type: "interactive", content }
|
|
1984
|
+
});
|
|
1985
|
+
messageId = sent.data?.message_id;
|
|
1986
|
+
}
|
|
1987
|
+
if (!messageId) {
|
|
1988
|
+
throw new Error("send card-by-reference returned no message_id");
|
|
1989
|
+
}
|
|
1990
|
+
byMessageId.set(messageId, { cardId, sequence: 0 });
|
|
1991
|
+
return { messageId, cardId };
|
|
1992
|
+
};
|
|
1993
|
+
for (let i = 0; ; i++) {
|
|
1994
|
+
try {
|
|
1995
|
+
return await attempt();
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
if (i >= 2 || !isCardIdNotReady(err)) throw err;
|
|
1998
|
+
log.fail("card", err, { phase: "managed-send", attempt: i, retry: true });
|
|
1999
|
+
await new Promise((r) => setTimeout(r, 400 * (i + 1)));
|
|
2000
|
+
}
|
|
1854
2001
|
}
|
|
1855
|
-
byMessageId.set(messageId, { cardId, sequence: 0 });
|
|
1856
|
-
return { messageId, cardId };
|
|
1857
2002
|
}
|
|
1858
2003
|
async function updateManagedCard(channel, messageId, card2) {
|
|
1859
2004
|
const entry = byMessageId.get(messageId);
|
|
@@ -1983,6 +2128,10 @@ function reduce(state, evt) {
|
|
|
1983
2128
|
});
|
|
1984
2129
|
return { ...state, blocks };
|
|
1985
2130
|
}
|
|
2131
|
+
case "context_usage":
|
|
2132
|
+
return { ...state, usage: { used: evt.usedTokens, window: evt.contextWindow } };
|
|
2133
|
+
// context_compacted is surfaced as a standalone notice by the run loop, not
|
|
2134
|
+
// folded into the card — fall through to the no-op default.
|
|
1986
2135
|
case "error":
|
|
1987
2136
|
return { ...state, terminal: "error", errorMsg: evt.message, footer: null };
|
|
1988
2137
|
case "done":
|
|
@@ -2102,6 +2251,9 @@ function image(imgKey, alt = "") {
|
|
|
2102
2251
|
function note(content) {
|
|
2103
2252
|
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: "grey" } };
|
|
2104
2253
|
}
|
|
2254
|
+
function colorNote(content, color) {
|
|
2255
|
+
return { tag: "div", text: { tag: "lark_md", content, text_size: "notation", text_color: color } };
|
|
2256
|
+
}
|
|
2105
2257
|
function hr() {
|
|
2106
2258
|
return { tag: "hr" };
|
|
2107
2259
|
}
|
|
@@ -2320,7 +2472,12 @@ function talkLine(noMention, tail) {
|
|
|
2320
2472
|
function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
2321
2473
|
const elements = [];
|
|
2322
2474
|
if (scope === "single") {
|
|
2323
|
-
const lines = [
|
|
2475
|
+
const lines = [
|
|
2476
|
+
talkLine(noMention, "\u4EA4\u7ED9\u6211\u5904\u7406"),
|
|
2477
|
+
"\xB7 `/model` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6",
|
|
2478
|
+
"\xB7 `/context` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4",
|
|
2479
|
+
"\xB7 `/compact` \u2192 \u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u91CA\u653E\u7A7A\u95F4\uFF09"
|
|
2480
|
+
];
|
|
2324
2481
|
if (isAdmin2) lines.push("\xB7 `/settings` \u2192 \u7FA4\u8BBE\u7F6E\uFF08\u514D@ \u5F00\u5173\uFF09");
|
|
2325
2482
|
lines.push("\xB7 `/help` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361");
|
|
2326
2483
|
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")));
|
|
@@ -2331,6 +2488,8 @@ function buildHelpCard(scope, noMention = true, isAdmin2 = false) {
|
|
|
2331
2488
|
md(
|
|
2332
2489
|
`${talkLine(noMention, "\u7EE7\u7EED\u5F53\u524D\u4F1A\u8BDD")}
|
|
2333
2490
|
\xB7 \`/model\` \u2192 \u5207\u6362\u6A21\u578B / \u63A8\u7406\u5F3A\u5EA6
|
|
2491
|
+
\xB7 \`/context\` \u2192 \u770B\u4E0A\u4E0B\u6587\u5360\u6BD4
|
|
2492
|
+
\xB7 \`/compact\` \u2192 \u538B\u7F29\u4E0A\u4E0B\u6587\uFF08\u91CA\u653E\u7A7A\u95F4\uFF09
|
|
2334
2493
|
\xB7 \`/help\` \u2192 \u8FD9\u5F20\u901F\u67E5\u5361`
|
|
2335
2494
|
),
|
|
2336
2495
|
note("\u5F00\u65B0\u8BDD\u9898\uFF1A\u56DE\u5230\u4E3B\u7FA4\u533A @\u6211 + \u5185\u5BB9\u3002")
|
|
@@ -2365,7 +2524,9 @@ function buildWelcomeCard(kind, docUrl, noMention = true) {
|
|
|
2365
2524
|
"\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"
|
|
2366
2525
|
),
|
|
2367
2526
|
md("\u{1F9F5} **\u8BDD\u9898\u5185**"),
|
|
2368
|
-
md(
|
|
2527
|
+
md(
|
|
2528
|
+
"\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"
|
|
2529
|
+
),
|
|
2369
2530
|
note("\u4EFB\u610F\u573A\u666F\u53D1 `/help` \u770B\u5F53\u524D\u53EF\u7528\u547D\u4EE4\u3002")
|
|
2370
2531
|
);
|
|
2371
2532
|
}
|
|
@@ -2635,7 +2796,80 @@ function escapeInline2(s) {
|
|
|
2635
2796
|
return s.replace(/\s+/g, " ").trim();
|
|
2636
2797
|
}
|
|
2637
2798
|
|
|
2799
|
+
// src/card/context-gauge.ts
|
|
2800
|
+
var CTX_WARN = 0.7;
|
|
2801
|
+
var CTX_HIGH = 0.85;
|
|
2802
|
+
var CTX_CRIT = 0.95;
|
|
2803
|
+
function ctxTier(frac) {
|
|
2804
|
+
if (frac >= CTX_CRIT) return { level: 3, color: "red", dot: "\u{1F534}", advice: "\u5F3A\u70C8\u5EFA\u8BAE `/compact` \u538B\u7F29" };
|
|
2805
|
+
if (frac >= CTX_HIGH) return { level: 2, color: "orange", dot: "\u{1F7E0}", advice: "\u5EFA\u8BAE `/compact` \u538B\u7F29" };
|
|
2806
|
+
if (frac >= CTX_WARN) return { level: 1, color: "yellow", dot: "\u{1F7E1}", advice: "\u53EF\u8003\u8651 `/compact` \u538B\u7F29" };
|
|
2807
|
+
return { level: 0, color: "green", dot: "\u{1F7E2}", advice: "" };
|
|
2808
|
+
}
|
|
2809
|
+
function ctxPercent(used, window) {
|
|
2810
|
+
if (!window || window <= 0) return null;
|
|
2811
|
+
return Math.max(0, Math.min(100, Math.round(used / window * 100)));
|
|
2812
|
+
}
|
|
2813
|
+
function k(n) {
|
|
2814
|
+
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(Math.max(0, Math.round(n)));
|
|
2815
|
+
}
|
|
2816
|
+
function runCardGauge(used, window) {
|
|
2817
|
+
const pct = ctxPercent(used, window);
|
|
2818
|
+
if (pct === null || !window) return null;
|
|
2819
|
+
const frac = used / window;
|
|
2820
|
+
if (frac < CTX_WARN) return null;
|
|
2821
|
+
const t = ctxTier(frac);
|
|
2822
|
+
return colorNote(`${t.dot} \u4E0A\u4E0B\u6587 ${pct}% \xB7 ${k(used)}/${k(window)} \xB7 ${t.advice}`, t.color);
|
|
2823
|
+
}
|
|
2824
|
+
function buildContextCard(used, window) {
|
|
2825
|
+
const pct = ctxPercent(used, window);
|
|
2826
|
+
if (pct === null) {
|
|
2827
|
+
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";
|
|
2828
|
+
return card([note(line)], { summary: "\u4E0A\u4E0B\u6587\u7528\u91CF" });
|
|
2829
|
+
}
|
|
2830
|
+
const t = ctxTier(used / window);
|
|
2831
|
+
const els = [colorNote(`${t.dot} **\u4E0A\u4E0B\u6587 ${pct}%** \xB7 ${k(used)}/${k(window)} tokens`, t.color)];
|
|
2832
|
+
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"));
|
|
2833
|
+
return card(els, { summary: "\u4E0A\u4E0B\u6587\u7528\u91CF" });
|
|
2834
|
+
}
|
|
2835
|
+
var COMPACT_SPINNER = ["\u25D0", "\u25D3", "\u25D1", "\u25D2"];
|
|
2836
|
+
function buildCompactingCard(tick = 0) {
|
|
2837
|
+
const spin = COMPACT_SPINNER[(tick % COMPACT_SPINNER.length + COMPACT_SPINNER.length) % COMPACT_SPINNER.length];
|
|
2838
|
+
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")], {
|
|
2839
|
+
summary: "\u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587"
|
|
2840
|
+
});
|
|
2841
|
+
}
|
|
2842
|
+
function buildCompactedCard(usage, before) {
|
|
2843
|
+
const els = [colorNote("\u2705 \u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210", "green")];
|
|
2844
|
+
const pct = usage ? ctxPercent(usage.usedTokens, usage.contextWindow) : null;
|
|
2845
|
+
const dropped = usage != null && before != null && usage.usedTokens < before.used;
|
|
2846
|
+
if (usage && pct !== null && usage.contextWindow && (dropped || before == null)) {
|
|
2847
|
+
const beforePct = before ? ctxPercent(before.used, before.window) : null;
|
|
2848
|
+
const from = dropped && beforePct !== null ? `${beforePct}% \u2192 ` : "";
|
|
2849
|
+
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`));
|
|
2850
|
+
} else {
|
|
2851
|
+
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"));
|
|
2852
|
+
}
|
|
2853
|
+
return card(els, { summary: "\u4E0A\u4E0B\u6587\u538B\u7F29\u5B8C\u6210" });
|
|
2854
|
+
}
|
|
2855
|
+
function buildCompactFailedCard(message) {
|
|
2856
|
+
return card([colorNote(`\u26A0\uFE0F \u538B\u7F29\u5931\u8D25\uFF1A${message}`, "red")], { summary: "\u538B\u7F29\u5931\u8D25" });
|
|
2857
|
+
}
|
|
2858
|
+
function buildAutoCompactCard() {
|
|
2859
|
+
return card(
|
|
2860
|
+
[
|
|
2861
|
+
hr(),
|
|
2862
|
+
colorNote("\u{1F5DC}\uFE0F \u2500\u2500\u2500 \u4E0A\u4E0B\u6587\u5DF2\u81EA\u52A8\u538B\u7F29 \u2500\u2500\u2500", "blue"),
|
|
2863
|
+
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")
|
|
2864
|
+
],
|
|
2865
|
+
{ summary: "\u4E0A\u4E0B\u6587\u5DF2\u81EA\u52A8\u538B\u7F29" }
|
|
2866
|
+
);
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2638
2869
|
// src/card/run-card.ts
|
|
2870
|
+
function gaugeEl(state) {
|
|
2871
|
+
return state.usage ? runCardGauge(state.usage.used, state.usage.window) : null;
|
|
2872
|
+
}
|
|
2639
2873
|
var RC = {
|
|
2640
2874
|
stop: "run.stop"
|
|
2641
2875
|
};
|
|
@@ -2667,7 +2901,9 @@ function renderRunning(state, rc) {
|
|
|
2667
2901
|
const answer = textParts.join("\n\n");
|
|
2668
2902
|
if (answer) elements.push(mdStream(answer, ANSWER_EID));
|
|
2669
2903
|
if (state.footer) elements.push(footerStatus(state.footer));
|
|
2670
|
-
if (rc.cardKey) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
|
|
2904
|
+
if (rc.cardKey && !rc.hideStop) elements.push(actions([button("\u23F9 \u7EC8\u6B62", { a: RC.stop, m: rc.cardKey }, "danger")]));
|
|
2905
|
+
const gauge = gaugeEl(state);
|
|
2906
|
+
if (gauge) elements.push(gauge);
|
|
2671
2907
|
return elements;
|
|
2672
2908
|
}
|
|
2673
2909
|
function renderTerminal(state, rc) {
|
|
@@ -2701,6 +2937,8 @@ function renderTerminal(state, rc) {
|
|
|
2701
2937
|
} else if (state.terminal === "done" && !answer) {
|
|
2702
2938
|
elements.push(noteMd("_\uFF08\u672A\u8FD4\u56DE\u5185\u5BB9\uFF09_"));
|
|
2703
2939
|
}
|
|
2940
|
+
const gauge = gaugeEl(state);
|
|
2941
|
+
if (gauge) elements.push(gauge);
|
|
2704
2942
|
return elements;
|
|
2705
2943
|
}
|
|
2706
2944
|
function lastTextIndex(blocks) {
|
|
@@ -2813,6 +3051,46 @@ function truncate4(s, n) {
|
|
|
2813
3051
|
return s.length > n ? `${s.slice(0, n)}\u2026` : s;
|
|
2814
3052
|
}
|
|
2815
3053
|
|
|
3054
|
+
// src/card/goal-card.ts
|
|
3055
|
+
function fmtTokens(n) {
|
|
3056
|
+
return Math.max(0, Math.round(n)).toLocaleString("en-US");
|
|
3057
|
+
}
|
|
3058
|
+
function fmtDuration(seconds) {
|
|
3059
|
+
const s = Math.max(0, Math.round(seconds));
|
|
3060
|
+
if (s < 60) return `\u7EA6 ${s} \u79D2`;
|
|
3061
|
+
const m = Math.floor(s / 60);
|
|
3062
|
+
const rem = s % 60;
|
|
3063
|
+
if (m < 60) return rem ? `\u7EA6 ${m} \u5206 ${rem} \u79D2` : `\u7EA6 ${m} \u5206`;
|
|
3064
|
+
const h = Math.floor(m / 60);
|
|
3065
|
+
const mm = m % 60;
|
|
3066
|
+
return mm ? `\u7EA6 ${h} \u65F6 ${mm} \u5206` : `\u7EA6 ${h} \u65F6`;
|
|
3067
|
+
}
|
|
3068
|
+
var ABNORMAL_REASON = {
|
|
3069
|
+
budgetLimited: "Token \u9884\u7B97\u7528\u5C3D",
|
|
3070
|
+
usageLimited: "\u8D26\u53F7\u7528\u91CF\u989D\u5EA6\u7528\u5C3D",
|
|
3071
|
+
blocked: "\u88AB\u963B\u585E\uFF0C\u9700\u4EBA\u5DE5\u4ECB\u5165",
|
|
3072
|
+
paused: "\u5DF2\u6682\u505C",
|
|
3073
|
+
timeout: "\u8FD0\u884C\u8D85\u8FC7\u65F6\u957F\u4E0A\u9650\u88AB\u4E2D\u6B62",
|
|
3074
|
+
error: "\u8FD0\u884C\u51FA\u9519"
|
|
3075
|
+
};
|
|
3076
|
+
function buildGoalDoneCard(d) {
|
|
3077
|
+
const ok = isGoalSuccess(d.status);
|
|
3078
|
+
const elements = [
|
|
3079
|
+
md(d.objective.trim() || "\uFF08\u65E0\u76EE\u6807\u63CF\u8FF0\uFF09"),
|
|
3080
|
+
hr(),
|
|
3081
|
+
note(`\u7528\u91CF\u3000${fmtTokens(d.tokensUsed)} tokens`),
|
|
3082
|
+
note(`\u8017\u65F6\u3000${fmtDuration(d.timeUsedSeconds)}`)
|
|
3083
|
+
];
|
|
3084
|
+
if (!ok) {
|
|
3085
|
+
const reason = d.errorMessage?.trim() || ABNORMAL_REASON[d.status] || `\u72B6\u6001\uFF1A${d.status}`;
|
|
3086
|
+
elements.push(note(`\u539F\u56E0\u3000${reason}`));
|
|
3087
|
+
}
|
|
3088
|
+
return card(elements, {
|
|
3089
|
+
header: ok ? { title: "\u{1F3AF} \u76EE\u6807\u5DF2\u5B8C\u6210", template: "green" } : { title: "\u{1F3AF} \u76EE\u6807\u5DF2\u4E2D\u6B62", template: "orange" },
|
|
3090
|
+
summary: ok ? "\u76EE\u6807\u5DF2\u5B8C\u6210" : "\u76EE\u6807\u5DF2\u4E2D\u6B62"
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
|
|
2816
3094
|
// src/card/run-card-stream.ts
|
|
2817
3095
|
var STREAM_THROTTLE_MS = 150;
|
|
2818
3096
|
var RunCardStream = class {
|
|
@@ -2914,35 +3192,55 @@ var RunCardStream = class {
|
|
|
2914
3192
|
}
|
|
2915
3193
|
}
|
|
2916
3194
|
/** Create the entity from the initial (running) card and send a message
|
|
2917
|
-
* referencing it by card_id. Returns the carrier message id.
|
|
3195
|
+
* referencing it by card_id. Returns the carrier message id.
|
|
3196
|
+
*
|
|
3197
|
+
* A just-created CardKit entity occasionally hasn't propagated when the message
|
|
3198
|
+
* referencing it is sent — Feishu 400s with 230099 / ErrCode 11310 "cardid is
|
|
3199
|
+
* invalid" and the run card silently fails to appear (this surfaced as
|
|
3200
|
+
* intermittent intake.fail). Same transient, same fix as
|
|
3201
|
+
* {@link ../card/managed#sendManagedCard}: retry the whole create+send with a
|
|
3202
|
+
* short backoff. Only this transient retries — Feishu rejected the message
|
|
3203
|
+
* outright (nothing sent), and a re-created entity that's never referenced is a
|
|
3204
|
+
* harmless orphan, so no duplicate card. */
|
|
2918
3205
|
async create(channel, chatId, initialCard, opts) {
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
const cardId = created.data?.card_id;
|
|
2923
|
-
if (!cardId) {
|
|
2924
|
-
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
2925
|
-
}
|
|
2926
|
-
this.cardId = cardId;
|
|
2927
|
-
this.lastContent = JSON.stringify(initialCard);
|
|
2928
|
-
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
2929
|
-
let messageId;
|
|
2930
|
-
if (opts.replyTo) {
|
|
2931
|
-
const r = await channel.rawClient.im.v1.message.reply({
|
|
2932
|
-
path: { message_id: opts.replyTo },
|
|
2933
|
-
data: { msg_type: "interactive", content, reply_in_thread: opts.replyInThread ?? false }
|
|
2934
|
-
});
|
|
2935
|
-
messageId = r.data?.message_id;
|
|
2936
|
-
} else {
|
|
2937
|
-
const r = await channel.rawClient.im.v1.message.create({
|
|
2938
|
-
params: { receive_id_type: "chat_id" },
|
|
2939
|
-
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3206
|
+
const attempt = async () => {
|
|
3207
|
+
const created = await channel.rawClient.cardkit.v1.card.create({
|
|
3208
|
+
data: { type: "card_json", data: JSON.stringify(initialCard) }
|
|
2940
3209
|
});
|
|
2941
|
-
|
|
3210
|
+
const cardId = created.data?.card_id;
|
|
3211
|
+
if (!cardId) {
|
|
3212
|
+
throw new Error(`cardkit.card.create returned no card_id: ${JSON.stringify(created).slice(0, 200)}`);
|
|
3213
|
+
}
|
|
3214
|
+
this.cardId = cardId;
|
|
3215
|
+
this.lastContent = JSON.stringify(initialCard);
|
|
3216
|
+
const content = JSON.stringify({ type: "card", data: { card_id: cardId } });
|
|
3217
|
+
let messageId;
|
|
3218
|
+
if (opts.replyTo) {
|
|
3219
|
+
const r = await channel.rawClient.im.v1.message.reply({
|
|
3220
|
+
path: { message_id: opts.replyTo },
|
|
3221
|
+
data: { msg_type: "interactive", content, reply_in_thread: opts.replyInThread ?? false }
|
|
3222
|
+
});
|
|
3223
|
+
messageId = r.data?.message_id;
|
|
3224
|
+
} else {
|
|
3225
|
+
const r = await channel.rawClient.im.v1.message.create({
|
|
3226
|
+
params: { receive_id_type: "chat_id" },
|
|
3227
|
+
data: { receive_id: chatId, msg_type: "interactive", content }
|
|
3228
|
+
});
|
|
3229
|
+
messageId = r.data?.message_id;
|
|
3230
|
+
}
|
|
3231
|
+
if (!messageId) throw new Error("run card send returned no message_id");
|
|
3232
|
+
this._messageId = messageId;
|
|
3233
|
+
return messageId;
|
|
3234
|
+
};
|
|
3235
|
+
for (let i = 0; ; i++) {
|
|
3236
|
+
try {
|
|
3237
|
+
return await attempt();
|
|
3238
|
+
} catch (err) {
|
|
3239
|
+
if (i >= 2 || !isCardIdNotReady(err)) throw err;
|
|
3240
|
+
log.fail("card", err, { phase: "run-stream-create", attempt: i, retry: true });
|
|
3241
|
+
await new Promise((r) => setTimeout(r, 400 * (i + 1)));
|
|
3242
|
+
}
|
|
2942
3243
|
}
|
|
2943
|
-
if (!messageId) throw new Error("run card send returned no message_id");
|
|
2944
|
-
this._messageId = messageId;
|
|
2945
|
-
return messageId;
|
|
2946
3244
|
}
|
|
2947
3245
|
/** Throttled whole-card stream update. Skips identical/too-soon pushes;
|
|
2948
3246
|
* `force` flushes regardless (still de-duped on content). */
|
|
@@ -3226,8 +3524,8 @@ async function updateProject(name, patch) {
|
|
|
3226
3524
|
if (!p) return;
|
|
3227
3525
|
const actual = typeof patch === "function" ? patch(p) : patch;
|
|
3228
3526
|
const target = p;
|
|
3229
|
-
for (const [
|
|
3230
|
-
if (v !== void 0) target[
|
|
3527
|
+
for (const [k2, v] of Object.entries(actual)) {
|
|
3528
|
+
if (v !== void 0) target[k2] = v;
|
|
3231
3529
|
}
|
|
3232
3530
|
await write(projects);
|
|
3233
3531
|
});
|
|
@@ -3286,13 +3584,18 @@ var DM = {
|
|
|
3286
3584
|
rmAllowed: "dm.allow.rm",
|
|
3287
3585
|
// 项目设置容器(项目列表 / 建项目完成卡 进入),以后的项目级设置项往这里加
|
|
3288
3586
|
projectSettings: "dm.projectSettings",
|
|
3587
|
+
// 🧵 话题钻取:项目总览的「🧵 N 话题」按钮 → 该项目话题列表卡
|
|
3588
|
+
projectTopics: "dm.projectTopics",
|
|
3289
3589
|
setNoMentionDm: "dm.proj.noMention",
|
|
3590
|
+
// 🗜️ 自动压缩:项目级开关(同群设置里的那个,DM 里也能改),按钮携带项目名 n
|
|
3591
|
+
setAutoCompactDm: "dm.proj.autoCompact",
|
|
3290
3592
|
// 🔐 权限:codex 沙箱档位(管理员档 + 普通用户档)+ 联网,做成下拉表单(选+提交)
|
|
3291
3593
|
permission: "dm.proj.perm",
|
|
3292
3594
|
permissionSubmit: "dm.proj.perm.submit"
|
|
3293
3595
|
};
|
|
3294
3596
|
var GS = {
|
|
3295
|
-
setNoMention: "gs.noMention"
|
|
3597
|
+
setNoMention: "gs.noMention",
|
|
3598
|
+
setAutoCompact: "gs.autoCompact"
|
|
3296
3599
|
};
|
|
3297
3600
|
function kindLabel(kind) {
|
|
3298
3601
|
return kind === "single" ? "\u{1F4AC} \u5355\u4F1A\u8BDD\u7FA4" : "\u{1F465} \u591A\u8BDD\u9898\u7FA4";
|
|
@@ -3565,6 +3868,7 @@ function buildNewProjectDoneCard(p) {
|
|
|
3565
3868
|
);
|
|
3566
3869
|
return card(elements, { header: { title, template: "green" } });
|
|
3567
3870
|
}
|
|
3871
|
+
var PROJECT_TOPICS_MAX = 50;
|
|
3568
3872
|
function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map()) {
|
|
3569
3873
|
if (projects.length === 0) {
|
|
3570
3874
|
return card(
|
|
@@ -3574,25 +3878,15 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3574
3878
|
}
|
|
3575
3879
|
const elements = [];
|
|
3576
3880
|
for (const p of projects) {
|
|
3881
|
+
const topicCount = (p.chatId ? sessionsByChat.get(p.chatId) : void 0)?.length ?? 0;
|
|
3882
|
+
const dir = `\u{1F4C2} \`${p.cwd}\`${p.branch && p.branch !== "\u2014" ? ` \u{1F33F} ${p.branch}` : ""}`;
|
|
3883
|
+
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";
|
|
3577
3884
|
elements.push(md(`**${p.name}**${p.blank ? " _(\u7A7A\u767D)_" : ""}`));
|
|
3578
|
-
elements.push(note(
|
|
3579
|
-
|
|
3580
|
-
note(
|
|
3581
|
-
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"
|
|
3582
|
-
)
|
|
3583
|
-
);
|
|
3584
|
-
const sessions = (p.chatId ? sessionsByChat.get(p.chatId) : void 0) ?? [];
|
|
3585
|
-
if (sessions.length === 0) {
|
|
3586
|
-
elements.push(note("\uFF08\u6682\u65E0\u8BDD\u9898\uFF09"));
|
|
3587
|
-
} else {
|
|
3588
|
-
const sorted = [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
3589
|
-
for (const s of sorted) {
|
|
3590
|
-
const title = (s.summary || "(\u7A7A)").replace(/\s+/g, " ").slice(0, 40);
|
|
3591
|
-
elements.push(note(`\xB7 ${title} \xB7 ${relativeTime(s.updatedAt)}`));
|
|
3592
|
-
}
|
|
3593
|
-
}
|
|
3885
|
+
elements.push(note(`${dir}
|
|
3886
|
+
${meta}`));
|
|
3594
3887
|
const row = [];
|
|
3595
3888
|
if (p.chatId) row.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(p.chatId)));
|
|
3889
|
+
row.push(button(`\u{1F9F5} ${topicCount} \u8BDD\u9898`, { a: DM.projectTopics, n: p.name }));
|
|
3596
3890
|
row.push(button("\u2699\uFE0F \u8BBE\u7F6E", { a: DM.projectSettings, n: p.name }));
|
|
3597
3891
|
row.push(button("\u{1F5D1} \u5220\u9664", { a: DM.rmConfirm, n: p.name }, "danger"));
|
|
3598
3892
|
elements.push(actions(row));
|
|
@@ -3602,6 +3896,26 @@ function buildProjectListCard(projects, sessionsByChat = /* @__PURE__ */ new Map
|
|
|
3602
3896
|
elements.push(actions([button("\u2B05\uFE0F \u83DC\u5355", { a: DM.menu })]));
|
|
3603
3897
|
return card(elements, { header: { title: "\u{1F4C1} \u9879\u76EE\u5217\u8868", template: "wathet" } });
|
|
3604
3898
|
}
|
|
3899
|
+
function buildProjectTopicsCard(project, sessions) {
|
|
3900
|
+
const elements = [md(`**${project.name}** \xB7 \u5171 ${sessions.length} \u4E2A\u8BDD\u9898`)];
|
|
3901
|
+
if (sessions.length === 0) {
|
|
3902
|
+
elements.push(note("\uFF08\u6682\u65E0\u8BDD\u9898\uFF09"));
|
|
3903
|
+
} else {
|
|
3904
|
+
const sorted = [...sessions].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
3905
|
+
for (const s of sorted.slice(0, PROJECT_TOPICS_MAX)) {
|
|
3906
|
+
const title = (s.summary || "(\u7A7A)").replace(/\s+/g, " ").slice(0, 50);
|
|
3907
|
+
elements.push(note(`\xB7 ${title} \xB7 ${relativeTime(s.updatedAt)}`));
|
|
3908
|
+
}
|
|
3909
|
+
if (sorted.length > PROJECT_TOPICS_MAX) {
|
|
3910
|
+
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`));
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
const nav = [];
|
|
3914
|
+
if (project.chatId) nav.push(linkButton("\u{1F4AC} \u6253\u5F00\u7FA4\u804A", openChatUrl(project.chatId)));
|
|
3915
|
+
nav.push(button("\u2B05\uFE0F \u9879\u76EE\u5217\u8868", { a: DM.projects }));
|
|
3916
|
+
elements.push(hr(), actions(nav));
|
|
3917
|
+
return card(elements, { header: { title: `\u{1F9F5} \u8BDD\u9898 \xB7 ${project.name}`, template: "wathet" } });
|
|
3918
|
+
}
|
|
3605
3919
|
function buildRmConfirmCard(name, origin) {
|
|
3606
3920
|
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";
|
|
3607
3921
|
return card(
|
|
@@ -3675,6 +3989,7 @@ function buildWatchdogCustomCard(cfg) {
|
|
|
3675
3989
|
function buildGroupSettingsCard(project) {
|
|
3676
3990
|
const kind = project.kind ?? "multi";
|
|
3677
3991
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
3992
|
+
const autoCompact = project.autoCompact ?? true;
|
|
3678
3993
|
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";
|
|
3679
3994
|
return card(
|
|
3680
3995
|
[
|
|
@@ -3685,7 +4000,12 @@ function buildGroupSettingsCard(project) {
|
|
|
3685
4000
|
{ label: "\u5173", value: "off" }
|
|
3686
4001
|
]),
|
|
3687
4002
|
note(scopeNote),
|
|
3688
|
-
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")
|
|
4003
|
+
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"),
|
|
4004
|
+
...optionRow("\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u4E0A\u4E0B\u6587", GS.setAutoCompact, autoCompact ? "on" : "off", [
|
|
4005
|
+
{ label: "\u5F00", value: "on" },
|
|
4006
|
+
{ label: "\u5173", value: "off" }
|
|
4007
|
+
]),
|
|
4008
|
+
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")
|
|
3689
4009
|
],
|
|
3690
4010
|
{ header: { title: "\u2699\uFE0F \u7FA4\u8BBE\u7F6E", template: "blue" } }
|
|
3691
4011
|
);
|
|
@@ -3803,6 +4123,7 @@ function buildPermissionCard(p) {
|
|
|
3803
4123
|
function buildProjectSettingsCard(project) {
|
|
3804
4124
|
const kind = project.kind ?? "multi";
|
|
3805
4125
|
const noMention = project.noMention ?? defaultNoMention(project);
|
|
4126
|
+
const autoCompact = project.autoCompact ?? true;
|
|
3806
4127
|
return card(
|
|
3807
4128
|
[
|
|
3808
4129
|
md(`**\u9879\u76EE\u8BBE\u7F6E** \xB7 ${project.name}`),
|
|
@@ -3820,6 +4141,13 @@ function buildProjectSettingsCard(project) {
|
|
|
3820
4141
|
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"
|
|
3821
4142
|
),
|
|
3822
4143
|
hr(),
|
|
4144
|
+
md("\u{1F5DC}\uFE0F \u81EA\u52A8\u538B\u7F29\u4E0A\u4E0B\u6587"),
|
|
4145
|
+
actions([
|
|
4146
|
+
button("\u5F00", { a: DM.setAutoCompactDm, v: "on", n: project.name }, autoCompact ? "primary" : "default"),
|
|
4147
|
+
button("\u5173", { a: DM.setAutoCompactDm, v: "off", n: project.name }, autoCompact ? "default" : "primary")
|
|
4148
|
+
]),
|
|
4149
|
+
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"),
|
|
4150
|
+
hr(),
|
|
3823
4151
|
actions([button("\u{1F6E1} \u54CD\u5E94\u767D\u540D\u5355", { a: DM.allowlist, n: project.name }, "primary")]),
|
|
3824
4152
|
note("\u8BBE\u7F6E\u8C01\u80FD\u8BA9\u6211\u5728\u672C\u7FA4\u54CD\u5E94 / \u8DD1 codex\uFF08\u7A7A = \u6240\u6709\u4EBA\uFF09\u3002"),
|
|
3825
4153
|
hr(),
|
|
@@ -5365,8 +5693,8 @@ async function patchSession(threadId, patch) {
|
|
|
5365
5693
|
const rec = sessions.find((s) => s.threadId === threadId);
|
|
5366
5694
|
if (!rec) return;
|
|
5367
5695
|
const target = rec;
|
|
5368
|
-
for (const [
|
|
5369
|
-
if (v !== void 0) target[
|
|
5696
|
+
for (const [k2, v] of Object.entries(patch)) {
|
|
5697
|
+
if (v !== void 0) target[k2] = v;
|
|
5370
5698
|
}
|
|
5371
5699
|
rec.updatedAt = Date.now();
|
|
5372
5700
|
await write2(sessions);
|
|
@@ -5484,7 +5812,7 @@ function walkForImageKeys(node, out) {
|
|
|
5484
5812
|
}
|
|
5485
5813
|
const obj = node;
|
|
5486
5814
|
if (obj.tag === "img" && typeof obj.image_key === "string") out.push(obj.image_key);
|
|
5487
|
-
for (const
|
|
5815
|
+
for (const k2 of Object.keys(obj)) walkForImageKeys(obj[k2], out);
|
|
5488
5816
|
}
|
|
5489
5817
|
async function downloadOne(channel, ref, index) {
|
|
5490
5818
|
try {
|
|
@@ -6112,6 +6440,11 @@ function selectValue(formValue, name) {
|
|
|
6112
6440
|
function asTier(v) {
|
|
6113
6441
|
return v === "qa" || v === "write" || v === "full" ? v : void 0;
|
|
6114
6442
|
}
|
|
6443
|
+
function parseGoalTrigger(text) {
|
|
6444
|
+
if (!/(^|\s)\/goal(?=\s|$)/i.test(text)) return null;
|
|
6445
|
+
const objective = text.replace(/(^|\s)\/goal(?=\s|$)/gi, " ").replace(/\s+/g, " ").trim();
|
|
6446
|
+
return objective.length > 0 ? objective : null;
|
|
6447
|
+
}
|
|
6115
6448
|
function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
6116
6449
|
const backend = createBackend();
|
|
6117
6450
|
const sessions = /* @__PURE__ */ new Map();
|
|
@@ -6125,6 +6458,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6125
6458
|
const runCards = /* @__PURE__ */ new Map();
|
|
6126
6459
|
const runStreams = /* @__PURE__ */ new Map();
|
|
6127
6460
|
const lastRunCard = /* @__PURE__ */ new Map();
|
|
6461
|
+
const lastUsage = /* @__PURE__ */ new Map();
|
|
6128
6462
|
let modelsCache = null;
|
|
6129
6463
|
async function listModels() {
|
|
6130
6464
|
if (!modelsCache) modelsCache = await backend.listModels();
|
|
@@ -6206,6 +6540,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6206
6540
|
}
|
|
6207
6541
|
const text = msg.content.trim();
|
|
6208
6542
|
const cmd = parseCommand(text);
|
|
6543
|
+
const goalObjective = parseGoalTrigger(text);
|
|
6209
6544
|
if ((project?.kind ?? "multi") === "single") {
|
|
6210
6545
|
if (cmd === "help") {
|
|
6211
6546
|
await postHelpCard(msg, "single", false, project);
|
|
@@ -6220,6 +6555,19 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6220
6555
|
await postModelCard(msg, ts.sessionKey);
|
|
6221
6556
|
return;
|
|
6222
6557
|
}
|
|
6558
|
+
if (cmd === "compact") {
|
|
6559
|
+
await runCompact(msg, ts.sessionKey, false, ts);
|
|
6560
|
+
return;
|
|
6561
|
+
}
|
|
6562
|
+
if (cmd === "context") {
|
|
6563
|
+
await postContextCard(msg, ts.sessionKey, false);
|
|
6564
|
+
return;
|
|
6565
|
+
}
|
|
6566
|
+
if (goalObjective) {
|
|
6567
|
+
void addReaction(msg.messageId, "OKR");
|
|
6568
|
+
startReservedRun(msg, goalObjective, ts.sessionKey, true, project, ts, void 0, void 0, void 0, true);
|
|
6569
|
+
return;
|
|
6570
|
+
}
|
|
6223
6571
|
handleTurn(msg, text, ts.sessionKey, true, project, ts);
|
|
6224
6572
|
return;
|
|
6225
6573
|
}
|
|
@@ -6233,6 +6581,19 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6233
6581
|
await postModelCard(msg, ts.sessionKey);
|
|
6234
6582
|
return;
|
|
6235
6583
|
}
|
|
6584
|
+
if (cmd === "compact") {
|
|
6585
|
+
await runCompact(msg, ts.sessionKey, true, ts);
|
|
6586
|
+
return;
|
|
6587
|
+
}
|
|
6588
|
+
if (cmd === "context") {
|
|
6589
|
+
await postContextCard(msg, ts.sessionKey, true);
|
|
6590
|
+
return;
|
|
6591
|
+
}
|
|
6592
|
+
if (goalObjective) {
|
|
6593
|
+
void addReaction(msg.messageId, "OKR");
|
|
6594
|
+
startReservedRun(msg, goalObjective, ts.sessionKey, false, project, ts, void 0, void 0, void 0, true);
|
|
6595
|
+
return;
|
|
6596
|
+
}
|
|
6236
6597
|
handleTurn(msg, text, ts.sessionKey, false, project, ts);
|
|
6237
6598
|
return;
|
|
6238
6599
|
}
|
|
@@ -6248,8 +6609,13 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6248
6609
|
await postGroupSettings(msg, project);
|
|
6249
6610
|
return;
|
|
6250
6611
|
}
|
|
6251
|
-
if (cmd === "model") {
|
|
6252
|
-
await channel.send(msg.chatId, { markdown:
|
|
6612
|
+
if (cmd === "model" || cmd === "compact" || cmd === "context") {
|
|
6613
|
+
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);
|
|
6614
|
+
return;
|
|
6615
|
+
}
|
|
6616
|
+
if (goalObjective) {
|
|
6617
|
+
void addReaction(msg.messageId, "OKR");
|
|
6618
|
+
startTopicDirectly(msg, goalObjective, project, true);
|
|
6253
6619
|
return;
|
|
6254
6620
|
}
|
|
6255
6621
|
startTopicDirectly(msg, text, project);
|
|
@@ -6257,13 +6623,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6257
6623
|
function parseCommand(text) {
|
|
6258
6624
|
const m = /^\/(\w+)/.exec(text);
|
|
6259
6625
|
const name = m?.[1]?.toLowerCase();
|
|
6260
|
-
return name === "resume" || name === "model" || name === "settings" || name === "help" ? name : null;
|
|
6626
|
+
return name === "resume" || name === "model" || name === "settings" || name === "help" || name === "compact" || name === "context" ? name : null;
|
|
6261
6627
|
}
|
|
6262
6628
|
function shouldRespondWithoutMention(project, msg) {
|
|
6263
6629
|
if (!(project.noMention ?? defaultNoMention(project))) return false;
|
|
6264
6630
|
if (msg.mentionAll || msg.mentions.some((m) => !m.isBot)) return false;
|
|
6265
6631
|
if ((project.kind ?? "multi") === "single") return true;
|
|
6266
|
-
|
|
6632
|
+
const content = msg.content.trim();
|
|
6633
|
+
return Boolean(msg.threadId) || parseCommand(content) !== null || parseGoalTrigger(content) !== null;
|
|
6267
6634
|
}
|
|
6268
6635
|
async function denyAdminCommand(msg, cmd) {
|
|
6269
6636
|
await channel.send(msg.chatId, { markdown: `\u26A0\uFE0F \`/${cmd}\` \u4EC5 bot \u7BA1\u7406\u5458\u53EF\u7528\u3002` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
@@ -6286,7 +6653,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6286
6653
|
function turnPerm(project, senderId) {
|
|
6287
6654
|
if (!project) return {};
|
|
6288
6655
|
const t = turnTier(project, isAdmin(cfg, senderId));
|
|
6289
|
-
return { mode: t.mode, network: project.network, roleSuffix: t.split ? t.role : void 0 };
|
|
6656
|
+
return { mode: t.mode, network: project.network, autoCompact: project.autoCompact, roleSuffix: t.split ? t.role : void 0 };
|
|
6290
6657
|
}
|
|
6291
6658
|
function turnSession(baseKey, project, senderId) {
|
|
6292
6659
|
const perm = turnPerm(project, senderId);
|
|
@@ -6335,9 +6702,13 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6335
6702
|
}
|
|
6336
6703
|
startReservedRun(msg, text, sessionKey, flat, project, perm);
|
|
6337
6704
|
}
|
|
6338
|
-
function startReservedRun(msg, text, sessionKey, flat, project, perm, preloadedImages, preIngested, summaryText2) {
|
|
6705
|
+
function startReservedRun(msg, text, sessionKey, flat, project, perm, preloadedImages, preIngested, summaryText2, goal) {
|
|
6339
6706
|
const existing = active.get(sessionKey);
|
|
6340
6707
|
if (existing) {
|
|
6708
|
+
if (goal) {
|
|
6709
|
+
void channel.send(msg.chatId, { markdown: "\u5F53\u524D\u4F1A\u8BDD\u6709\u4EFB\u52A1\u5728\u8DD1\uFF0C\u8BF7\u7B49\u5B83\u7ED3\u675F\u540E\u518D\u53D1 `/goal`\u3002" }, { replyTo: msg.messageId, replyInThread: !flat }).catch(() => void 0);
|
|
6710
|
+
return;
|
|
6711
|
+
}
|
|
6341
6712
|
existing.queue.push({ text, images: preloadedImages });
|
|
6342
6713
|
log.info("intake", "queued", { depth: existing.queue.length });
|
|
6343
6714
|
return;
|
|
@@ -6345,13 +6716,14 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6345
6716
|
const reserved = { queue: [], requesterOpenId: msg.senderId };
|
|
6346
6717
|
active.set(sessionKey, reserved);
|
|
6347
6718
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
6348
|
-
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
6719
|
+
const reaction = goal ? void 0 : runReaction(msg.messageId, !sema.hasFree());
|
|
6349
6720
|
try {
|
|
6350
6721
|
const images = preloadedImages ?? (messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0);
|
|
6351
6722
|
let firstText = preIngested ? text : await ingestContext(msg, text);
|
|
6352
6723
|
const { thread: resolved, recreated } = await resolveThread(sessionKey, msg.chatId, {
|
|
6353
6724
|
mode: perm.mode,
|
|
6354
|
-
network: perm.network
|
|
6725
|
+
network: perm.network,
|
|
6726
|
+
autoCompact: perm.autoCompact
|
|
6355
6727
|
});
|
|
6356
6728
|
let thread = resolved;
|
|
6357
6729
|
const neverSeen = !thread;
|
|
@@ -6359,7 +6731,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6359
6731
|
const prior = neverSeen ? void 0 : await getSession(sessionKey);
|
|
6360
6732
|
if (!thread) {
|
|
6361
6733
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
6362
|
-
thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network });
|
|
6734
|
+
thread = await backend.startThread({ cwd, mode: perm.mode, network: perm.network, autoCompact: perm.autoCompact });
|
|
6363
6735
|
sessions.set(sessionKey, thread);
|
|
6364
6736
|
await upsertSession({
|
|
6365
6737
|
threadId: sessionKey,
|
|
@@ -6375,7 +6747,7 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6375
6747
|
updatedAt: Date.now()
|
|
6376
6748
|
});
|
|
6377
6749
|
}
|
|
6378
|
-
if (msg.threadId && (codexEmpty || prior?.lastSeenAt !== void 0)) {
|
|
6750
|
+
if (!goal && msg.threadId && (codexEmpty || prior?.lastSeenAt !== void 0)) {
|
|
6379
6751
|
const history = await fetchThreadContext(channel, msg.threadId, {
|
|
6380
6752
|
sinceTime: codexEmpty ? 0 : prior?.lastSeenAt ?? 0,
|
|
6381
6753
|
excludeMessageId: msg.messageId
|
|
@@ -6384,23 +6756,22 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6384
6756
|
}
|
|
6385
6757
|
if (!neverSeen) void patchSession(sessionKey, { lastSeenAt: msg.createTime }).catch(() => void 0);
|
|
6386
6758
|
reserved.thread = thread;
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
);
|
|
6759
|
+
const launchOpts = {
|
|
6760
|
+
chatId: msg.chatId,
|
|
6761
|
+
replyTo: msg.messageId,
|
|
6762
|
+
replyInThread: !flat,
|
|
6763
|
+
flat,
|
|
6764
|
+
thread,
|
|
6765
|
+
firstText,
|
|
6766
|
+
images,
|
|
6767
|
+
knownThreadId: sessionKey,
|
|
6768
|
+
requesterOpenId: msg.senderId
|
|
6769
|
+
};
|
|
6770
|
+
if (goal) await launchGoalRun(launchOpts);
|
|
6771
|
+
else await launchRun(launchOpts, reaction);
|
|
6401
6772
|
} catch (err) {
|
|
6402
6773
|
active.delete(sessionKey);
|
|
6403
|
-
reaction
|
|
6774
|
+
reaction?.done();
|
|
6404
6775
|
log.fail("intake", err);
|
|
6405
6776
|
await channel.send(msg.chatId, { markdown: `\u274C ${err instanceof Error ? err.message : String(err)}` }, { replyTo: msg.messageId, replyInThread: !flat }).catch(() => void 0);
|
|
6406
6777
|
}
|
|
@@ -6418,7 +6789,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6418
6789
|
model: rec.model,
|
|
6419
6790
|
effort: rec.effort,
|
|
6420
6791
|
mode: perm?.mode,
|
|
6421
|
-
network: perm?.network
|
|
6792
|
+
network: perm?.network,
|
|
6793
|
+
autoCompact: perm?.autoCompact
|
|
6422
6794
|
});
|
|
6423
6795
|
sessions.set(threadId, resumed);
|
|
6424
6796
|
return { thread: resumed, recreated: false };
|
|
@@ -6431,7 +6803,8 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6431
6803
|
model: rec.model,
|
|
6432
6804
|
effort: rec.effort,
|
|
6433
6805
|
mode: perm?.mode ?? project?.mode,
|
|
6434
|
-
network: perm?.network ?? project?.network
|
|
6806
|
+
network: perm?.network ?? project?.network,
|
|
6807
|
+
autoCompact: perm?.autoCompact ?? project?.autoCompact
|
|
6435
6808
|
});
|
|
6436
6809
|
sessions.set(threadId, fresh);
|
|
6437
6810
|
await patchSession(threadId, { codexThreadId: fresh.codexThreadId }).catch(() => void 0);
|
|
@@ -6450,44 +6823,47 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6450
6823
|
}
|
|
6451
6824
|
if (closed) log.info("console", "tier-evict", { chatId, closed });
|
|
6452
6825
|
}
|
|
6453
|
-
function startTopicDirectly(msg, text, project) {
|
|
6826
|
+
function startTopicDirectly(msg, text, project, goal) {
|
|
6454
6827
|
void withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
6455
|
-
const reaction = runReaction(msg.messageId, !sema.hasFree());
|
|
6828
|
+
const reaction = goal ? void 0 : runReaction(msg.messageId, !sema.hasFree());
|
|
6456
6829
|
const cwd = project?.cwd ?? fallbackCwd;
|
|
6457
6830
|
const perm = turnPerm(project, msg.senderId);
|
|
6458
6831
|
if (project) void refreshBranch(channel, project).catch(() => void 0);
|
|
6459
6832
|
const { model, effort } = pickDefault(await listModels());
|
|
6460
6833
|
let thread;
|
|
6461
6834
|
try {
|
|
6462
|
-
thread = await backend.startThread({ cwd, model, effort, mode: perm.mode, network: perm.network });
|
|
6835
|
+
thread = await backend.startThread({ cwd, model, effort, mode: perm.mode, network: perm.network, autoCompact: perm.autoCompact });
|
|
6463
6836
|
} catch (err) {
|
|
6464
|
-
reaction
|
|
6837
|
+
reaction?.done();
|
|
6465
6838
|
log.fail("card", err, { phase: "start-topic" });
|
|
6466
6839
|
await channel.send(msg.chatId, { markdown: `\u274C \u542F\u52A8\u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}` }, { replyTo: msg.messageId }).catch(() => void 0);
|
|
6467
6840
|
return;
|
|
6468
6841
|
}
|
|
6469
6842
|
const images = messageHasImages(msg) ? await collectInboundImages(channel, msg) : void 0;
|
|
6470
6843
|
const firstText = await ingestContext(msg, text) || "\u4F60\u597D\uFF0C\u6211\u4EEC\u5F00\u59CB\u5427\u3002";
|
|
6471
|
-
log.info("card", "start", { project: project?.name ?? "(unregistered)", model, effort, images: images?.length ?? 0 });
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6844
|
+
log.info("card", "start", { project: project?.name ?? "(unregistered)", model, effort, images: images?.length ?? 0, goal: Boolean(goal) });
|
|
6845
|
+
const launchOpts = {
|
|
6846
|
+
chatId: msg.chatId,
|
|
6847
|
+
replyTo: msg.messageId,
|
|
6848
|
+
replyInThread: true,
|
|
6849
|
+
thread,
|
|
6850
|
+
firstText,
|
|
6851
|
+
images,
|
|
6852
|
+
model,
|
|
6853
|
+
effort,
|
|
6854
|
+
cwd,
|
|
6855
|
+
summary: stripFileTokens(text).slice(0, 80) || "(\u7A7A)",
|
|
6856
|
+
requesterOpenId: msg.senderId,
|
|
6857
|
+
roleSuffix: perm.roleSuffix
|
|
6858
|
+
};
|
|
6859
|
+
if (goal) await launchGoalRun(launchOpts);
|
|
6860
|
+
else
|
|
6861
|
+
await launchRun(
|
|
6862
|
+
launchOpts,
|
|
6863
|
+
reaction,
|
|
6864
|
+
() => reaction?.done()
|
|
6865
|
+
// topic created → ✅ DONE (don't wait for the reply)
|
|
6866
|
+
);
|
|
6491
6867
|
}).catch((err) => log.fail("intake", err));
|
|
6492
6868
|
}
|
|
6493
6869
|
async function postResumeCard(msg) {
|
|
@@ -6533,6 +6909,76 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6533
6909
|
log.info("card", "model", { threadId: sessionKey, model: state.model, effort: state.effort });
|
|
6534
6910
|
});
|
|
6535
6911
|
}
|
|
6912
|
+
async function postContextCard(msg, sessionKey, inThread) {
|
|
6913
|
+
const u = lastUsage.get(sessionKey);
|
|
6914
|
+
await sendManagedCard(channel, msg.chatId, buildContextCard(u?.used ?? 0, u?.window ?? null), msg.messageId, inThread).catch(
|
|
6915
|
+
(err) => log.fail("card", err, { phase: "context" })
|
|
6916
|
+
);
|
|
6917
|
+
}
|
|
6918
|
+
const COMPACT_ANIM_INTERVAL_MS = 800;
|
|
6919
|
+
async function runCompact(msg, sessionKey, inThread, perm) {
|
|
6920
|
+
const reply = (markdown) => channel.send(msg.chatId, { markdown }, { replyTo: msg.messageId, replyInThread: inThread }).then(() => void 0, () => void 0);
|
|
6921
|
+
if (active.get(sessionKey)) {
|
|
6922
|
+
await reply("\u23F3 \u8FD9\u4E00\u8F6E\u8FD8\u5728\u8DD1\uFF0C\u7ED3\u675F\u540E\u518D `/compact`\u3002");
|
|
6923
|
+
return;
|
|
6924
|
+
}
|
|
6925
|
+
const { thread } = await resolveThread(sessionKey, msg.chatId, {
|
|
6926
|
+
mode: perm.mode,
|
|
6927
|
+
network: perm.network,
|
|
6928
|
+
autoCompact: perm.autoCompact
|
|
6929
|
+
});
|
|
6930
|
+
if (!thread) {
|
|
6931
|
+
await reply("\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u5F00\u59CB\uFF0C\u5148\u53D1\u6761\u6D88\u606F\u804A\u4E24\u53E5\u518D `/compact`\u3002");
|
|
6932
|
+
return;
|
|
6933
|
+
}
|
|
6934
|
+
let cardMsgId;
|
|
6935
|
+
try {
|
|
6936
|
+
const sent = await sendManagedCard(channel, msg.chatId, buildCompactingCard(0), msg.messageId, inThread);
|
|
6937
|
+
cardMsgId = sent.messageId;
|
|
6938
|
+
} catch (err) {
|
|
6939
|
+
log.fail("card", err, { phase: "compact-start-card" });
|
|
6940
|
+
}
|
|
6941
|
+
let stop = false;
|
|
6942
|
+
const wakers = [];
|
|
6943
|
+
const sleep = (ms) => new Promise((res) => {
|
|
6944
|
+
const t = setTimeout(res, ms);
|
|
6945
|
+
wakers.push(() => {
|
|
6946
|
+
clearTimeout(t);
|
|
6947
|
+
res();
|
|
6948
|
+
});
|
|
6949
|
+
});
|
|
6950
|
+
const anim = (async () => {
|
|
6951
|
+
let tick = 0;
|
|
6952
|
+
while (!stop && cardMsgId) {
|
|
6953
|
+
await sleep(COMPACT_ANIM_INTERVAL_MS);
|
|
6954
|
+
if (stop || !cardMsgId) break;
|
|
6955
|
+
tick++;
|
|
6956
|
+
await updateManagedCard(channel, cardMsgId, buildCompactingCard(tick)).catch(() => void 0);
|
|
6957
|
+
}
|
|
6958
|
+
})();
|
|
6959
|
+
const settle = async (result) => {
|
|
6960
|
+
stop = true;
|
|
6961
|
+
wakers.forEach((w) => w());
|
|
6962
|
+
await anim;
|
|
6963
|
+
if (cardMsgId && await updateManagedCard(channel, cardMsgId, result)) return;
|
|
6964
|
+
await sendManagedCard(channel, msg.chatId, result, msg.messageId, inThread).catch(
|
|
6965
|
+
(err) => log.fail("card", err, { phase: "compact-settle" })
|
|
6966
|
+
);
|
|
6967
|
+
};
|
|
6968
|
+
const before = lastUsage.get(sessionKey) ?? null;
|
|
6969
|
+
try {
|
|
6970
|
+
const { usage } = await thread.compact();
|
|
6971
|
+
if (usage) lastUsage.set(sessionKey, { used: usage.usedTokens, window: usage.contextWindow });
|
|
6972
|
+
else lastUsage.delete(sessionKey);
|
|
6973
|
+
log.info("intake", "compact", { sessionKey, used: usage?.usedTokens ?? null, before: before?.used ?? null });
|
|
6974
|
+
await settle(buildCompactedCard(usage, before));
|
|
6975
|
+
} catch (err) {
|
|
6976
|
+
const m = err instanceof Error ? err.message : String(err);
|
|
6977
|
+
const unsupported = /method not found|-32601|unknown (method|request)/i.test(m);
|
|
6978
|
+
log.fail("intake", err, { phase: "compact" });
|
|
6979
|
+
await settle(buildCompactFailedCard(unsupported ? "\u5F53\u524D codex \u7248\u672C\u4E0D\u652F\u6301 /compact\uFF0C\u8BF7\u5347\u7EA7\u540E\u518D\u8BD5\u3002" : m));
|
|
6980
|
+
}
|
|
6981
|
+
}
|
|
6536
6982
|
async function postHelpCard(msg, scope, inThread = false, project) {
|
|
6537
6983
|
const noMention = project ? project.noMention ?? defaultNoMention(project) : true;
|
|
6538
6984
|
await withTrace({ chatId: msg.chatId, msgId: msg.messageId }, async () => {
|
|
@@ -6544,28 +6990,31 @@ function createOrchestrator(channel, cfg, fallbackCwd) {
|
|
|
6544
6990
|
}
|
|
6545
6991
|
const dispatcher = new CardDispatcher(channel, cfg);
|
|
6546
6992
|
const PENDING_TTL_MS = 30 * 6e4;
|
|
6993
|
+
const GOAL_MAX_MS = 30 * 6e4;
|
|
6547
6994
|
const CARD_SETTLE_MS = 500;
|
|
6548
6995
|
const settleUpdate = (msgId, c, fallbackChatId) => {
|
|
6549
6996
|
const armedAt = Date.now();
|
|
6550
6997
|
void (async () => {
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6998
|
+
try {
|
|
6999
|
+
await new Promise((r) => setTimeout(r, CARD_SETTLE_MS));
|
|
7000
|
+
const card2 = typeof c === "function" ? await c() : c;
|
|
7001
|
+
const ok = await updateManagedCard(channel, msgId, card2);
|
|
7002
|
+
log.info("console", "settle-update", { msgId, ok, waitedMs: Date.now() - armedAt, fallback: !ok && !!fallbackChatId });
|
|
7003
|
+
if (!ok && fallbackChatId) {
|
|
7004
|
+
await sendManagedCard(channel, fallbackChatId, card2);
|
|
7005
|
+
}
|
|
7006
|
+
} catch (err) {
|
|
7007
|
+
log.fail("console", err, { phase: "settle-update", msgId });
|
|
6559
7008
|
}
|
|
6560
7009
|
})();
|
|
6561
7010
|
};
|
|
6562
7011
|
function pruneResumePending() {
|
|
6563
7012
|
const now = Date.now();
|
|
6564
|
-
for (const [
|
|
7013
|
+
for (const [k2, s] of resumePending) if (now - s.createdAt > PENDING_TTL_MS) resumePending.delete(k2);
|
|
6565
7014
|
}
|
|
6566
7015
|
function pruneModelPending() {
|
|
6567
7016
|
const now = Date.now();
|
|
6568
|
-
for (const [
|
|
7017
|
+
for (const [k2, s] of modelPending) if (now - s.createdAt > PENDING_TTL_MS) modelPending.delete(k2);
|
|
6569
7018
|
}
|
|
6570
7019
|
function authPending(map, evt) {
|
|
6571
7020
|
const state = map.get(evt.messageId);
|
|
@@ -6929,6 +7378,19 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
6929
7378
|
}
|
|
6930
7379
|
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", noMention: on });
|
|
6931
7380
|
});
|
|
7381
|
+
}).on(GS.setAutoCompact, ({ evt, value }) => {
|
|
7382
|
+
if (!isAdmin(cfg, evt.operator?.openId ?? "")) return;
|
|
7383
|
+
const on = value.v === "on";
|
|
7384
|
+
patch(evt, async () => {
|
|
7385
|
+
const project = await getProjectByChatId(evt.chatId);
|
|
7386
|
+
if (project) {
|
|
7387
|
+
await updateProject(project.name, { autoCompact: on });
|
|
7388
|
+
await evictLiveSessionsForChat(project.chatId);
|
|
7389
|
+
log.info("console", "group-autocompact", { project: project.name, on });
|
|
7390
|
+
return buildGroupSettingsCard({ ...project, autoCompact: on });
|
|
7391
|
+
}
|
|
7392
|
+
return buildGroupSettingsCard({ name: "\u672C\u7FA4", kind: "multi", autoCompact: on });
|
|
7393
|
+
});
|
|
6932
7394
|
}).on(DM.admins, ({ evt }) => {
|
|
6933
7395
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
6934
7396
|
patch(
|
|
@@ -7020,6 +7482,15 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7020
7482
|
const p = await getProjectByName(name);
|
|
7021
7483
|
return p ? buildProjectSettingsCard(p) : buildDmMenuCard();
|
|
7022
7484
|
});
|
|
7485
|
+
}).on(DM.projectTopics, ({ evt, value }) => {
|
|
7486
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7487
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
7488
|
+
patch(evt, async () => {
|
|
7489
|
+
const p = await getProjectByName(name);
|
|
7490
|
+
if (!p) return buildDmMenuCard();
|
|
7491
|
+
const sessions2 = (await listSessions()).filter((s) => s.chatId === p.chatId);
|
|
7492
|
+
return buildProjectTopicsCard(p, sessions2);
|
|
7493
|
+
});
|
|
7023
7494
|
}).on(DM.setNoMentionDm, ({ evt, value }) => {
|
|
7024
7495
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7025
7496
|
const name = typeof value.n === "string" ? value.n : "";
|
|
@@ -7030,6 +7501,18 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7030
7501
|
await updateProject(name, { noMention: on });
|
|
7031
7502
|
return buildProjectSettingsCard({ ...p, noMention: on });
|
|
7032
7503
|
});
|
|
7504
|
+
}).on(DM.setAutoCompactDm, ({ evt, value }) => {
|
|
7505
|
+
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7506
|
+
const name = typeof value.n === "string" ? value.n : "";
|
|
7507
|
+
const on = value.v === "on";
|
|
7508
|
+
patch(evt, async () => {
|
|
7509
|
+
const p = await getProjectByName(name);
|
|
7510
|
+
if (!p) return buildDmMenuCard();
|
|
7511
|
+
await updateProject(name, { autoCompact: on });
|
|
7512
|
+
await evictLiveSessionsForChat(p.chatId);
|
|
7513
|
+
log.info("console", "project-autocompact", { project: name, on });
|
|
7514
|
+
return buildProjectSettingsCard({ ...p, autoCompact: on });
|
|
7515
|
+
});
|
|
7033
7516
|
}).on(DM.permission, ({ evt, value }) => {
|
|
7034
7517
|
if (!dmAdmin(evt.operator?.openId)) return;
|
|
7035
7518
|
const name = typeof value.n === "string" ? value.n : "";
|
|
@@ -7221,6 +7704,14 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7221
7704
|
}
|
|
7222
7705
|
lastEvAt = tEv;
|
|
7223
7706
|
evCount++;
|
|
7707
|
+
if (et === "context_usage" && topicThreadId) {
|
|
7708
|
+
const cu = ev;
|
|
7709
|
+
lastUsage.set(topicThreadId, { used: cu.usedTokens, window: cu.contextWindow });
|
|
7710
|
+
} else if (et === "context_compacted") {
|
|
7711
|
+
void sendManagedCard(channel, opts.chatId, buildAutoCompactCard(), cardMsgId, !opts.flat).catch(
|
|
7712
|
+
(err) => log.fail("card", err, { phase: "auto-compact-notice" })
|
|
7713
|
+
);
|
|
7714
|
+
}
|
|
7224
7715
|
render.apply(ev);
|
|
7225
7716
|
rc.rs = render.snapshot();
|
|
7226
7717
|
stream2.streamCoalesced(channel, buildRunCard(rc), ANSWER_EID);
|
|
@@ -7293,6 +7784,184 @@ ${tail}` }, { replyTo: evt.messageId }).catch(() => void 0);
|
|
|
7293
7784
|
release();
|
|
7294
7785
|
}
|
|
7295
7786
|
}
|
|
7787
|
+
async function launchGoalRun(opts) {
|
|
7788
|
+
const objective = opts.firstText;
|
|
7789
|
+
const release = await sema.acquire();
|
|
7790
|
+
let activeKey = opts.knownThreadId ?? `pending:${opts.replyTo}`;
|
|
7791
|
+
let topicThreadId = opts.knownThreadId;
|
|
7792
|
+
const state = active.get(activeKey) ?? { queue: [], requesterOpenId: opts.requesterOpenId };
|
|
7793
|
+
state.thread = opts.thread;
|
|
7794
|
+
if (opts.requesterOpenId) state.requesterOpenId = opts.requesterOpenId;
|
|
7795
|
+
active.set(activeKey, state);
|
|
7796
|
+
if (opts.knownThreadId) sessions.set(opts.knownThreadId, opts.thread);
|
|
7797
|
+
const persist = async (threadId) => {
|
|
7798
|
+
await upsertSession({
|
|
7799
|
+
threadId,
|
|
7800
|
+
chatId: opts.chatId,
|
|
7801
|
+
cwd: opts.cwd ?? fallbackCwd,
|
|
7802
|
+
codexThreadId: opts.thread.codexThreadId,
|
|
7803
|
+
model: opts.model,
|
|
7804
|
+
effort: opts.effort,
|
|
7805
|
+
summary: opts.summary ?? objective.slice(0, 80),
|
|
7806
|
+
createdAt: Date.now(),
|
|
7807
|
+
updatedAt: Date.now()
|
|
7808
|
+
}).catch(() => void 0);
|
|
7809
|
+
};
|
|
7810
|
+
let cur = null;
|
|
7811
|
+
let replyTo = opts.replyTo;
|
|
7812
|
+
let replyInThread = opts.flat ? false : opts.replyInThread ?? Boolean(opts.knownThreadId);
|
|
7813
|
+
const adoptThreadId = async (messageId, card2) => {
|
|
7814
|
+
if (activeKey.startsWith("pending:")) {
|
|
7815
|
+
const tid = await getThreadId(channel, messageId);
|
|
7816
|
+
if (tid) {
|
|
7817
|
+
const key = opts.roleSuffix ? `${tid}#${opts.roleSuffix}` : tid;
|
|
7818
|
+
active.delete(activeKey);
|
|
7819
|
+
active.set(key, state);
|
|
7820
|
+
sessions.set(key, opts.thread);
|
|
7821
|
+
activeKey = key;
|
|
7822
|
+
topicThreadId = key;
|
|
7823
|
+
card2.threadId = key;
|
|
7824
|
+
await persist(key);
|
|
7825
|
+
}
|
|
7826
|
+
} else {
|
|
7827
|
+
topicThreadId = activeKey;
|
|
7828
|
+
card2.threadId = activeKey;
|
|
7829
|
+
}
|
|
7830
|
+
};
|
|
7831
|
+
const promoteCard = (msgId, card2) => {
|
|
7832
|
+
if (!topicThreadId) return;
|
|
7833
|
+
const prev = lastRunCard.get(topicThreadId);
|
|
7834
|
+
if (prev && prev !== msgId) {
|
|
7835
|
+
const prevState = runCards.get(prev);
|
|
7836
|
+
const prevStream = runStreams.get(prev);
|
|
7837
|
+
if (prevState && prevStream) void prevStream.updateCard(channel, buildRunCardPlain(prevState));
|
|
7838
|
+
runCards.delete(prev);
|
|
7839
|
+
runStreams.delete(prev);
|
|
7840
|
+
}
|
|
7841
|
+
lastRunCard.set(topicThreadId, msgId);
|
|
7842
|
+
runCards.set(msgId, card2);
|
|
7843
|
+
};
|
|
7844
|
+
const finalizeCard = async (ctx) => {
|
|
7845
|
+
if (!ctx || !ctx.stream || !ctx.cardMsgId) return;
|
|
7846
|
+
await ctx.stream.drain();
|
|
7847
|
+
ctx.render.finalize();
|
|
7848
|
+
ctx.rc.rs = ctx.render.snapshot();
|
|
7849
|
+
await ctx.stream.updateCard(channel, buildRunCard(ctx.rc));
|
|
7850
|
+
runsByCard.delete(ctx.cardMsgId);
|
|
7851
|
+
promoteCard(ctx.cardMsgId, ctx.rc);
|
|
7852
|
+
};
|
|
7853
|
+
const startTurn = () => {
|
|
7854
|
+
const render = new RunRender();
|
|
7855
|
+
render.showTools = getShowToolCalls(cfg);
|
|
7856
|
+
const rc = { rs: render.snapshot(), requesterOpenId: opts.requesterOpenId, showTools: render.showTools, hideStop: true };
|
|
7857
|
+
return { render, rc, stream: null, cardMsgId: null };
|
|
7858
|
+
};
|
|
7859
|
+
const ensureCard = async (ctx) => {
|
|
7860
|
+
if (ctx.stream) return;
|
|
7861
|
+
const stream2 = new RunCardStream();
|
|
7862
|
+
const cardMsgId = await stream2.create(channel, opts.chatId, buildRunCard(ctx.rc), { replyTo, replyInThread });
|
|
7863
|
+
ctx.rc.cardKey = cardMsgId;
|
|
7864
|
+
ctx.stream = stream2;
|
|
7865
|
+
ctx.cardMsgId = cardMsgId;
|
|
7866
|
+
runsByCard.set(cardMsgId, state);
|
|
7867
|
+
runStreams.set(cardMsgId, stream2);
|
|
7868
|
+
await adoptThreadId(cardMsgId, ctx.rc);
|
|
7869
|
+
replyTo = cardMsgId;
|
|
7870
|
+
replyInThread = !opts.flat;
|
|
7871
|
+
};
|
|
7872
|
+
let lastStatus = "active";
|
|
7873
|
+
let goalTokens = 0;
|
|
7874
|
+
let goalSeconds = 0;
|
|
7875
|
+
let goalErrorMsg;
|
|
7876
|
+
let capped = false;
|
|
7877
|
+
let resolveCap;
|
|
7878
|
+
const capSignal = new Promise((res) => {
|
|
7879
|
+
resolveCap = res;
|
|
7880
|
+
});
|
|
7881
|
+
const capTimer = setTimeout(() => {
|
|
7882
|
+
capped = true;
|
|
7883
|
+
resolveCap();
|
|
7884
|
+
}, GOAL_MAX_MS);
|
|
7885
|
+
try {
|
|
7886
|
+
const run = opts.thread.runGoal(objective);
|
|
7887
|
+
state.run = run;
|
|
7888
|
+
const guarded = withIdleTimeout(run.events, 0, () => void 0, capSignal);
|
|
7889
|
+
for await (const ev of guarded) {
|
|
7890
|
+
if (ev.type === "goal_update") {
|
|
7891
|
+
lastStatus = ev.status;
|
|
7892
|
+
goalTokens = ev.tokensUsed;
|
|
7893
|
+
goalSeconds = ev.timeUsedSeconds;
|
|
7894
|
+
continue;
|
|
7895
|
+
}
|
|
7896
|
+
if (ev.type === "context_usage") {
|
|
7897
|
+
if (topicThreadId) lastUsage.set(topicThreadId, { used: ev.usedTokens, window: ev.contextWindow });
|
|
7898
|
+
if (cur) {
|
|
7899
|
+
cur.render.apply(ev);
|
|
7900
|
+
cur.rc.rs = cur.render.snapshot();
|
|
7901
|
+
}
|
|
7902
|
+
continue;
|
|
7903
|
+
}
|
|
7904
|
+
if (ev.type === "context_compacted") {
|
|
7905
|
+
void sendManagedCard(channel, opts.chatId, buildAutoCompactCard(), cur?.cardMsgId ?? void 0, !opts.flat).catch(
|
|
7906
|
+
(err) => log.fail("card", err, { phase: "auto-compact-notice" })
|
|
7907
|
+
);
|
|
7908
|
+
continue;
|
|
7909
|
+
}
|
|
7910
|
+
if (ev.type === "turn_started") {
|
|
7911
|
+
await finalizeCard(cur);
|
|
7912
|
+
cur = startTurn();
|
|
7913
|
+
continue;
|
|
7914
|
+
}
|
|
7915
|
+
if (ev.type === "done") {
|
|
7916
|
+
if (cur) {
|
|
7917
|
+
cur.render.apply(ev);
|
|
7918
|
+
await finalizeCard(cur);
|
|
7919
|
+
cur = null;
|
|
7920
|
+
}
|
|
7921
|
+
continue;
|
|
7922
|
+
}
|
|
7923
|
+
if (ev.type === "error") {
|
|
7924
|
+
goalErrorMsg = ev.message;
|
|
7925
|
+
if (!cur) continue;
|
|
7926
|
+
}
|
|
7927
|
+
if (!cur) cur = startTurn();
|
|
7928
|
+
cur.render.apply(ev);
|
|
7929
|
+
if (ev.type === "thinking" || ev.type === "thinking_delta") {
|
|
7930
|
+
if (cur.stream) {
|
|
7931
|
+
cur.rc.rs = cur.render.snapshot();
|
|
7932
|
+
cur.stream.streamCoalesced(channel, buildRunCard(cur.rc), ANSWER_EID);
|
|
7933
|
+
}
|
|
7934
|
+
continue;
|
|
7935
|
+
}
|
|
7936
|
+
await ensureCard(cur);
|
|
7937
|
+
cur.rc.rs = cur.render.snapshot();
|
|
7938
|
+
cur.stream.streamCoalesced(channel, buildRunCard(cur.rc), ANSWER_EID);
|
|
7939
|
+
}
|
|
7940
|
+
await finalizeCard(cur);
|
|
7941
|
+
cur = null;
|
|
7942
|
+
await opts.thread.clearGoal().catch(() => void 0);
|
|
7943
|
+
const status = capped ? "timeout" : goalErrorMsg && !isGoalTerminal(lastStatus) ? "error" : lastStatus;
|
|
7944
|
+
await sendManagedCard(
|
|
7945
|
+
channel,
|
|
7946
|
+
opts.chatId,
|
|
7947
|
+
buildGoalDoneCard({ objective, status, tokensUsed: goalTokens, timeUsedSeconds: goalSeconds, errorMessage: goalErrorMsg }),
|
|
7948
|
+
replyTo,
|
|
7949
|
+
!opts.flat
|
|
7950
|
+
).catch((err) => log.fail("card", err, { phase: "goal-done" }));
|
|
7951
|
+
if (topicThreadId) await patchSession(topicThreadId, { updatedAt: Date.now() }).catch(() => void 0);
|
|
7952
|
+
log.info("card", "goal-final", { status, tokens: goalTokens, seconds: goalSeconds });
|
|
7953
|
+
} catch (err) {
|
|
7954
|
+
log.fail("intake", err);
|
|
7955
|
+
await channel.send(opts.chatId, { markdown: `\u274C ${err instanceof Error ? err.message : String(err)}` }, { replyTo: opts.replyTo, replyInThread: !opts.flat }).catch(() => void 0);
|
|
7956
|
+
} finally {
|
|
7957
|
+
clearTimeout(capTimer);
|
|
7958
|
+
active.delete(activeKey);
|
|
7959
|
+
if (cur?.cardMsgId) runsByCard.delete(cur.cardMsgId);
|
|
7960
|
+
void opts.thread.close().catch(() => void 0);
|
|
7961
|
+
if (topicThreadId) sessions.delete(topicThreadId);
|
|
7962
|
+
release();
|
|
7963
|
+
}
|
|
7964
|
+
}
|
|
7296
7965
|
const onComment = async (evt) => {
|
|
7297
7966
|
await withTrace({ chatId: "comment" }, async () => {
|
|
7298
7967
|
log.info("comment", "enter", {
|