appback-remoteagent 0.13.11 → 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 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) {
@@ -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
- const entry = JSON.parse(line);
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 this.truncate(line.replace(/\s+/g, " ").trim(), 220);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appback-remoteagent",
3
- "version": "0.13.11",
3
+ "version": "0.13.12",
4
4
  "description": "Personal installable session server for continuing local AI work across PC and Telegram",
5
5
  "license": "MIT",
6
6
  "type": "module",