agentus 0.7.0 → 0.8.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.
@@ -3,7 +3,7 @@
3
3
  // messages carry monotonic per-session `seq` (reconnect replay anchor, AC6).
4
4
  import fs from "node:fs";
5
5
  import { DatabaseSync } from "node:sqlite";
6
- import type { BackendId, PlanSnapshot, SessionStatus, StoredMessage } from "../../../shared/src/index.js";
6
+ import type { BackendId, PlanSnapshot, SessionStatus, StatusNoteItem, StoredMessage } from "../../../shared/src/index.js";
7
7
  import type { BackendHandshake, BackendHealth, BackendKind, BackendRow } from "../acp/registry.js";
8
8
  import { normalizeItems } from "../plan/plan.js";
9
9
  import { DEFAULT_NATIVE_PLAN_SOURCE } from "../acp/backends.js";
@@ -965,7 +965,11 @@ export class Store {
965
965
  * (说明这中间没有别的消息落下),就更新它,而不是再加一行;位置变了(新消息进来了)就另起一行。
966
966
  * 于是「压缩中 → 完成」收敛成一行(保留开始时间),一个回合一次回忆也只留一行。
967
967
  */
968
- appendNote(sessionId: string, note: { category: string; text: string; createdAt?: number }): StoredMessage {
968
+ appendNote(sessionId: string, note: {
969
+ category: string; text: string; createdAt?: number;
970
+ /** memory 行的条目(issue #37)—— 只在 category==="memory" 时有意义。 */
971
+ items?: StatusNoteItem[]; truncated?: boolean;
972
+ }): StoredMessage {
969
973
  const createdAt = note.createdAt ?? Date.now();
970
974
  const edge = this.#db
971
975
  .prepare("select coalesce(max(seq),0) as seq from messages where session_id = ?")
@@ -974,9 +978,18 @@ export class Store {
974
978
  .prepare("select seq, payload, created_at from messages where session_id = ? and kind = 'note' order by seq desc limit 1")
975
979
  .get(sessionId) as { seq: number; payload: string; created_at: number } | undefined;
976
980
  if (last && last.seq === edge.seq) {
977
- const prev = JSON.parse(last.payload) as { category?: string; text?: string; hits?: number };
981
+ const prev = JSON.parse(last.payload) as {
982
+ category?: string; text?: string; hits?: number; items?: StatusNoteItem[]; truncated?: boolean;
983
+ };
978
984
  if (prev.category === note.category) {
979
- const payload = { category: note.category, text: note.text, hits: (prev.hits ?? 1) + 1 };
985
+ // 折叠只发生在「位置上没有别的消息插进来」时(压缩的开始/心跳/完成)。条目取**最新一次**:
986
+ // 折叠出来的是同一行,这行的内容是最后一次召回,而不是第一次的残留。
987
+ const payload: Record<string, unknown> = {
988
+ category: note.category, text: note.text, hits: (prev.hits ?? 1) + 1,
989
+ };
990
+ const items = note.items ?? prev.items;
991
+ if (items?.length) payload.items = items;
992
+ if (note.truncated ?? prev.truncated) payload.truncated = true;
980
993
  // `created_at` stays: it answers "这一轮什么时候开始回忆/压缩的", and the heartbeat is not news.
981
994
  this.#db
982
995
  .prepare("update messages set payload = ? where session_id = ? and seq = ?")
@@ -986,9 +999,10 @@ export class Store {
986
999
  };
987
1000
  }
988
1001
  }
989
- const row = this.appendMessage({
990
- sessionId, kind: "note", payload: { category: note.category, text: note.text, hits: 1 }, createdAt,
991
- });
1002
+ const payload: Record<string, unknown> = { category: note.category, text: note.text, hits: 1 };
1003
+ if (note.items?.length) payload.items = note.items;
1004
+ if (note.truncated) payload.truncated = true;
1005
+ const row = this.appendMessage({ sessionId, kind: "note", payload, createdAt });
992
1006
  // Bounded: a session left open for weeks must not keep every recall it ever did. 80 is well past
993
1007
  // what any transcript page shows, and a note is ~120 bytes.
994
1008
  const over = this.#db
@@ -263,6 +263,16 @@ export interface BackgroundTask {
263
263
  status: "running" | "exited";
264
264
  uptimeSeconds?: number;
265
265
  exitCode?: number | null;
266
+ /** What this row IS (issue #39). `process` = a `terminal(background=true)` child in the process
267
+ * registry; `delegation` = a live `delegate_task(background=true)` subagent, which never appears
268
+ * there. Absent on frames from an older agent, and absent must read as `process`: that is what
269
+ * every field above has always meant. */
270
+ kind?: "process" | "delegation";
271
+ /** Delegations only: the ledger's own phase (`running` / `stalling` / `finalizing`). `status`
272
+ * stays the host's two-state vocabulary, so a `finalizing` child still reads as running. */
273
+ phase?: string;
274
+ /** Delegations only: how many children one row stands for (a batch of N counts N). */
275
+ taskCount?: number;
266
276
  /** Does this process OWE the session a wake-up (`notify_on_complete`)? Issue #35: that is the
267
277
  * difference between 「agent 已经收工」 and 「agent 还在等这次唤醒才能继续」. Optional — an
268
278
  * older agent never sends it, and `undefined` must read as "silent", never as "unknown so
@@ -434,6 +444,18 @@ export type ClientCommand =
434
444
  signature?: string; // "<kind>:<toolCallTitle>" for always-allow memory
435
445
  };
436
446
 
447
+ /** 一条记忆召回条目 —— `note` 行 `payload.items` 的元素(issue #37)。
448
+ *
449
+ * 「recalled 8 memories」只说记忆被用上了,没说用上的是哪几条,于是「召回错了/召回旧了」和
450
+ * 「召回得好」在屏幕上长得一模一样。条目由 provider 侧截断(200 字符/条、6K/轮),server 再按
451
+ * 自己的上限(20 条 / 300 字符)限一次,因为这条数据来自 agent 进程并且要落库。
452
+ */
453
+ export interface StatusNoteItem {
454
+ text: string;
455
+ tags?: string[];
456
+ type?: string;
457
+ }
458
+
437
459
  export interface StoredMessage {
438
460
  seq: number; // monotonic per session — reconnect dedup anchor
439
461
  sessionId: string;