appback-remoteagent 0.14.7 → 0.15.1
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/bot.js +113 -4
- package/dist/telegram-command-menu.js +1 -0
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -18,6 +18,7 @@ const HELP_TEXT = [
|
|
|
18
18
|
"/list [-a]",
|
|
19
19
|
"/new",
|
|
20
20
|
"/switch <session>",
|
|
21
|
+
"/plan <count>",
|
|
21
22
|
"/batch start|send|cancel|status",
|
|
22
23
|
"/attach codex <thread_id>",
|
|
23
24
|
"/attach claude <session_id>",
|
|
@@ -29,7 +30,8 @@ const HELP_TEXT = [
|
|
|
29
30
|
"/state [clear|note <text>]",
|
|
30
31
|
"/artifacts list|cleanup <days>",
|
|
31
32
|
"/secret set|list|remove",
|
|
32
|
-
"/docs pin|find|list|remove",
|
|
33
|
+
"/docs pin|find|list|remove|reinforce",
|
|
34
|
+
"/보강 <count>",
|
|
33
35
|
"/bots",
|
|
34
36
|
"/bot add <token>",
|
|
35
37
|
"/bot doctor",
|
|
@@ -70,6 +72,7 @@ const RECOGNIZED_COMMANDS = new Set([
|
|
|
70
72
|
"list",
|
|
71
73
|
"new",
|
|
72
74
|
"switch",
|
|
75
|
+
"plan",
|
|
73
76
|
"batch",
|
|
74
77
|
"attach",
|
|
75
78
|
"model",
|
|
@@ -258,6 +261,19 @@ export function createBot(token, bridge, botManagement, botInfo) {
|
|
|
258
261
|
throw error;
|
|
259
262
|
}
|
|
260
263
|
};
|
|
264
|
+
const runPlanDocumentReinforcement = async (ctx, count) => {
|
|
265
|
+
if (!ctx.chat) {
|
|
266
|
+
throw new Error("Telegram chat context is missing.");
|
|
267
|
+
}
|
|
268
|
+
const botId = getBotId();
|
|
269
|
+
const chatId = String(ctx.chat.id);
|
|
270
|
+
await bridge.logSystem(botId, chatId, `Plan document reinforcement requested (${count} turns max).`);
|
|
271
|
+
await runWithPendingAnimation(token, ctx.chat.id, async (helpers) => {
|
|
272
|
+
return {
|
|
273
|
+
chunks: await routeTelegramWorkLoop(bridge, botId, chatId, formatPlanDocumentReinforcementPrompt(count), "Plan document reinforcement", botManagement, helpers, autoContinue, memoryService, (blocks) => blocks, { maxTurns: count }),
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
};
|
|
261
277
|
bot.use(async (ctx, next) => {
|
|
262
278
|
const updateKind = Object.keys(ctx.update).join(",");
|
|
263
279
|
const text = ctx.message?.text ?? ctx.editedMessage?.text ?? ctx.channelPost?.text ?? "";
|
|
@@ -365,6 +381,19 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
365
381
|
}
|
|
366
382
|
await reply(ctx, `Switched this chat to session ${sessionId}.\n\n${bridge.formatCurrentSession(mapping)}`);
|
|
367
383
|
});
|
|
384
|
+
bot.command("plan", async (ctx) => {
|
|
385
|
+
const { args, rest } = parseCommand(ctx.message?.text, 1);
|
|
386
|
+
if (rest?.trim()) {
|
|
387
|
+
await reply(ctx, "Usage: `/plan <1-10>`", { parse_mode: "Markdown" });
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const parsed = parsePlanReinforcementCount(args[0]?.trim());
|
|
391
|
+
if (parsed.kind === "invalid") {
|
|
392
|
+
await reply(ctx, "Usage: `/plan <1-10>`", { parse_mode: "Markdown" });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
await runPlanDocumentReinforcement(ctx, parsed.count);
|
|
396
|
+
});
|
|
368
397
|
bot.command("batch", async (ctx) => {
|
|
369
398
|
const botId = getBotId();
|
|
370
399
|
const chatId = String(ctx.chat.id);
|
|
@@ -649,6 +678,19 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
649
678
|
const { args, rest } = parseCommand(ctx.message?.text, 2);
|
|
650
679
|
const action = args[0]?.toLowerCase() || "list";
|
|
651
680
|
const keyword = args[1]?.trim();
|
|
681
|
+
if (action === "reinforce") {
|
|
682
|
+
if (rest?.trim()) {
|
|
683
|
+
await reply(ctx, "Usage: `/docs reinforce <1-10>`", { parse_mode: "Markdown" });
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const parsed = parsePlanReinforcementCount(keyword);
|
|
687
|
+
if (parsed.kind === "invalid") {
|
|
688
|
+
await reply(ctx, "Usage: `/docs reinforce <1-10>`", { parse_mode: "Markdown" });
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
await runPlanDocumentReinforcement(ctx, parsed.count);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
652
694
|
if (action === "list") {
|
|
653
695
|
await reply(ctx, await memoryService.listDocuments());
|
|
654
696
|
return;
|
|
@@ -681,7 +723,7 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
681
723
|
await reply(ctx, removed ? `Removed docs keyword ${keyword}.` : `Docs keyword was not found: ${keyword}`);
|
|
682
724
|
return;
|
|
683
725
|
}
|
|
684
|
-
await reply(ctx, "Usage: `/docs list`, `/docs find <keyword>`, `/docs pin <keyword> <path>`, or `/docs
|
|
726
|
+
await reply(ctx, "Usage: `/docs list`, `/docs find <keyword>`, `/docs pin <keyword> <path>`, `/docs remove <keyword>`, or `/docs reinforce <1-10>`", { parse_mode: "Markdown" });
|
|
685
727
|
});
|
|
686
728
|
bot.command("bots", async (ctx) => {
|
|
687
729
|
await ensureOwnerControlAccess(ctx);
|
|
@@ -799,6 +841,15 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
799
841
|
const voice = ctx.message.voice;
|
|
800
842
|
const audio = ctx.message.audio;
|
|
801
843
|
const text = ctx.message.text?.trim();
|
|
844
|
+
const planReinforcement = parseKoreanPlanReinforcementCommand(text, botId);
|
|
845
|
+
if (planReinforcement.kind === "matched") {
|
|
846
|
+
await runPlanDocumentReinforcement(ctx, planReinforcement.count);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (planReinforcement.kind === "invalid") {
|
|
850
|
+
await reply(ctx, "Usage: `/보강 <1-10>`", { parse_mode: "Markdown" });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
802
853
|
if (text && isRecognizedSlashCommand(text, botId)) {
|
|
803
854
|
return;
|
|
804
855
|
}
|
|
@@ -1130,7 +1181,7 @@ async function runWithPendingAnimation(botToken, chatId, task) {
|
|
|
1130
1181
|
}
|
|
1131
1182
|
}
|
|
1132
1183
|
}
|
|
1133
|
-
async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botManagement, helpers, autoContinue, memoryService, transform = (blocks) => blocks) {
|
|
1184
|
+
async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botManagement, helpers, autoContinue, memoryService, transform = (blocks) => blocks, options = {}) {
|
|
1134
1185
|
const currentSession = await bridge.status(botId, chatId);
|
|
1135
1186
|
const sessionId = currentSession?.session.sessionId;
|
|
1136
1187
|
const activeKey = workLoopKey(botId, chatId, sessionId);
|
|
@@ -1151,7 +1202,7 @@ async function routeTelegramWorkLoop(bridge, botId, chatId, message, label, botM
|
|
|
1151
1202
|
: "";
|
|
1152
1203
|
autoContinue.clear(botId, chatId, sessionId);
|
|
1153
1204
|
let prompt = appendManagedContext(appendReportProtocol(message), managedContext);
|
|
1154
|
-
const maxTurns = config.telegramAutoProgressMaxTurns;
|
|
1205
|
+
const maxTurns = options.maxTurns ?? config.telegramAutoProgressMaxTurns;
|
|
1155
1206
|
const emptyResponseRetries = config.telegramEmptyResponseRetries;
|
|
1156
1207
|
const retryableErrorRetries = config.telegramRetryableErrorRetries;
|
|
1157
1208
|
const retryableErrorDelayMs = config.telegramRetryableErrorDelayMs;
|
|
@@ -1580,6 +1631,9 @@ function classifyRetryableProviderIssue(message, retryAfterMs) {
|
|
|
1580
1631
|
if (isEmptyResponseError(message)) {
|
|
1581
1632
|
return { kind: "empty-response", retryAfterMs };
|
|
1582
1633
|
}
|
|
1634
|
+
if (/remote compact task|stream disconnected|websocket protocol error|connection reset without closing handshake/i.test(message)) {
|
|
1635
|
+
return { kind: "transport", retryAfterMs };
|
|
1636
|
+
}
|
|
1583
1637
|
return undefined;
|
|
1584
1638
|
}
|
|
1585
1639
|
function formatRetryableProviderRetryMessage(issue, attempt, maxAttempts) {
|
|
@@ -1589,6 +1643,8 @@ function formatRetryableProviderRetryMessage(issue, attempt, maxAttempts) {
|
|
|
1589
1643
|
return `선택한 모델이 capacity 상태라 ${waitSeconds}초 후 다시 시도합니다. (${attempt}/${maxAttempts})`;
|
|
1590
1644
|
case "empty-response":
|
|
1591
1645
|
return `후속 응답이 비어 있어 ${waitSeconds}초 후 다시 시도합니다. (${attempt}/${maxAttempts})`;
|
|
1646
|
+
case "transport":
|
|
1647
|
+
return `Codex 연결이 일시적으로 끊겨 ${waitSeconds}초 후 다시 시도합니다. (${attempt}/${maxAttempts})`;
|
|
1592
1648
|
}
|
|
1593
1649
|
}
|
|
1594
1650
|
function formatRetryableProviderFinalMessage(issue) {
|
|
@@ -1597,6 +1653,8 @@ function formatRetryableProviderFinalMessage(issue) {
|
|
|
1597
1653
|
return "선택한 모델이 capacity 상태라 자동 재시도를 모두 사용했습니다. 잠시 후 다시 시도하거나 `/model`로 다른 모델을 선택해 주세요.";
|
|
1598
1654
|
case "empty-response":
|
|
1599
1655
|
return "후속 응답이 반복해서 비어 자동 재시도를 중단했습니다. 같은 세션에서 다시 시도해 주세요.";
|
|
1656
|
+
case "transport":
|
|
1657
|
+
return "Codex 연결이 반복해서 끊겨 자동 재시도를 중단했습니다. 잠시 후 같은 세션에서 다시 시도해 주세요.";
|
|
1600
1658
|
}
|
|
1601
1659
|
}
|
|
1602
1660
|
function isProviderTimeoutError(message) {
|
|
@@ -1683,6 +1741,57 @@ function parseCommand(text, headCount) {
|
|
|
1683
1741
|
rest: remaining || undefined,
|
|
1684
1742
|
};
|
|
1685
1743
|
}
|
|
1744
|
+
function parsePlanReinforcementCount(raw) {
|
|
1745
|
+
const value = raw?.trim();
|
|
1746
|
+
if (!value) {
|
|
1747
|
+
return { kind: "valid", count: 5 };
|
|
1748
|
+
}
|
|
1749
|
+
if (!/^\d+$/.test(value)) {
|
|
1750
|
+
return { kind: "invalid" };
|
|
1751
|
+
}
|
|
1752
|
+
const count = Number(value);
|
|
1753
|
+
if (!Number.isInteger(count) || count < 1 || count > 10) {
|
|
1754
|
+
return { kind: "invalid" };
|
|
1755
|
+
}
|
|
1756
|
+
return { kind: "valid", count };
|
|
1757
|
+
}
|
|
1758
|
+
function parseKoreanPlanReinforcementCommand(text, botId) {
|
|
1759
|
+
const trimmed = text?.trim();
|
|
1760
|
+
if (!trimmed?.startsWith("/")) {
|
|
1761
|
+
return { kind: "none" };
|
|
1762
|
+
}
|
|
1763
|
+
const [commandToken, maybeCount, ...extra] = trimmed.slice(1).split(/\s+/);
|
|
1764
|
+
const [name, mention] = commandToken.split("@", 2);
|
|
1765
|
+
if (name !== "보강") {
|
|
1766
|
+
return { kind: "none" };
|
|
1767
|
+
}
|
|
1768
|
+
if (mention && mention.toLowerCase() !== botId.toLowerCase()) {
|
|
1769
|
+
return { kind: "none" };
|
|
1770
|
+
}
|
|
1771
|
+
if (extra.length > 0) {
|
|
1772
|
+
return { kind: "invalid" };
|
|
1773
|
+
}
|
|
1774
|
+
const parsed = parsePlanReinforcementCount(maybeCount);
|
|
1775
|
+
return parsed.kind === "valid" ? { kind: "matched", count: parsed.count } : { kind: "invalid" };
|
|
1776
|
+
}
|
|
1777
|
+
function formatPlanDocumentReinforcementPrompt(count) {
|
|
1778
|
+
return [
|
|
1779
|
+
`계획문서를 처음부터 끝까지 확인하고, 최대 ${count}회까지만 보강 루프를 수행하세요.`,
|
|
1780
|
+
"",
|
|
1781
|
+
"반복 절차:",
|
|
1782
|
+
"1. 계획문서와 관련 문서를 실제로 읽고 보강할 내용이 있는지 판단하세요.",
|
|
1783
|
+
"2. 보강할 내용이 있으면 직접 문서를 수정하세요.",
|
|
1784
|
+
"3. 수정 후 같은 기준으로 다시 점검하세요.",
|
|
1785
|
+
`4. 더 이상 보강할 내용이 없거나 ${count}회에 도달하면 종료하세요.`,
|
|
1786
|
+
"",
|
|
1787
|
+
"중요 규칙:",
|
|
1788
|
+
"- 추정하지 말고 실제 파일 경로와 확인 근거를 남기세요.",
|
|
1789
|
+
"- 같은 보강을 반복하지 마세요.",
|
|
1790
|
+
"- 보강할 내용이 없으면 없다고 보고하고 종료하세요.",
|
|
1791
|
+
"- 권한, 정보, 파일 위치가 부족하면 REPORT:blocked로 정확한 blocker를 말하세요.",
|
|
1792
|
+
"- 완료 시 확인한 문서, 변경한 문서, 남은 위험, 검증 결과를 짧게 보고하세요.",
|
|
1793
|
+
].join("\n");
|
|
1794
|
+
}
|
|
1686
1795
|
function commandTarget(args, rest) {
|
|
1687
1796
|
return (rest?.trim() || args.slice(1).join(" ").trim() || args[0]?.trim() || "");
|
|
1688
1797
|
}
|
|
@@ -6,6 +6,7 @@ export const TELEGRAM_COMMAND_MENU = [
|
|
|
6
6
|
{ command: "list", description: "List sessions" },
|
|
7
7
|
{ command: "new", description: "Start a fresh session" },
|
|
8
8
|
{ command: "switch", description: "Switch to a session" },
|
|
9
|
+
{ command: "plan", description: "Reinforce planning documents" },
|
|
9
10
|
{ command: "status", description: "Show current session status" },
|
|
10
11
|
{ command: "attach", description: "Attach an existing provider session" },
|
|
11
12
|
{ command: "state", description: "Show or edit session state notes" },
|