appback-remoteagent 0.13.10 → 0.13.12
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 +64 -4
- package/dist/services/agent-memory-service.js +94 -5
- package/package.json +1 -1
package/dist/bot.js
CHANGED
|
@@ -886,6 +886,14 @@ ${bridge.formatStatus(mapping)}`);
|
|
|
886
886
|
if (!text) {
|
|
887
887
|
return;
|
|
888
888
|
}
|
|
889
|
+
if (mapping) {
|
|
890
|
+
const localStatus = await memoryService.formatLocalStatusQuestion(mapping.session, text);
|
|
891
|
+
if (localStatus) {
|
|
892
|
+
await bridge.logSystem(botId, chatId, "Answered local session status without provider execution.");
|
|
893
|
+
await reply(ctx, localStatus);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
889
897
|
if (isRemoteShellMessage(text)) {
|
|
890
898
|
const shellRequest = parseRemoteShellRequest(text);
|
|
891
899
|
if (!shellRequest) {
|
|
@@ -1090,8 +1098,9 @@ async function runWithPendingAnimation(botToken, chatId, task) {
|
|
|
1090
1098
|
if (progressChunks.length === 0 && normalized.documents.length === 0) {
|
|
1091
1099
|
return;
|
|
1092
1100
|
}
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1101
|
+
const rendered = formatProviderTelegramChunks(progressChunks, parseMode);
|
|
1102
|
+
const extra = rendered.parseMode ? { parse_mode: rendered.parseMode } : undefined;
|
|
1103
|
+
for (const chunk of rendered.chunks) {
|
|
1095
1104
|
await sendTelegramMessage(botToken, chatId, chunk, extra);
|
|
1096
1105
|
}
|
|
1097
1106
|
if (normalized.documents.length > 0) {
|
|
@@ -1102,12 +1111,13 @@ async function runWithPendingAnimation(botToken, chatId, task) {
|
|
|
1102
1111
|
const result = await task(helpers);
|
|
1103
1112
|
const normalized = await normalizeTelegramDelivery(result.chunks);
|
|
1104
1113
|
const chunks = flattenChunks(normalized.chunks, 3900);
|
|
1105
|
-
const extra = result.parseMode ? { parse_mode: result.parseMode } : undefined;
|
|
1106
1114
|
if (chunks.length === 0 && normalized.documents.length === 0) {
|
|
1107
1115
|
await sendTelegramMessage(botToken, chatId, "Response was empty.");
|
|
1108
1116
|
return;
|
|
1109
1117
|
}
|
|
1110
|
-
|
|
1118
|
+
const rendered = formatProviderTelegramChunks(chunks, result.parseMode);
|
|
1119
|
+
const extra = rendered.parseMode ? { parse_mode: rendered.parseMode } : undefined;
|
|
1120
|
+
for (const chunk of rendered.chunks) {
|
|
1111
1121
|
await sendTelegramMessage(botToken, chatId, chunk, extra);
|
|
1112
1122
|
}
|
|
1113
1123
|
if (normalized.documents.length > 0) {
|
|
@@ -1357,6 +1367,56 @@ function parseReportResponses(formattedBlocks, transform) {
|
|
|
1357
1367
|
const chunks = transform(parsedBlocks.map((item) => item.text));
|
|
1358
1368
|
return { kind, chunks };
|
|
1359
1369
|
}
|
|
1370
|
+
function formatProviderTelegramChunks(chunks, explicitParseMode) {
|
|
1371
|
+
if (explicitParseMode) {
|
|
1372
|
+
return { chunks, parseMode: explicitParseMode };
|
|
1373
|
+
}
|
|
1374
|
+
return {
|
|
1375
|
+
chunks: chunks.map((chunk) => renderTelegramHtml(chunk)),
|
|
1376
|
+
parseMode: "HTML",
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function renderTelegramHtml(text) {
|
|
1380
|
+
const lines = text.split(/\r?\n/);
|
|
1381
|
+
const rendered = [];
|
|
1382
|
+
let codeFence;
|
|
1383
|
+
for (const line of lines) {
|
|
1384
|
+
if (/^```\w*\s*$/.test(line.trim())) {
|
|
1385
|
+
if (codeFence) {
|
|
1386
|
+
rendered.push(`<pre>${escapeTelegramHtml(codeFence.join("\n"))}</pre>`);
|
|
1387
|
+
codeFence = undefined;
|
|
1388
|
+
}
|
|
1389
|
+
else {
|
|
1390
|
+
codeFence = [];
|
|
1391
|
+
}
|
|
1392
|
+
continue;
|
|
1393
|
+
}
|
|
1394
|
+
if (codeFence) {
|
|
1395
|
+
codeFence.push(line);
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
rendered.push(renderTelegramInlineHtml(line));
|
|
1399
|
+
}
|
|
1400
|
+
if (codeFence) {
|
|
1401
|
+
rendered.push(`<pre>${escapeTelegramHtml(codeFence.join("\n"))}</pre>`);
|
|
1402
|
+
}
|
|
1403
|
+
return rendered.join("\n");
|
|
1404
|
+
}
|
|
1405
|
+
function renderTelegramInlineHtml(line) {
|
|
1406
|
+
const parts = line.split(/(`[^`\n]+`)/g);
|
|
1407
|
+
return parts.map((part) => {
|
|
1408
|
+
if (part.startsWith("`") && part.endsWith("`") && part.length >= 2) {
|
|
1409
|
+
return `<code>${escapeTelegramHtml(part.slice(1, -1))}</code>`;
|
|
1410
|
+
}
|
|
1411
|
+
return escapeTelegramHtml(part);
|
|
1412
|
+
}).join("");
|
|
1413
|
+
}
|
|
1414
|
+
function escapeTelegramHtml(value) {
|
|
1415
|
+
return value
|
|
1416
|
+
.replace(/&/g, "&")
|
|
1417
|
+
.replace(/</g, "<")
|
|
1418
|
+
.replace(/>/g, ">");
|
|
1419
|
+
}
|
|
1360
1420
|
function looksLikeUntaggedIntentOnlyResponse(text) {
|
|
1361
1421
|
const normalized = text.trim();
|
|
1362
1422
|
if (!normalized) {
|
|
@@ -71,6 +71,53 @@ export class AgentMemoryService {
|
|
|
71
71
|
history.length > 0 ? ["Recent history:", ...history.map((entry) => `- ${entry}`)].join("\n") : undefined,
|
|
72
72
|
].filter(Boolean).join("\n");
|
|
73
73
|
}
|
|
74
|
+
async formatLocalStatusQuestion(session, question) {
|
|
75
|
+
if (!this.looksLikeLocalStatusQuestion(question)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const current = await this.currentTaskText(session);
|
|
79
|
+
const todo = await this.readTodo(session);
|
|
80
|
+
const active = this.activeTodoItems(todo);
|
|
81
|
+
const history = await this.readRecentHistory(session, 8);
|
|
82
|
+
const workHistory = await this.readRecentWorkHistory(session, 6);
|
|
83
|
+
const latestInstruction = this.extractLatestInstructionFromCurrent(current);
|
|
84
|
+
const currentIsStatusOnly = latestInstruction ? this.looksLikeLocalStatusQuestion(latestInstruction) : false;
|
|
85
|
+
const hasCurrentWork = Boolean(current.trim()) && !currentIsStatusOnly;
|
|
86
|
+
const lines = [
|
|
87
|
+
`Session ${session.publicId} local state`,
|
|
88
|
+
"",
|
|
89
|
+
`workspace: ${session.workspace}`,
|
|
90
|
+
`memory: ${this.sessionDir(session)}`,
|
|
91
|
+
"",
|
|
92
|
+
];
|
|
93
|
+
if (!hasCurrentWork && active.length === 0 && workHistory.length === 0) {
|
|
94
|
+
lines.push("현재 하네스 기준으로 진행 중이거나 완료된 작업 기록이 없습니다.", "이 세션은 깨끗한 상태로 보입니다.");
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
if (hasCurrentWork) {
|
|
98
|
+
lines.push("Current note:", this.summarizeCurrentTask(current), "");
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
lines.push("Current note: none", "");
|
|
102
|
+
}
|
|
103
|
+
if (active.length > 0) {
|
|
104
|
+
lines.push("Active TODO:", this.formatTodoSummary(todo, false), "");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
lines.push("Active TODO: none", "");
|
|
108
|
+
}
|
|
109
|
+
if (workHistory.length > 0) {
|
|
110
|
+
lines.push("Recent work history:", ...workHistory.map((entry) => `- ${entry}`));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
lines.push("Recent work history: none");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (history.length > 0) {
|
|
117
|
+
lines.push("", "Recent raw history:", ...history.map((entry) => `- ${entry}`));
|
|
118
|
+
}
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
74
121
|
async clearSessionState(session, summary) {
|
|
75
122
|
const dir = this.sessionDir(session);
|
|
76
123
|
const currentPath = path.join(dir, "current.md");
|
|
@@ -360,6 +407,17 @@ export class AgentMemoryService {
|
|
|
360
407
|
return fs.readFile(path.join(this.sessionDir(session), "current.md"), "utf8").catch(() => "");
|
|
361
408
|
}
|
|
362
409
|
async readRecentHistory(session, limit) {
|
|
410
|
+
return (await this.readHistoryEntries(session, limit))
|
|
411
|
+
.slice(-limit)
|
|
412
|
+
.map((entry) => this.formatHistoryEntry(entry));
|
|
413
|
+
}
|
|
414
|
+
async readRecentWorkHistory(session, limit) {
|
|
415
|
+
return (await this.readHistoryEntries(session, 80))
|
|
416
|
+
.filter((entry) => ["progress", "completed", "archived", "note"].includes(String(entry.type ?? "")))
|
|
417
|
+
.slice(-limit)
|
|
418
|
+
.map((entry) => this.formatHistoryEntry(entry));
|
|
419
|
+
}
|
|
420
|
+
async readHistoryEntries(session, limit) {
|
|
363
421
|
const historyPath = path.join(this.sessionDir(session), "history.ndjson");
|
|
364
422
|
const raw = await fs.readFile(historyPath, "utf8").catch(() => "");
|
|
365
423
|
return raw
|
|
@@ -369,16 +427,24 @@ export class AgentMemoryService {
|
|
|
369
427
|
.slice(-limit)
|
|
370
428
|
.map((line) => {
|
|
371
429
|
try {
|
|
372
|
-
|
|
373
|
-
const kind = entry.mode ? `${entry.type ?? "event"}:${entry.mode}` : entry.type ?? "event";
|
|
374
|
-
const body = entry.summary ?? entry.text ?? entry.reason ?? "";
|
|
375
|
-
return `${entry.at ?? "unknown"} ${kind}: ${this.truncate(body.replace(/\s+/g, " ").trim(), 220)}`;
|
|
430
|
+
return JSON.parse(line);
|
|
376
431
|
}
|
|
377
432
|
catch {
|
|
378
|
-
return
|
|
433
|
+
return { type: "raw", text: line };
|
|
379
434
|
}
|
|
380
435
|
});
|
|
381
436
|
}
|
|
437
|
+
formatHistoryEntry(entry) {
|
|
438
|
+
const type = typeof entry.type === "string" ? entry.type : "event";
|
|
439
|
+
const mode = typeof entry.mode === "string" ? entry.mode : undefined;
|
|
440
|
+
const at = typeof entry.at === "string" ? entry.at : "unknown";
|
|
441
|
+
const text = typeof entry.text === "string" ? entry.text : "";
|
|
442
|
+
const summary = typeof entry.summary === "string" ? entry.summary : "";
|
|
443
|
+
const reason = typeof entry.reason === "string" ? entry.reason : "";
|
|
444
|
+
const kind = mode ? `${type}:${mode}` : type;
|
|
445
|
+
const body = summary || text || reason || "";
|
|
446
|
+
return `${at} ${kind}: ${this.truncate(body.replace(/\s+/g, " ").trim(), 220)}`;
|
|
447
|
+
}
|
|
382
448
|
todoPath(session) {
|
|
383
449
|
return path.join(this.sessionDir(session), "todo.json");
|
|
384
450
|
}
|
|
@@ -576,12 +642,35 @@ export class AgentMemoryService {
|
|
|
576
642
|
return /^(새로|새\s*작업|다른\s*작업|이제\s*부터|다음\s*작업|전환|바꿔서|new task)/i.test(text.trim());
|
|
577
643
|
}
|
|
578
644
|
summarizeCurrentTask(current) {
|
|
645
|
+
const latestInstruction = this.extractLatestInstructionFromCurrent(current);
|
|
646
|
+
if (latestInstruction) {
|
|
647
|
+
return this.truncate(latestInstruction, 700);
|
|
648
|
+
}
|
|
579
649
|
const instructionIndex = current.indexOf("## Instruction");
|
|
580
650
|
const body = instructionIndex >= 0 ? current.slice(instructionIndex + "## Instruction".length) : current;
|
|
581
651
|
const immediateRuleIndex = body.indexOf("## Immediate Rule");
|
|
582
652
|
const instruction = (immediateRuleIndex >= 0 ? body.slice(0, immediateRuleIndex) : body).trim();
|
|
583
653
|
return this.truncate(instruction || current.trim(), 700);
|
|
584
654
|
}
|
|
655
|
+
extractLatestInstructionFromCurrent(current) {
|
|
656
|
+
const match = /## Latest User Instruction\s*\n([\s\S]*?)(?:\n## |\s*$)/.exec(current);
|
|
657
|
+
return match?.[1]?.trim() || undefined;
|
|
658
|
+
}
|
|
659
|
+
looksLikeLocalStatusQuestion(text) {
|
|
660
|
+
const normalized = text.trim();
|
|
661
|
+
if (!normalized) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
return [
|
|
665
|
+
/최근\s*(작업|진행|히스토리|기록)/,
|
|
666
|
+
/현재\s*(작업|진행|상태|세션)/,
|
|
667
|
+
/(뭐|무엇)\s*(하고|하는)\s*(있어|중|거야)?/,
|
|
668
|
+
/작업\s*(상태|히스토리|기록|있어|없어)/,
|
|
669
|
+
/세션\s*(상태|히스토리|기록)/,
|
|
670
|
+
/진행\s*(상태|중인\s*작업)/,
|
|
671
|
+
/\b(status|current work|recent work|what are you doing|session state)\b/i,
|
|
672
|
+
].some((pattern) => pattern.test(normalized));
|
|
673
|
+
}
|
|
585
674
|
formatTodoSummary(todo, includeDone = false, detailed = false) {
|
|
586
675
|
const items = includeDone ? todo.items : todo.items.filter((item) => item.status !== "done");
|
|
587
676
|
if (items.length === 0) {
|