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.
Files changed (2) hide show
  1. package/dist/bot.js +70 -68
  2. 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.loadStopGates();
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
- loadStopGates() {
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.mkdirSync(path.dirname(this.stopGatePath), { recursive: true });
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
- // Stop should still work in memory even if persisting the gate fails.
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} turns max).`);
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: await routeTelegramWorkLoop(bridge, botId, chatId, formatPlanDocumentReinforcementPrompt(count), "Plan document reinforcement", botManagement, helpers, autoContinue, memoryService, (blocks) => blocks, { maxTurns: count }),
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 formatPlanDocumentReinforcementPrompt(count) {
1747
+ function formatPlanDocumentCheckPrompt(cycle, count) {
1778
1748
  return [
1779
- `계획문서를 처음부터 끝까지 확인하고, 최대 ${count}회까지만 보강 루프를 수행하세요.`,
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.15.1",
3
+ "version": "0.15.3",
4
4
  "description": "Personal installable session server for continuing local AI work across PC and Telegram",
5
5
  "license": "MIT",
6
6
  "type": "module",