@thread-memory/core 0.1.0

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 (76) hide show
  1. package/dist/events.d.ts +18 -0
  2. package/dist/events.js +17 -0
  3. package/dist/events.js.map +1 -0
  4. package/dist/events.test.d.ts +1 -0
  5. package/dist/events.test.js +46 -0
  6. package/dist/events.test.js.map +1 -0
  7. package/dist/feedback-guard.d.ts +3 -0
  8. package/dist/feedback-guard.js +33 -0
  9. package/dist/feedback-guard.js.map +1 -0
  10. package/dist/feedback-guard.test.d.ts +1 -0
  11. package/dist/feedback-guard.test.js +67 -0
  12. package/dist/feedback-guard.test.js.map +1 -0
  13. package/dist/governor.d.ts +16 -0
  14. package/dist/governor.js +25 -0
  15. package/dist/governor.js.map +1 -0
  16. package/dist/index.d.ts +12 -0
  17. package/dist/index.js +13 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/index.test.d.ts +1 -0
  20. package/dist/index.test.js +8 -0
  21. package/dist/index.test.js.map +1 -0
  22. package/dist/isolation.test.d.ts +1 -0
  23. package/dist/isolation.test.js +102 -0
  24. package/dist/isolation.test.js.map +1 -0
  25. package/dist/light-confirm.d.ts +37 -0
  26. package/dist/light-confirm.js +149 -0
  27. package/dist/light-confirm.js.map +1 -0
  28. package/dist/light-confirm.test.d.ts +1 -0
  29. package/dist/light-confirm.test.js +154 -0
  30. package/dist/light-confirm.test.js.map +1 -0
  31. package/dist/lineage.test.d.ts +1 -0
  32. package/dist/lineage.test.js +58 -0
  33. package/dist/lineage.test.js.map +1 -0
  34. package/dist/migrate.d.ts +45 -0
  35. package/dist/migrate.js +349 -0
  36. package/dist/migrate.js.map +1 -0
  37. package/dist/migrate.test.d.ts +1 -0
  38. package/dist/migrate.test.js +111 -0
  39. package/dist/migrate.test.js.map +1 -0
  40. package/dist/paths.d.ts +7 -0
  41. package/dist/paths.js +16 -0
  42. package/dist/paths.js.map +1 -0
  43. package/dist/project-key.d.ts +3 -0
  44. package/dist/project-key.js +48 -0
  45. package/dist/project-key.js.map +1 -0
  46. package/dist/project-key.test.d.ts +1 -0
  47. package/dist/project-key.test.js +29 -0
  48. package/dist/project-key.test.js.map +1 -0
  49. package/dist/query.d.ts +49 -0
  50. package/dist/query.js +165 -0
  51. package/dist/query.js.map +1 -0
  52. package/dist/query.test.d.ts +1 -0
  53. package/dist/query.test.js +162 -0
  54. package/dist/query.test.js.map +1 -0
  55. package/dist/schema.d.ts +4 -0
  56. package/dist/schema.js +92 -0
  57. package/dist/schema.js.map +1 -0
  58. package/dist/schema.test.d.ts +1 -0
  59. package/dist/schema.test.js +80 -0
  60. package/dist/schema.test.js.map +1 -0
  61. package/dist/state.d.ts +4 -0
  62. package/dist/state.js +15 -0
  63. package/dist/state.js.map +1 -0
  64. package/dist/status-card.d.ts +10 -0
  65. package/dist/status-card.js +65 -0
  66. package/dist/status-card.js.map +1 -0
  67. package/dist/status-card.test.d.ts +1 -0
  68. package/dist/status-card.test.js +53 -0
  69. package/dist/status-card.test.js.map +1 -0
  70. package/dist/store.d.ts +170 -0
  71. package/dist/store.js +664 -0
  72. package/dist/store.js.map +1 -0
  73. package/dist/store.test.d.ts +1 -0
  74. package/dist/store.test.js +284 -0
  75. package/dist/store.test.js.map +1 -0
  76. package/package.json +29 -0
