agentus 0.4.3 → 0.4.4
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/package.json +1 -1
- package/packages/server/mock/agent.mjs +23 -8
- package/packages/server/src/acp/session-manager.ts +33 -1
- package/packages/server/src/index.ts +49 -1
- package/packages/server/src/notify/center.ts +127 -7
- package/packages/server/src/notify/webpush.ts +295 -0
- package/packages/server/src/store/store.ts +50 -0
- package/packages/shared/src/index.ts +5 -1
- package/packages/web/dist/assets/{index-DzCpHPZV.js → index-DwdrqRgR.js} +56 -56
- package/packages/web/dist/assets/index-xlniZD2Q.css +32 -0
- package/packages/web/dist/index.html +2 -2
- package/packages/web/dist/sw.js +44 -0
- package/packages/web/dist/assets/index-CdQ43EDJ.css +0 -32
|
@@ -924,6 +924,56 @@ export class Store {
|
|
|
924
924
|
return { ...m, seq };
|
|
925
925
|
}
|
|
926
926
|
|
|
927
|
+
/**
|
|
928
|
+
* 一条「状态行」落库(操作者 2026-10-18):回忆了几条记忆、上下文压缩了、有警告。
|
|
929
|
+
*
|
|
930
|
+
* 为什么它不是 `notes` 表而是一行 `messages`(kind `note`):状态行的位置信息就是 seq ——
|
|
931
|
+
* 「在用户那句话之后、在回复之前」。单独一张表要么丢掉这个位置,要么在客户端重建锚点,
|
|
932
|
+
* 而消息流本来就已经按 seq 排队、分页、回放,复用它是这里最省也最不容易错的做法。
|
|
933
|
+
*
|
|
934
|
+
* 为什么必须在这里折叠,而不是留给前端(前端 state.ts 原来是自己折的):压缩是「开始 → 每几秒
|
|
935
|
+
* 一个心跳 → 完成」,provider 等待也是十来秒一条。客户端折只解决"屏幕上的噪音",库里照样会
|
|
936
|
+
* 长出一串心跳。规则只有一条 —— **位置没变就折**:本会话最后一条消息如果是同类的状态行
|
|
937
|
+
* (说明这中间没有别的消息落下),就更新它,而不是再加一行;位置变了(新消息进来了)就另起一行。
|
|
938
|
+
* 于是「压缩中 → 完成」收敛成一行(保留开始时间),一个回合一次回忆也只留一行。
|
|
939
|
+
*/
|
|
940
|
+
appendNote(sessionId: string, note: { category: string; text: string; createdAt?: number }): StoredMessage {
|
|
941
|
+
const createdAt = note.createdAt ?? Date.now();
|
|
942
|
+
const edge = this.#db
|
|
943
|
+
.prepare("select coalesce(max(seq),0) as seq from messages where session_id = ?")
|
|
944
|
+
.get(sessionId) as { seq: number };
|
|
945
|
+
const last = this.#db
|
|
946
|
+
.prepare("select seq, payload, created_at from messages where session_id = ? and kind = 'note' order by seq desc limit 1")
|
|
947
|
+
.get(sessionId) as { seq: number; payload: string; created_at: number } | undefined;
|
|
948
|
+
if (last && last.seq === edge.seq) {
|
|
949
|
+
const prev = JSON.parse(last.payload) as { category?: string; text?: string; hits?: number };
|
|
950
|
+
if (prev.category === note.category) {
|
|
951
|
+
const payload = { category: note.category, text: note.text, hits: (prev.hits ?? 1) + 1 };
|
|
952
|
+
// `created_at` stays: it answers "这一轮什么时候开始回忆/压缩的", and the heartbeat is not news.
|
|
953
|
+
this.#db
|
|
954
|
+
.prepare("update messages set payload = ? where session_id = ? and seq = ?")
|
|
955
|
+
.run(JSON.stringify(payload), sessionId, last.seq);
|
|
956
|
+
return {
|
|
957
|
+
seq: last.seq, sessionId, kind: "note", payload, createdAt: last.created_at,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
const row = this.appendMessage({
|
|
962
|
+
sessionId, kind: "note", payload: { category: note.category, text: note.text, hits: 1 }, createdAt,
|
|
963
|
+
});
|
|
964
|
+
// Bounded: a session left open for weeks must not keep every recall it ever did. 80 is well past
|
|
965
|
+
// what any transcript page shows, and a note is ~120 bytes.
|
|
966
|
+
const over = this.#db
|
|
967
|
+
.prepare("select seq from messages where session_id = ? and kind = 'note' order by seq desc limit -1 offset 80")
|
|
968
|
+
.all(sessionId) as { seq: number }[];
|
|
969
|
+
if (over.length) {
|
|
970
|
+
const del = this.#db.prepare("delete from messages where session_id = ? and seq = ?");
|
|
971
|
+
for (const r of over) del.run(sessionId, r.seq);
|
|
972
|
+
}
|
|
973
|
+
return row;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
|
|
927
977
|
/**
|
|
928
978
|
* The streaming half of the transcript: one logical message, one row.
|
|
929
979
|
*
|
|
@@ -424,7 +424,11 @@ export type ClientCommand =
|
|
|
424
424
|
export interface StoredMessage {
|
|
425
425
|
seq: number; // monotonic per session — reconnect dedup anchor
|
|
426
426
|
sessionId: string;
|
|
427
|
-
|
|
427
|
+
/** `note` = a 「状态行」 that is not a message: 「recalled 8 memories」 / 「compacting」 / a warning.
|
|
428
|
+
* Deliberately a transcript row (not a separate table): its position IS the seq — after the
|
|
429
|
+
* operator's sentence, before the reply — and the page already orders, pages and replays by seq.
|
|
430
|
+
* `meta` stays what it was: a line the HOST says (a rejected send), never persisted. */
|
|
431
|
+
kind: "user" | "agent" | "thought" | "tool" | "plan" | "meta" | "note";
|
|
428
432
|
payload: unknown; // raw ACP content/update fragment (kept lossless)
|
|
429
433
|
toolCallId?: string; // set for kind=tool; upsert key
|
|
430
434
|
createdAt: number;
|