appback-remoteagent 0.15.1 → 0.15.3
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 +70 -68
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -105,24 +105,18 @@ class AutoContinueController {
|
|
|
105
105
|
stopInProgress = new Set();
|
|
106
106
|
stopDedupUntil = new Map();
|
|
107
107
|
stopDedupMs = 10_000;
|
|
108
|
-
suppressUntil = new Map();
|
|
109
|
-
suppressMs = 60_000;
|
|
110
108
|
constructor(stopGatePath) {
|
|
111
109
|
this.stopGatePath = stopGatePath;
|
|
112
|
-
this.
|
|
110
|
+
this.clearLegacyStopGates();
|
|
113
111
|
}
|
|
114
112
|
requestStop(botId, chatId, sessionId) {
|
|
115
113
|
for (const key of this.keys(botId, chatId, sessionId)) {
|
|
116
114
|
this.stops.add(key);
|
|
117
|
-
this.suppressUntil.set(key, Date.now() + this.suppressMs);
|
|
118
115
|
}
|
|
119
|
-
this.persistStopGates();
|
|
120
116
|
}
|
|
121
117
|
requestSessionStop(sessionId) {
|
|
122
118
|
const key = this.sessionKey(sessionId);
|
|
123
119
|
this.stops.add(key);
|
|
124
|
-
this.suppressUntil.set(key, Date.now() + this.suppressMs);
|
|
125
|
-
this.persistStopGates();
|
|
126
120
|
}
|
|
127
121
|
beginStop(botId, chatId, sessionId) {
|
|
128
122
|
const key = this.primaryKey(botId, chatId, sessionId);
|
|
@@ -153,26 +147,6 @@ class AutoContinueController {
|
|
|
153
147
|
isStopRequested(botId, chatId, sessionId) {
|
|
154
148
|
return this.keys(botId, chatId, sessionId).some((key) => this.stops.has(key));
|
|
155
149
|
}
|
|
156
|
-
isSuppressingNewWork(botId, chatId, sessionId) {
|
|
157
|
-
for (const key of this.keys(botId, chatId, sessionId)) {
|
|
158
|
-
if (this.isSuppressingKey(key)) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
isSuppressingKey(key) {
|
|
165
|
-
const until = this.suppressUntil.get(key);
|
|
166
|
-
if (!until) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
if (Date.now() > until) {
|
|
170
|
-
this.suppressUntil.delete(key);
|
|
171
|
-
this.persistStopGates();
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
150
|
keys(botId, chatId, sessionId) {
|
|
177
151
|
const keys = [this.chatKey(botId, chatId)];
|
|
178
152
|
if (sessionId) {
|
|
@@ -189,31 +163,12 @@ class AutoContinueController {
|
|
|
189
163
|
sessionKey(sessionId) {
|
|
190
164
|
return `session:${sessionId}`;
|
|
191
165
|
}
|
|
192
|
-
|
|
193
|
-
try {
|
|
194
|
-
const raw = fsSync.readFileSync(this.stopGatePath, "utf8");
|
|
195
|
-
const parsed = JSON.parse(raw);
|
|
196
|
-
const now = Date.now();
|
|
197
|
-
for (const [key, until] of Object.entries(parsed)) {
|
|
198
|
-
if (Number.isFinite(until) && until > now) {
|
|
199
|
-
this.suppressUntil.set(key, until);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
// Missing or invalid gate files should not prevent the bot from starting.
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
persistStopGates() {
|
|
208
|
-
const entries = Object.fromEntries(this.suppressUntil.entries());
|
|
166
|
+
clearLegacyStopGates() {
|
|
209
167
|
try {
|
|
210
|
-
fsSync.
|
|
211
|
-
const tmpPath = `${this.stopGatePath}.tmp`;
|
|
212
|
-
fsSync.writeFileSync(tmpPath, JSON.stringify(entries, null, 2), "utf8");
|
|
213
|
-
fsSync.renameSync(tmpPath, this.stopGatePath);
|
|
168
|
+
fsSync.rmSync(this.stopGatePath, { force: true });
|
|
214
169
|
}
|
|
215
170
|
catch {
|
|
216
|
-
//
|
|
171
|
+
// Legacy stop gate cleanup must not prevent the bot from starting.
|
|
217
172
|
}
|
|
218
173
|
}
|
|
219
174
|
}
|
|
@@ -267,10 +222,30 @@ export function createBot(token, bridge, botManagement, botInfo) {
|
|
|
267
222
|
}
|
|
268
223
|
const botId = getBotId();
|
|
269
224
|
const chatId = String(ctx.chat.id);
|
|
270
|
-
await bridge.logSystem(botId, chatId, `Plan document reinforcement requested (${count}
|
|
225
|
+
await bridge.logSystem(botId, chatId, `Plan document reinforcement requested (${count} check/apply cycle max).`);
|
|
271
226
|
await runWithPendingAnimation(token, ctx.chat.id, async (helpers) => {
|
|
227
|
+
for (let cycle = 1; cycle <= count; cycle += 1) {
|
|
228
|
+
const checkChunks = await routeTelegramWorkLoop(bridge, botId, chatId, formatPlanDocumentCheckPrompt(cycle, count), `Plan document check ${cycle}`, botManagement, helpers, autoContinue, memoryService, (blocks) => blocks, { maxTurns: 1 });
|
|
229
|
+
const decision = classifyPlanReinforcementDecision(checkChunks.join("\n"));
|
|
230
|
+
await helpers.reportProgress(formatPlanCycleProgress("확인 결과", cycle, count, stripPlanReinforcementMarkers(checkChunks)));
|
|
231
|
+
if (decision === "none") {
|
|
232
|
+
return {
|
|
233
|
+
chunks: [`보강할 내용이 없다고 판단되어 ${cycle}/${count}회차에서 종료했습니다.`],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (decision === "unknown") {
|
|
237
|
+
return {
|
|
238
|
+
chunks: [
|
|
239
|
+
"보강 필요 여부를 명확히 판정할 수 없어 중단했습니다.",
|
|
240
|
+
"계획문서 확인 응답에는 `PLAN_REINFORCE: needed` 또는 `PLAN_REINFORCE: none`이 포함되어야 합니다.",
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const applyChunks = await routeTelegramWorkLoop(bridge, botId, chatId, formatPlanDocumentApplyPrompt(cycle, count), `Plan document apply ${cycle}`, botManagement, helpers, autoContinue, memoryService, stripPlanReinforcementMarkers, { maxTurns: 1 });
|
|
245
|
+
await helpers.reportProgress(formatPlanCycleProgress("보강 결과", cycle, count, applyChunks));
|
|
246
|
+
}
|
|
272
247
|
return {
|
|
273
|
-
chunks:
|
|
248
|
+
chunks: [`최대 ${count}회 확인/보강 반복을 완료했습니다.`],
|
|
274
249
|
};
|
|
275
250
|
});
|
|
276
251
|
};
|
|
@@ -858,11 +833,6 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
858
833
|
return;
|
|
859
834
|
}
|
|
860
835
|
const mapping = await bridge.status(botId, chatId).catch(() => undefined);
|
|
861
|
-
if (autoContinue.isSuppressingNewWork(botId, chatId, mapping?.session.sessionId)) {
|
|
862
|
-
await bridge.logSystem(botId, chatId, "Telegram message ignored during stop cooldown.");
|
|
863
|
-
await reply(ctx, "Stopped. Ignored this message so the previous work does not restart. Send a new message again in a moment to start fresh.");
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
836
|
if (photo) {
|
|
867
837
|
const downloaded = await downloadTelegramFile(token, botId, chatId, photo.file_id, "telegram-photo.jpg");
|
|
868
838
|
await memoryService.recordArtifact({
|
|
@@ -1774,24 +1744,56 @@ function parseKoreanPlanReinforcementCommand(text, botId) {
|
|
|
1774
1744
|
const parsed = parsePlanReinforcementCount(maybeCount);
|
|
1775
1745
|
return parsed.kind === "valid" ? { kind: "matched", count: parsed.count } : { kind: "invalid" };
|
|
1776
1746
|
}
|
|
1777
|
-
function
|
|
1747
|
+
function formatPlanDocumentCheckPrompt(cycle, count) {
|
|
1778
1748
|
return [
|
|
1779
|
-
|
|
1749
|
+
"계획문서 처음부터 확인해서 보강해야할 내용 확인해",
|
|
1780
1750
|
"",
|
|
1781
|
-
|
|
1782
|
-
"1. 계획문서와 관련 문서를 실제로 읽고 보강할 내용이 있는지 판단하세요.",
|
|
1783
|
-
"2. 보강할 내용이 있으면 직접 문서를 수정하세요.",
|
|
1784
|
-
"3. 수정 후 같은 기준으로 다시 점검하세요.",
|
|
1785
|
-
`4. 더 이상 보강할 내용이 없거나 ${count}회에 도달하면 종료하세요.`,
|
|
1751
|
+
`현재 반복: ${cycle}/${count}`,
|
|
1786
1752
|
"",
|
|
1787
|
-
"
|
|
1788
|
-
"-
|
|
1789
|
-
"-
|
|
1790
|
-
"- 보강할 내용이
|
|
1753
|
+
"응답 규칙:",
|
|
1754
|
+
"- 첫 줄은 REPORT:result 또는 REPORT:blocked.",
|
|
1755
|
+
"- REPORT:result 본문 첫 줄은 반드시 `PLAN_REINFORCE: needed` 또는 `PLAN_REINFORCE: none`.",
|
|
1756
|
+
"- 보강할 내용이 있으면 `PLAN_REINFORCE: needed` 후 항목과 근거 파일을 답하세요.",
|
|
1757
|
+
"- 보강할 내용이 없으면 `PLAN_REINFORCE: none` 후 확인 근거 파일을 답하세요.",
|
|
1758
|
+
"- 이 단계에서는 문서를 수정하지 마세요. 확인과 답변만 하세요.",
|
|
1791
1759
|
"- 권한, 정보, 파일 위치가 부족하면 REPORT:blocked로 정확한 blocker를 말하세요.",
|
|
1792
|
-
"- 완료 시 확인한 문서, 변경한 문서, 남은 위험, 검증 결과를 짧게 보고하세요.",
|
|
1793
1760
|
].join("\n");
|
|
1794
1761
|
}
|
|
1762
|
+
function formatPlanDocumentApplyPrompt(cycle, count) {
|
|
1763
|
+
return [
|
|
1764
|
+
"보강해",
|
|
1765
|
+
"",
|
|
1766
|
+
`현재 반복: ${cycle}/${count}`,
|
|
1767
|
+
"",
|
|
1768
|
+
"방금 확인 응답에서 보강 필요하다고 답한 항목만 실제 문서에 반영하세요.",
|
|
1769
|
+
"",
|
|
1770
|
+
"응답 규칙:",
|
|
1771
|
+
"- 첫 줄은 REPORT:result 또는 REPORT:blocked.",
|
|
1772
|
+
"- REPORT:result 본문에는 변경한 파일 경로와 검증/확인 근거를 포함하세요.",
|
|
1773
|
+
"- 새 범위를 만들지 말고 방금 확인한 보강 항목만 처리하세요.",
|
|
1774
|
+
"- 권한, 정보, 파일 위치가 부족하면 REPORT:blocked로 정확한 blocker를 말하세요.",
|
|
1775
|
+
].join("\n");
|
|
1776
|
+
}
|
|
1777
|
+
function classifyPlanReinforcementDecision(text) {
|
|
1778
|
+
if (/PLAN_REINFORCE:\s*none/i.test(text)) {
|
|
1779
|
+
return "none";
|
|
1780
|
+
}
|
|
1781
|
+
if (/PLAN_REINFORCE:\s*needed/i.test(text)) {
|
|
1782
|
+
return "needed";
|
|
1783
|
+
}
|
|
1784
|
+
return "unknown";
|
|
1785
|
+
}
|
|
1786
|
+
function stripPlanReinforcementMarkers(blocks) {
|
|
1787
|
+
return blocks.map((block) => block
|
|
1788
|
+
.replace(/^PLAN_REINFORCE:\s*(needed|none)\s*\r?\n?/gim, "")
|
|
1789
|
+
.trim());
|
|
1790
|
+
}
|
|
1791
|
+
function formatPlanCycleProgress(label, cycle, count, chunks) {
|
|
1792
|
+
const prefix = `[보강 ${cycle}/${count}] ${label}`;
|
|
1793
|
+
return chunks.length > 0
|
|
1794
|
+
? chunks.map((chunk, index) => index === 0 ? `${prefix}\n${chunk}` : chunk)
|
|
1795
|
+
: [prefix];
|
|
1796
|
+
}
|
|
1795
1797
|
function commandTarget(args, rest) {
|
|
1796
1798
|
return (rest?.trim() || args.slice(1).join(" ").trim() || args[0]?.trim() || "");
|
|
1797
1799
|
}
|