@@ -0,0 +1,18 @@
1
+ import type Database from "better-sqlite3";
2
+ export declare const MAX_BODY_CHARS = 100000;
3
+ export type EventKind = "user_message" | "assistant_message" | "tool_call" | "tool_result" | "session_start" | "session_end" | "compact_checkpoint";
4
+ export interface SessionEvent {
5
+ id: number;
6
+ session_id: string;
7
+ kind: EventKind;
8
+ ts: string;
9
+ seq: number;
10
+ body: string;
11
+ meta?: Record<string, unknown>;
12
+ truncated: boolean;
13
+ }
14
+ export declare function truncateBody(body: string, maxChars?: number): {
15
+ body: string;
16
+ truncated: boolean;
17
+ };
18
+ export declare function eventKindCounts(db: Database.Database): Partial<Record<EventKind, number>>;
package/dist/events.js ADDED
@@ -0,0 +1,17 @@
1
+ export const MAX_BODY_CHARS = 100_000;
2
+ export function truncateBody(body, maxChars = MAX_BODY_CHARS) {
3
+ if (body.length <= maxChars) {
4
+ return { body, truncated: false };
5
+ }
6
+ const kept = body.slice(0, maxChars);
7
+ return { body: `${kept}\n... [truncated ${body.length - maxChars} chars]`, truncated: true };
8
+ }
9
+ export function eventKindCounts(db) {
10
+ const counts = {};
11
+ const rows = db.prepare(`SELECT kind, COUNT(*) AS c FROM events GROUP BY kind`).all();
12
+ for (const row of rows) {
13
+ counts[row.kind] = row.c;
14
+ }
15
+ return counts;
16
+ }
17
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAsBtC,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,WAAmB,cAAc;IAC1E,IAAI,IAAI,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACpC,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IACrC,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,oBAAoB,IAAI,CAAC,MAAM,GAAG,QAAQ,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,EAAqB;IACnD,MAAM,MAAM,GAAuC,EAAE,CAAC;IACtD,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,sDAAsD,CAAC,CAAC,GAAG,EAGjF,CAAC;IACH,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import Database from "better-sqlite3";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { eventKindCounts } from "./events.js";
7
+ function openEventsDb(prefix) {
8
+ const dir = mkdtempSync(join(tmpdir(), prefix));
9
+ const db = new Database(join(dir, "events.db"));
10
+ db.exec(`CREATE TABLE events (
11
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
12
+ session_id TEXT NOT NULL,
13
+ kind TEXT NOT NULL,
14
+ ts TEXT NOT NULL,
15
+ seq INTEGER NOT NULL,
16
+ body TEXT NOT NULL,
17
+ meta TEXT,
18
+ truncated INTEGER NOT NULL DEFAULT 0
19
+ );`);
20
+ return { db, dir };
21
+ }
22
+ describe("eventKindCounts", () => {
23
+ it("counts events grouped by kind", () => {
24
+ const { db, dir } = openEventsDb("thread-events-");
25
+ const insert = db.prepare(`INSERT INTO events (session_id, kind, ts, seq, body) VALUES (?, ?, ?, ?, ?)`);
26
+ const rows = [
27
+ ["s1", "user_message", "2026-08-13T00:00:00.000Z", 1, "hello"],
28
+ ["s1", "tool_call", "2026-08-13T00:00:01.000Z", 2, "edit a.ts"],
29
+ ["s1", "tool_result", "2026-08-13T00:00:02.000Z", 3, "ok"],
30
+ ["s2", "user_message", "2026-08-13T00:00:03.000Z", 1, "hi"],
31
+ ["s2", "user_message", "2026-08-13T00:00:04.000Z", 2, "again"],
32
+ ];
33
+ for (const row of rows)
34
+ insert.run(...row);
35
+ expect(eventKindCounts(db)).toEqual({ user_message: 3, tool_call: 1, tool_result: 1 });
36
+ db.close();
37
+ rmSync(dir, { recursive: true, force: true });
38
+ });
39
+ it("returns an empty object for an empty table", () => {
40
+ const { db, dir } = openEventsDb("thread-events-empty-");
41
+ expect(eventKindCounts(db)).toEqual({});
42
+ db.close();
43
+ rmSync(dir, { recursive: true, force: true });
44
+ });
45
+ });
46
+ //# sourceMappingURL=events.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.test.js","sourceRoot":"","sources":["../src/events.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,SAAS,YAAY,CAAC,MAAc;IAClC,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,MAAM,EAAE,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;IAChD,EAAE,CAAC,IAAI,CAAC;;;;;;;;;KASL,CAAC,CAAC;IACL,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC;AACrB,CAAC;AAED,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACvC,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;QACnD,MAAM,MAAM,GAAG,EAAE,CAAC,OAAO,CACvB,6EAA6E,CAC9E,CAAC;QACF,MAAM,IAAI,GAAoD;YAC5D,CAAC,IAAI,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,EAAE,OAAO,CAAC;YAC9D,CAAC,IAAI,EAAE,WAAW,EAAE,0BAA0B,EAAE,CAAC,EAAE,WAAW,CAAC;YAC/D,CAAC,IAAI,EAAE,aAAa,EAAE,0BAA0B,EAAE,CAAC,EAAE,IAAI,CAAC;YAC1D,CAAC,IAAI,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,EAAE,IAAI,CAAC;YAC3D,CAAC,IAAI,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,EAAE,OAAO,CAAC;SAC/D,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QAE3C,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC,CAAC;QACvF,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,YAAY,CAAC,sBAAsB,CAAC,CAAC;QAEzD,MAAM,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACxC,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FeedbackRow } from "./store.js";
2
+ export declare function extractBlockedTokens(text: string): string[];
3
+ export declare function matchToolFeedback(rows: readonly FeedbackRow[], toolName: string): FeedbackRow | undefined;
@@ -0,0 +1,33 @@
1
+ // B⑥-② 反馈拦截:确定性提取"禁用指令"里的目标工具名,与待执行工具名匹配。
2
+ // 零 LLM、同步——误报可接受(阻断成本低,拒绝原因即教训原文,可见可纠偏)。
3
+ const BLOCK_PATTERNS = [
4
+ /不要(?:再|直接|再直接)?(?:用|使用|调用)?\s*[`'"]?([A-Za-z_][\w-]{2,}|[\u4e00-\u9fa5]{2,8})[`'"]?/g,
5
+ /(?:别|禁止|禁用|勿)(?:再|直接|再直接)?(?:用|使用|调用)?\s*[`'"]?([A-Za-z_][\w-]{2,}|[\u4e00-\u9fa5]{2,8})[`'"]?/g,
6
+ /never\s+(?:use|call)\s+[`'"]?([A-Za-z_][\w-]{2,})[`'"]?/gi,
7
+ /don'?t\s+(?:use|call)\s+[`'"]?([A-Za-z_][\w-]{2,})[`'"]?/gi,
8
+ /avoid\s+(?:using|calling)?\s*[`'"]?([A-Za-z_][\w-]{2,})[`'"]?/gi,
9
+ ];
10
+ export function extractBlockedTokens(text) {
11
+ const tokens = new Set();
12
+ for (const pattern of BLOCK_PATTERNS) {
13
+ for (const m of text.matchAll(pattern)) {
14
+ const token = m[1];
15
+ if (token) {
16
+ tokens.add(token.toLowerCase());
17
+ }
18
+ }
19
+ }
20
+ return [...tokens];
21
+ }
22
+ export function matchToolFeedback(rows, toolName) {
23
+ const name = toolName.toLowerCase();
24
+ for (const row of rows) {
25
+ for (const token of extractBlockedTokens(row.text)) {
26
+ if (token === name || (token.length >= 3 && name.includes(token))) {
27
+ return row;
28
+ }
29
+ }
30
+ }
31
+ return undefined;
32
+ }
33
+ //# sourceMappingURL=feedback-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback-guard.js","sourceRoot":"","sources":["../src/feedback-guard.ts"],"names":[],"mappings":"AAEA,0CAA0C;AAC1C,0CAA0C;AAC1C,MAAM,cAAc,GAAa;IAC/B,sFAAsF;IACtF,iGAAiG;IACjG,2DAA2D;IAC3D,4DAA4D;IAC5D,iEAAiE;CAClE,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,IAA4B,EAC5B,QAAgB;IAEhB,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACpC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,KAAK,MAAM,KAAK,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBAClE,OAAO,GAAG,CAAC;YACb,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { extractBlockedTokens, matchToolFeedback } from "./feedback-guard.js";
6
+ import { ThreadStore } from "./store.js";
7
+ const row = (text, kind = "correction") => ({ id: 1, session_id: "s1", text, kind, created_at: "2026-08-15T00:00:00Z" });
8
+ describe("extractBlockedTokens(B⑥-② 禁用指令 token 提取)", () => {
9
+ it("提取中文指令后的工具名", () => {
10
+ expect(extractBlockedTokens("不要用 pwsh 直接改文件")).toContain("pwsh");
11
+ expect(extractBlockedTokens("别再用 Read 读大文件")).toContain("read");
12
+ expect(extractBlockedTokens("禁止调用 query_session_memory 做验证")).toContain("query_session_memory");
13
+ });
14
+ it("提取英文指令后的工具名", () => {
15
+ expect(extractBlockedTokens("never use bash")).toContain("bash");
16
+ expect(extractBlockedTokens("don't call git_commit")).toContain("git_commit");
17
+ expect(extractBlockedTokens("avoid using pwsh")).toContain("pwsh");
18
+ });
19
+ it("无禁用指令时返回空", () => {
20
+ expect(extractBlockedTokens("我记下了用 JWT 做认证")).toHaveLength(0);
21
+ expect(extractBlockedTokens("项目决策:状态卡预算 200 行")).toHaveLength(0);
22
+ });
23
+ });
24
+ describe("matchToolFeedback(工具名匹配)", () => {
25
+ it("命中:token 与工具名相等(大小写不敏感)", () => {
26
+ const hit = matchToolFeedback([row("不要用 pwsh 改配置")], "pwsh");
27
+ expect(hit?.text).toContain("pwsh");
28
+ });
29
+ it("命中:工具名包含 token(如 edit → edit_file)", () => {
30
+ const hit = matchToolFeedback([row("不要用 edit 直接改")], "edit_file");
31
+ expect(hit?.text).toContain("edit");
32
+ });
33
+ it("不命中:无关教训", () => {
34
+ expect(matchToolFeedback([row("不要用 pwsh 改配置")], "read")).toBeUndefined();
35
+ });
36
+ it("不命中:短 token 不参与包含匹配(噪声防护)", () => {
37
+ // "别问了" 提取中文 token,不影响任何英文工具名
38
+ expect(matchToolFeedback([row("以后别问了")], "write")).toBeUndefined();
39
+ });
40
+ it("命中后返回第一条匹配教训原文", () => {
41
+ const rows = [row("第一条无关"), row("不要用 grep"), row("也不要再用 grep")];
42
+ expect(matchToolFeedback(rows, "grep")?.text).toBe("不要用 grep");
43
+ });
44
+ });
45
+ describe("deleteFeedback(B⑥-② 恢复通道:教训可删即恢复)", () => {
46
+ let dir;
47
+ let store;
48
+ beforeAll(() => {
49
+ dir = mkdtempSync(join(tmpdir(), "thread-guard-"));
50
+ store = new ThreadStore({ eventsPath: join(dir, "events.db"), structuredPath: join(dir, "structured.db"), projectKey: "guard-proj" });
51
+ });
52
+ afterAll(() => {
53
+ store.close();
54
+ rmSync(dir, { recursive: true, force: true });
55
+ });
56
+ it("删除后守卫不再命中(恢复通道闭环)", () => {
57
+ const row = store.addFeedback("s1", "不要用 pwsh 改配置", "correction");
58
+ const before = matchToolFeedback(store.getFeedbackMerged("s1", "guard-proj", 50), "pwsh");
59
+ expect(before?.id).toBe(row.id);
60
+ expect(store.deleteFeedback(row.id)).toBe(true);
61
+ expect(matchToolFeedback(store.getFeedbackMerged("s1", "guard-proj", 50), "pwsh")).toBeUndefined();
62
+ });
63
+ it("不存在的 id 返回 false", () => {
64
+ expect(store.deleteFeedback(999999)).toBe(false);
65
+ });
66
+ });
67
+ //# sourceMappingURL=feedback-guard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback-guard.test.js","sourceRoot":"","sources":["../src/feedback-guard.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { EventKind } from "./events.js";
2
+ export interface SpillDecision {
3
+ spill: boolean;
4
+ kept: string;
5
+ ref?: string;
6
+ }
7
+ export declare class SpillPolicy {
8
+ private threshold;
9
+ private keptChars;
10
+ constructor(threshold?: number, keptChars?: number);
11
+ evaluate(body: string, opts?: {
12
+ ref?: string;
13
+ }): SpillDecision;
14
+ }
15
+ export declare const INDEXABLE_KINDS: ReadonlySet<EventKind>;
16
+ export declare function isIndexable(kind: EventKind): boolean;
@@ -0,0 +1,25 @@
1
+ // 大正文 spill:正文 > 阈值 → 保留前 keptChars 字符摘要 + ref 引用;原文进 spills 表或指向底座日志
2
+ export class SpillPolicy {
3
+ threshold;
4
+ keptChars;
5
+ constructor(threshold = 4096, keptChars = 400) {
6
+ this.threshold = threshold;
7
+ this.keptChars = keptChars;
8
+ }
9
+ evaluate(body, opts = {}) {
10
+ if (body.length <= this.threshold) {
11
+ return { spill: false, kept: body };
12
+ }
13
+ return { spill: true, kept: body.slice(0, this.keptChars), ref: opts.ref };
14
+ }
15
+ }
16
+ // FTS 分层:只索引轻量文本(indexable),tool_call/tool_result 大块不建全文索引,检索按引用回拉
17
+ export const INDEXABLE_KINDS = new Set([
18
+ "user_message",
19
+ "assistant_message",
20
+ "compact_checkpoint",
21
+ ]);
22
+ export function isIndexable(kind) {
23
+ return INDEXABLE_KINDS.has(kind);
24
+ }
25
+ //# sourceMappingURL=governor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"governor.js","sourceRoot":"","sources":["../src/governor.ts"],"names":[],"mappings":"AAQA,sEAAsE;AACtE,MAAM,OAAO,WAAW;IAEZ;IACA;IAFV,YACU,YAAY,IAAI,EAChB,YAAY,GAAG;QADf,cAAS,GAAT,SAAS,CAAO;QAChB,cAAS,GAAT,SAAS,CAAM;IACtB,CAAC;IAEJ,QAAQ,CAAC,IAAY,EAAE,OAAyB,EAAE;QAChD,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAClC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACtC,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7E,CAAC;CACF;AAED,mEAAmE;AACnE,MAAM,CAAC,MAAM,eAAe,GAA2B,IAAI,GAAG,CAAY;IACxE,cAAc;IACd,mBAAmB;IACnB,oBAAoB;CACrB,CAAC,CAAC;AAEH,MAAM,UAAU,WAAW,CAAC,IAAe;IACzC,OAAO,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const THREAD_VERSION = "0.0.0";
2
+ export * from "./events.js";
3
+ export * from "./state.js";
4
+ export * from "./store.js";
5
+ export * from "./light-confirm.js";
6
+ export * from "./query.js";
7
+ export * from "./governor.js";
8
+ export * from "./project-key.js";
9
+ export * from "./migrate.js";
10
+ export * from "./paths.js";
11
+ export * from "./status-card.js";
12
+ export * from "./feedback-guard.js";
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ export const THREAD_VERSION = "0.0.0";
2
+ export * from "./events.js";
3
+ export * from "./state.js";
4
+ export * from "./store.js";
5
+ export * from "./light-confirm.js";
6
+ export * from "./query.js";
7
+ export * from "./governor.js";
8
+ export * from "./project-key.js";
9
+ export * from "./migrate.js";
10
+ export * from "./paths.js";
11
+ export * from "./status-card.js";
12
+ export * from "./feedback-guard.js";
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { THREAD_VERSION } from "./index.js";
3
+ describe("core", () => {
4
+ it("exposes a version", () => {
5
+ expect(THREAD_VERSION).toBe("0.0.0");
6
+ });
7
+ });
8
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE;IACpB,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { buildStatusCard } from "./status-card.js";
6
+ import { queryEvents } from "./query.js";
7
+ import { ThreadStore } from "./store.js";
8
+ let dir;
9
+ let store;
10
+ beforeAll(() => {
11
+ dir = mkdtempSync(join(tmpdir(), "thread-isolation-"));
12
+ store = new ThreadStore({
13
+ eventsPath: join(dir, "events.db"),
14
+ structuredPath: join(dir, "structured.db"),
15
+ projectKey: "proj-iso",
16
+ });
17
+ });
18
+ afterAll(() => {
19
+ store.close();
20
+ rmSync(dir, { recursive: true, force: true });
21
+ });
22
+ describe("会话隔离(B⑧)", () => {
23
+ it("写入:对话上下文打隔离标记,tool 事件强制共享", () => {
24
+ const msg = store.append({ session_id: "iso-s1", kind: "user_message", ts: "2026-08-15T00:00:00.000Z", body: "隔离会话消息" }, { isolation: true });
25
+ expect(msg.isolation).toBe(1);
26
+ const tool = store.append({ session_id: "iso-s1", kind: "tool_call", ts: "2026-08-15T00:00:01.000Z", body: "改文件", meta: { tool_name: "Write", file_path: "a.ts" } }, { isolation: true });
27
+ expect(tool.isolation).toBe(0);
28
+ });
29
+ it("结构化写入带隔离标记(决策/反馈),跨会话合并视图过滤", () => {
30
+ store.addFeedback("iso-s1", "隔离会话偏好:用 pnpm", "preference", {
31
+ scope: "project",
32
+ projectKey: "proj-iso",
33
+ isolation: true,
34
+ ts: "2026-08-15T00:00:02.000Z",
35
+ });
36
+ store.proposeDecision("iso-s1", "隔离决策:改用 yarn", {
37
+ scope: "project",
38
+ projectKey: "proj-iso",
39
+ isolation: true,
40
+ ts: "2026-08-15T00:00:03.000Z",
41
+ });
42
+ store.confirmLatestProposed("iso-s1", { ts: "2026-08-15T00:00:04.000Z" });
43
+ // 本会话自己可见
44
+ expect(store.getActiveDecisions("iso-s1").some((d) => d.text.includes("yarn"))).toBe(true);
45
+ // 其他会话(未隔离)看不到隔离内容
46
+ expect(store.getActiveDecisionsMerged("other-s1", "proj-iso").some((d) => d.text.includes("yarn"))).toBe(false);
47
+ expect(store.getFeedbackMerged("other-s1", "proj-iso", 10).some((f) => f.text.includes("pnpm"))).toBe(false);
48
+ });
49
+ it("检索过滤:search / queryEvents / expand 对他人不可见,自己可见", () => {
50
+ store.append({ session_id: "iso-s1", kind: "user_message", ts: "2026-08-15T00:00:05.000Z", body: "隔离细节金丝雀编号 8842" }, { isolation: true });
51
+ expect(store.search("金丝雀", { sessionId: "other-s1" })).toHaveLength(0);
52
+ expect(store.search("金丝雀", { sessionId: "iso-s1" }).length).toBeGreaterThan(0);
53
+ const ev = store.append({ session_id: "iso-s1", kind: "user_message", ts: "2026-08-15T00:00:06.000Z", body: "隔离可回拉标记" }, { isolation: true, spillRef: "iso://spill/1" });
54
+ expect(store.expand(ev.id, { sessionId: "other-s1" })).toContain("隔离内容不可见");
55
+ expect(store.expand(ev.id, { sessionId: "iso-s1" })).toContain("隔离可回拉标记");
56
+ const q = queryEvents(store, { sessionId: "other-s1", kind: "user_message" });
57
+ expect(q.results.some((r) => r.body.includes("金丝雀"))).toBe(false);
58
+ const self = queryEvents(store, { sessionId: "iso-s1", kind: "user_message" });
59
+ expect(self.results.some((r) => r.body.includes("金丝雀"))).toBe(true);
60
+ });
61
+ it("会话隔离状态切换:进入后写入隔离,解除后历史仍隔离、新写共享", () => {
62
+ store.setSessionIsolation("iso-s2", true);
63
+ expect(store.getSessionIsolation("iso-s2")).toBe(true);
64
+ store.append({ session_id: "iso-s2", kind: "user_message", ts: "2026-08-15T00:00:07.000Z", body: "隔离期内容" }, { isolation: store.getSessionIsolation("iso-s2") });
65
+ expect(store.eventsDb.prepare(`SELECT isolation FROM events WHERE session_id = 'iso-s2' AND body = '隔离期内容'`).get()).toEqual({ isolation: 1 });
66
+ store.setSessionIsolation("iso-s2", false);
67
+ expect(store.getSessionIsolation("iso-s2")).toBe(false);
68
+ store.append({ session_id: "iso-s2", kind: "user_message", ts: "2026-08-15T00:00:08.000Z", body: "解除后共享内容" }, { isolation: store.getSessionIsolation("iso-s2") });
69
+ expect(store.eventsDb.prepare(`SELECT isolation FROM events WHERE session_id = 'iso-s2' AND body = '解除后共享内容'`).get()).toEqual({ isolation: 0 });
70
+ // 历史仍隔离:他人检索不到
71
+ expect(store.search("隔离期", { sessionId: "other-s1" })).toHaveLength(0);
72
+ });
73
+ it("沉淀:unisolateRow 后他人可见,supersede replacement 继承隔离标记", () => {
74
+ const d = store.getActiveDecisions("iso-s1").find((x) => x.text.includes("yarn"));
75
+ expect(d).toBeDefined();
76
+ expect(d?.isolation).toBe(1);
77
+ const ok = store.unisolateRow("iso-s1", "decisions", d.id);
78
+ expect(ok).toBe(true);
79
+ expect(store.getActiveDecisionsMerged("other-s1", "proj-iso").some((x) => x.text.includes("yarn"))).toBe(true);
80
+ // supersede replacement 继承被 supersede 行的隔离标记
81
+ store.proposeDecision("iso-s1", "待替换的隔离决策", {
82
+ scope: "project",
83
+ projectKey: "proj-iso",
84
+ isolation: true,
85
+ ts: "2026-08-15T00:00:09.000Z",
86
+ });
87
+ store.confirmLatestProposed("iso-s1", { ts: "2026-08-15T00:00:10.000Z" });
88
+ const r = store.supersedeLatestActive("iso-s1", "替换后决策", { ts: "2026-08-15T00:00:11.000Z" });
89
+ expect(r?.replacement.isolation).toBe(1);
90
+ });
91
+ it("状态卡:隔离模式只显示本会话内容并标注", () => {
92
+ const card = buildStatusCard(store, { sessionId: "iso-s1", projectKey: "proj-iso", isolated: true });
93
+ expect(card).toContain("本会话已隔离");
94
+ expect(card).toContain("替换后决策");
95
+ expect(card).not.toContain("(来自其他会话)");
96
+ // 非隔离模式状态卡包含已沉淀内容(未隔离继承)
97
+ const normal = buildStatusCard(store, { sessionId: "other-s1", projectKey: "proj-iso" });
98
+ expect(normal).toContain("yarn");
99
+ expect(normal).not.toContain("替换后决策");
100
+ });
101
+ });
102
+ //# sourceMappingURL=isolation.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isolation.test.js","sourceRoot":"","sources":["../src/isolation.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ import type { Decision, FeedbackRow, Goal, StructuredWriteOptions, ThreadStore } from "./store.js";
2
+ export interface TurnInput {
3
+ user_msg?: string;
4
+ assistant_msg?: string;
5
+ }
6
+ export interface GoalAction {
7
+ text: string;
8
+ action: "add";
9
+ }
10
+ export type DecisionAction = {
11
+ action: "propose";
12
+ text: string;
13
+ } | {
14
+ action: "confirm";
15
+ } | {
16
+ action: "revoke";
17
+ } | {
18
+ action: "supersede";
19
+ text: string;
20
+ };
21
+ export interface FeedbackAction {
22
+ text: string;
23
+ kind: "preference" | "correction";
24
+ }
25
+ export interface TurnAnalysis {
26
+ goals: GoalAction[];
27
+ decisions: DecisionAction[];
28
+ feedback: FeedbackAction[];
29
+ }
30
+ export declare function analyzeTurn(input: TurnInput): TurnAnalysis;
31
+ export interface AppliedTurn {
32
+ goals: Goal[];
33
+ decisions: Decision[];
34
+ feedback: FeedbackRow[];
35
+ }
36
+ export declare function applyTurn(store: ThreadStore, sessionId: string, input: TurnInput, opts?: StructuredWriteOptions): AppliedTurn;
37
+ export declare function applyAnalysis(store: ThreadStore, sessionId: string, input: TurnInput, opts?: StructuredWriteOptions): AppliedTurn;
@@ -0,0 +1,149 @@
1
+ const ACCEPT_SHORT = new Set([
2
+ "嗯",
3
+ "恩",
4
+ "好",
5
+ "好的",
6
+ "可以",
7
+ "行",
8
+ "没问题",
9
+ "ok",
10
+ "okay",
11
+ "确认",
12
+ "同意",
13
+ "就这样",
14
+ "不错",
15
+ "对",
16
+ "是",
17
+ "收到",
18
+ "明白",
19
+ ]);
20
+ const ACCEPT_RE = /^(嗯+|好+|行+|可以|没问题|OK|ok|Okay|收到|明白|确认|同意)$/;
21
+ const SUPERSEDE_RE = /(?:改成|改用|换成|改为|换用)[::,,\s]*([^。!!??]+)/;
22
+ const REVOKE_RE = /(?:算了|撤销|放弃|不要(?:用|做|要)?了|不要用|别(?:用|做|要)|不用了|回退|改回|别用了)/;
23
+ const DECLARE_RE = [
24
+ /(?<![忘标登])(?:我|我们)?(?:已|已经)?记下(?:了)?[::,,\s]*([^。!!??]+)/,
25
+ /(?:方案定为|就采用|决定采用)[::,,\s]*([^。!!??]+)/,
26
+ /(?:我|我们)(?:已|已经)?(?:决定(?!性)|确定(?!性))[::,,\s]*([^。!!??]+)/,
27
+ ];
28
+ const GOAL_RE = /^(?:帮我|请|再帮我|然后帮我|接着帮我|帮我再|还要帮我|麻烦帮我)?(?:实现|修复|重构|添加|新增|完成|构建|创建|删除|优化|升级|接入|验证|设计|规划|开发|搭建|编写|整理|部署|迁移|拆分|合并|安装|配置|生成)/;
29
+ const PREFERENCE_RE = /(?:以后|下次|永远|总是|记得|优先|别再|不要再)/;
30
+ const CORRECTION_RE = /(?:不要|别|别再|不要再)/;
31
+ const MAX_DECISION_TEXT = 200;
32
+ const MAX_GOAL_TEXT = 500;
33
+ export function analyzeTurn(input) {
34
+ const analysis = { goals: [], decisions: [], feedback: [] };
35
+ const userRaw = (input.user_msg ?? "").trim();
36
+ const user = stripPunct(userRaw);
37
+ const assistantRaw = (input.assistant_msg ?? "").trim();
38
+ const assistant = stripPunct(assistantRaw);
39
+ if (user) {
40
+ if (GOAL_RE.test(user)) {
41
+ analysis.goals.push({ text: userRaw.slice(0, MAX_GOAL_TEXT), action: "add" });
42
+ }
43
+ const isPreference = PREFERENCE_RE.test(user);
44
+ if (isPreference) {
45
+ analysis.feedback.push({
46
+ text: userRaw.slice(0, MAX_GOAL_TEXT),
47
+ kind: CORRECTION_RE.test(user) ? "correction" : "preference",
48
+ });
49
+ }
50
+ const supersede = userRaw.match(SUPERSEDE_RE);
51
+ if (supersede) {
52
+ analysis.decisions.push({ action: "supersede", text: supersede[1].trim().slice(0, MAX_DECISION_TEXT) });
53
+ }
54
+ else if (!isPreference) {
55
+ if (REVOKE_RE.test(user)) {
56
+ analysis.decisions.push({ action: "revoke" });
57
+ }
58
+ else if (ACCEPT_SHORT.has(user) || ACCEPT_RE.test(user)) {
59
+ analysis.decisions.push({ action: "confirm" });
60
+ }
61
+ }
62
+ }
63
+ if (assistant) {
64
+ for (const re of DECLARE_RE) {
65
+ const m = assistantRaw.match(re);
66
+ if (m && m[1]?.trim()) {
67
+ const text = m[1].trim().slice(0, MAX_DECISION_TEXT);
68
+ if (!/[吗呢吧]$/.test(text)) {
69
+ analysis.decisions.push({ action: "propose", text });
70
+ }
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ return analysis;
76
+ }
77
+ export function applyTurn(store, sessionId, input, opts = {}) {
78
+ const ts = opts.ts ?? new Date().toISOString();
79
+ let sourceEvent = opts.sourceEvent;
80
+ const userMsg = (input.user_msg ?? "").trim();
81
+ const assistantMsg = (input.assistant_msg ?? "").trim();
82
+ if (userMsg) {
83
+ const ev = store.append({
84
+ session_id: sessionId,
85
+ kind: "user_message",
86
+ ts,
87
+ body: userMsg,
88
+ });
89
+ sourceEvent ??= ev.id;
90
+ }
91
+ if (assistantMsg) {
92
+ const ev = store.append({
93
+ session_id: sessionId,
94
+ kind: "assistant_message",
95
+ ts,
96
+ body: assistantMsg,
97
+ });
98
+ sourceEvent ??= ev.id;
99
+ }
100
+ return applyAnalysis(store, sessionId, input, { ...opts, sourceEvent, ts });
101
+ }
102
+ export function applyAnalysis(store, sessionId, input, opts = {}) {
103
+ const analysis = analyzeTurn(input);
104
+ const ts = opts.ts ?? new Date().toISOString();
105
+ const sourceEvent = opts.sourceEvent;
106
+ const structuredOpts = { sourceEvent, ts, scope: opts.scope, projectKey: opts.projectKey, origin: opts.origin, isolation: opts.isolation };
107
+ return store.transact(() => {
108
+ const applied = { goals: [], decisions: [], feedback: [] };
109
+ for (const g of analysis.goals) {
110
+ applied.goals.push(store.addGoal(sessionId, g.text, structuredOpts));
111
+ }
112
+ for (const d of analysis.decisions) {
113
+ switch (d.action) {
114
+ case "propose":
115
+ applied.decisions.push(store.proposeDecision(sessionId, d.text, structuredOpts));
116
+ break;
117
+ case "confirm": {
118
+ const confirmed = store.confirmLatestProposed(sessionId, { ts });
119
+ if (confirmed) {
120
+ applied.decisions.push(confirmed);
121
+ }
122
+ break;
123
+ }
124
+ case "revoke": {
125
+ const revoked = store.revokeLatestActive(sessionId, { ts });
126
+ if (revoked) {
127
+ applied.decisions.push(revoked);
128
+ }
129
+ break;
130
+ }
131
+ case "supersede": {
132
+ const result = store.supersedeLatestActive(sessionId, d.text, structuredOpts);
133
+ if (result) {
134
+ applied.decisions.push(result.superseded, result.replacement);
135
+ }
136
+ break;
137
+ }
138
+ }
139
+ }
140
+ for (const f of analysis.feedback) {
141
+ applied.feedback.push(store.addFeedback(sessionId, f.text, f.kind, structuredOpts));
142
+ }
143
+ return applied;
144
+ });
145
+ }
146
+ function stripPunct(text) {
147
+ return text.replace(/[,。!!??、]/g, "").trim();
148
+ }
149
+ //# sourceMappingURL=light-confirm.js.map