@kanadego/dsh-heartbeat 1.8.0 → 1.9.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.
- package/README.md +50 -69
- package/assets/frontwin.ps1 +1 -0
- package/assets/idle.ps1 +1 -0
- package/assets/screenpulse.ps1 +4 -2
- package/config/policy.json +73 -72
- package/config/profile-schema.json +306 -306
- package/dist/{bindings-XPPSKILN.js → bindings-225SI7KC.js} +3 -2
- package/dist/{chunk-UCJR6TC7.js → chunk-2NB4QILF.js} +73 -21
- package/dist/chunk-2NB4QILF.js.map +1 -0
- package/dist/{chunk-IFTFDHZX.js → chunk-7TW6DD6Q.js} +11 -6
- package/dist/chunk-7TW6DD6Q.js.map +1 -0
- package/dist/{chunk-63FUJLIF.js → chunk-BQCXYE7N.js} +17 -2
- package/dist/chunk-BQCXYE7N.js.map +1 -0
- package/dist/chunk-HT57FOEF.js +2454 -0
- package/dist/chunk-HT57FOEF.js.map +1 -0
- package/dist/chunk-IV2ZWQA3.js +56 -0
- package/dist/chunk-IV2ZWQA3.js.map +1 -0
- package/dist/{chunk-MXJPX3FM.js → chunk-KOOQOMQY.js} +117 -62
- package/dist/chunk-KOOQOMQY.js.map +1 -0
- package/dist/{chunk-VIZNIQLK.js → chunk-OYVNWB5G.js} +82 -74
- package/dist/chunk-OYVNWB5G.js.map +1 -0
- package/dist/{chunk-RB3NLLGF.js → chunk-QOQ7EPZZ.js} +3 -3
- package/dist/{chunk-J6ZTRFFW.js → chunk-TY6UP74X.js} +6 -2
- package/dist/chunk-TY6UP74X.js.map +1 -0
- package/dist/chunk-WRJCINZ7.js +569 -0
- package/dist/chunk-WRJCINZ7.js.map +1 -0
- package/dist/chunk-WRUTATW4.js +47 -0
- package/dist/chunk-WRUTATW4.js.map +1 -0
- package/dist/chunk-ZLV4NMFX.js +333 -0
- package/dist/chunk-ZLV4NMFX.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +565 -491
- package/dist/cli/index.js.map +1 -1
- package/dist/{inbox-57NHQHY3.js → inbox-JLT5G7TL.js} +5 -3
- package/dist/index.d.ts +517 -0
- package/dist/index.js +482 -2418
- package/dist/index.js.map +1 -1
- package/dist/{migrate-43PUK25G.js → migrate-WDVFHUP7.js} +4 -3
- package/dist/orchestrator-2ZLU7RGE.js +60 -0
- package/dist/{pool-JQC5JWIZ.js → pool-B34NTH6D.js} +5 -3
- package/dist/{snapshot-ZBCSX67Y.js → snapshot-JQDJ346Z.js} +5 -4
- package/dist/snapshot-JQDJ346Z.js.map +1 -0
- package/package.json +10 -3
- package/config/watchlist.json +0 -9
- package/dist/chunk-63FUJLIF.js.map +0 -1
- package/dist/chunk-IFTFDHZX.js.map +0 -1
- package/dist/chunk-J6ZTRFFW.js.map +0 -1
- package/dist/chunk-MXJPX3FM.js.map +0 -1
- package/dist/chunk-SQJLKP7L.js +0 -729
- package/dist/chunk-SQJLKP7L.js.map +0 -1
- package/dist/chunk-UCJR6TC7.js.map +0 -1
- package/dist/chunk-VIZNIQLK.js.map +0 -1
- /package/dist/{bindings-XPPSKILN.js.map → bindings-225SI7KC.js.map} +0 -0
- /package/dist/{chunk-RB3NLLGF.js.map → chunk-QOQ7EPZZ.js.map} +0 -0
- /package/dist/{inbox-57NHQHY3.js.map → inbox-JLT5G7TL.js.map} +0 -0
- /package/dist/{migrate-43PUK25G.js.map → migrate-WDVFHUP7.js.map} +0 -0
- /package/dist/{pool-JQC5JWIZ.js.map → orchestrator-2ZLU7RGE.js.map} +0 -0
- /package/dist/{snapshot-ZBCSX67Y.js.map → pool-B34NTH6D.js.map} +0 -0
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
collectMigrationEntries,
|
|
7
7
|
decryptContainer,
|
|
8
8
|
encryptContainer
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-2NB4QILF.js";
|
|
10
|
+
import "./chunk-7TW6DD6Q.js";
|
|
11
|
+
import "./chunk-WRUTATW4.js";
|
|
11
12
|
export {
|
|
12
13
|
MIGRATE_FILES,
|
|
13
14
|
MIGRATE_MAGIC,
|
|
@@ -17,4 +18,4 @@ export {
|
|
|
17
18
|
decryptContainer,
|
|
18
19
|
encryptContainer
|
|
19
20
|
};
|
|
20
|
-
//# sourceMappingURL=migrate-
|
|
21
|
+
//# sourceMappingURL=migrate-WDVFHUP7.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ENGINE_ROOM_EXTRA_TOOLS,
|
|
3
|
+
HOME_ROTATE_EVENT_COUNT,
|
|
4
|
+
TOKEN_SAVER_IDLE_SECONDS,
|
|
5
|
+
agentLooksAlive,
|
|
6
|
+
applyHeartbeatInterval,
|
|
7
|
+
beat,
|
|
8
|
+
buildEngineRoomAllowList,
|
|
9
|
+
gateFilePath,
|
|
10
|
+
getLastBeat,
|
|
11
|
+
homeSessionId,
|
|
12
|
+
isContextOverflowError,
|
|
13
|
+
loadCursors,
|
|
14
|
+
markHomeRotateIfOverflow,
|
|
15
|
+
parseJsonBlock,
|
|
16
|
+
pickSpokenLine,
|
|
17
|
+
resetHomeAgent,
|
|
18
|
+
resetHomeSession,
|
|
19
|
+
sessionEventCount,
|
|
20
|
+
sessionEvents,
|
|
21
|
+
shouldArchiveRotatedHome,
|
|
22
|
+
shouldRotateHome,
|
|
23
|
+
spokeTextSince,
|
|
24
|
+
startOrchestrator
|
|
25
|
+
} from "./chunk-HT57FOEF.js";
|
|
26
|
+
import "./chunk-WRJCINZ7.js";
|
|
27
|
+
import "./chunk-3QJJRXIR.js";
|
|
28
|
+
import "./chunk-KOOQOMQY.js";
|
|
29
|
+
import "./chunk-BQCXYE7N.js";
|
|
30
|
+
import "./chunk-IV2ZWQA3.js";
|
|
31
|
+
import "./chunk-QOQ7EPZZ.js";
|
|
32
|
+
import "./chunk-OYVNWB5G.js";
|
|
33
|
+
import "./chunk-7TW6DD6Q.js";
|
|
34
|
+
import "./chunk-WRUTATW4.js";
|
|
35
|
+
export {
|
|
36
|
+
ENGINE_ROOM_EXTRA_TOOLS,
|
|
37
|
+
HOME_ROTATE_EVENT_COUNT,
|
|
38
|
+
TOKEN_SAVER_IDLE_SECONDS,
|
|
39
|
+
agentLooksAlive,
|
|
40
|
+
applyHeartbeatInterval,
|
|
41
|
+
beat,
|
|
42
|
+
buildEngineRoomAllowList,
|
|
43
|
+
gateFilePath,
|
|
44
|
+
getLastBeat,
|
|
45
|
+
homeSessionId,
|
|
46
|
+
isContextOverflowError,
|
|
47
|
+
loadCursors,
|
|
48
|
+
markHomeRotateIfOverflow,
|
|
49
|
+
parseJsonBlock,
|
|
50
|
+
pickSpokenLine,
|
|
51
|
+
resetHomeAgent,
|
|
52
|
+
resetHomeSession,
|
|
53
|
+
sessionEventCount,
|
|
54
|
+
sessionEvents,
|
|
55
|
+
shouldArchiveRotatedHome,
|
|
56
|
+
shouldRotateHome,
|
|
57
|
+
spokeTextSince,
|
|
58
|
+
startOrchestrator
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=orchestrator-2ZLU7RGE.js.map
|
|
@@ -16,8 +16,10 @@ import {
|
|
|
16
16
|
savePool,
|
|
17
17
|
seedsFilePath,
|
|
18
18
|
surfaceSeed
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-KOOQOMQY.js";
|
|
20
|
+
import "./chunk-IV2ZWQA3.js";
|
|
21
|
+
import "./chunk-7TW6DD6Q.js";
|
|
22
|
+
import "./chunk-WRUTATW4.js";
|
|
21
23
|
export {
|
|
22
24
|
SEED_CATEGORY_CAPS,
|
|
23
25
|
TTL_KEYS,
|
|
@@ -37,4 +39,4 @@ export {
|
|
|
37
39
|
seedsFilePath,
|
|
38
40
|
surfaceSeed
|
|
39
41
|
};
|
|
40
|
-
//# sourceMappingURL=pool-
|
|
42
|
+
//# sourceMappingURL=pool-B34NTH6D.js.map
|
|
@@ -3,13 +3,14 @@ import {
|
|
|
3
3
|
snapshotDue,
|
|
4
4
|
snapshotIfDue,
|
|
5
5
|
snapshotProfile
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-QOQ7EPZZ.js";
|
|
7
|
+
import "./chunk-OYVNWB5G.js";
|
|
8
|
+
import "./chunk-7TW6DD6Q.js";
|
|
9
|
+
import "./chunk-WRUTATW4.js";
|
|
9
10
|
export {
|
|
10
11
|
SNAPSHOT_THRESHOLD,
|
|
11
12
|
snapshotDue,
|
|
12
13
|
snapshotIfDue,
|
|
13
14
|
snapshotProfile
|
|
14
15
|
};
|
|
15
|
-
//# sourceMappingURL=snapshot-
|
|
16
|
+
//# sourceMappingURL=snapshot-JQDJ346Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanadego/dsh-heartbeat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.1",
|
|
4
4
|
"description": "Heartbeat plugin for DeepSeek Harness: scheduled autonomous reflection loop with user profile, material pool, pace gate, and gated proactive expression.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,9 +23,16 @@
|
|
|
23
23
|
},
|
|
24
24
|
"type": "module",
|
|
25
25
|
"main": "dist/index.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
26
27
|
"exports": {
|
|
27
|
-
".":
|
|
28
|
-
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./cli": {
|
|
33
|
+
"types": "./dist/cli/index.d.ts",
|
|
34
|
+
"default": "./dist/cli/index.js"
|
|
35
|
+
},
|
|
29
36
|
"./client": "./client.js",
|
|
30
37
|
"./package.json": "./package.json",
|
|
31
38
|
"./cordis.patch.yml": "./cordis.patch.yml"
|
package/config/watchlist.json
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"_comment": "心跳的兴趣追踪清单 v1。type=npm|github。新增一行=多一个好奇方向。查询频率由浏览相内置节流(默认 6h/轮),此处无需配置。",
|
|
3
|
-
"targets": [
|
|
4
|
-
{ "id": "dsh-core", "type": "npm", "name": "@deepseek-ai/dsh", "note": "DeepSeek Harness 本体——我的家,升级前必知" },
|
|
5
|
-
{ "id": "presence-watch", "type": "github", "repo": "tomsteve1102/presence-watch", "note": "presence-watch——本项目的讨论起点(在场感知模块),值得长期围观" },
|
|
6
|
-
{ "id": "eco-spec", "type": "github", "repo": "T-Auto/dsh-ecosystem-spec", "note": "dsh 生态适配器规范——关系到我们的多版本适配路线" },
|
|
7
|
-
{ "id": "dsh-std", "type": "github", "repo": "Yan-Zero/dsh-std", "note": "dsh-std 元协议——同上,未来可能的迁移目标" }
|
|
8
|
-
]
|
|
9
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/profile/inbox.ts"],"sourcesContent":["// Observation inbox (design doc §3.2): append-only queue of pending\n// observations; drained by the consolidation run. DPAPI-encrypted. Items hold\n// pointers + at most one short sentence - never raw conversation/screen text.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { loadEncryptedText, saveEncryptedText } from '../vault/vault.js';\nimport type { InboxItem } from './types.js';\n\nconst MAX_NOTE_CHARS = 120;\n\nexport function inboxFilePath(dataDir: string): string {\n return path.join(dataDir, 'profile_inbox.jsonl');\n}\n\nfunction truncateNote(note: string): string {\n const oneLine = note.replace(/\\r?\\n/g, ' ').trim();\n return oneLine.length > MAX_NOTE_CHARS ? oneLine.slice(0, MAX_NOTE_CHARS) + '…' : oneLine;\n}\n\nexport function inboxAppend(\n guard: PathGuard,\n file: string,\n item: Omit<InboxItem, 'id'> & { id?: string },\n): InboxItem {\n const full: InboxItem = {\n id: item.id ?? randomUUID().slice(0, 8),\n kind: item.kind,\n at: item.at,\n ref: item.ref,\n note: truncateNote(item.note),\n };\n const prev = loadEncryptedText(guard, file) ?? '';\n saveEncryptedText(guard, file, prev + JSON.stringify(full) + '\\n');\n return full;\n}\n\nexport function inboxCount(guard: PathGuard, file: string): number {\n const raw = loadEncryptedText(guard, file);\n if (!raw) return 0;\n return raw.split('\\n').filter((l) => l.trim()).length;\n}\n\n/**\n * Drain all items. `commit` controls crash semantics: on consolidation failure\n * the inbox must SURVIVE (§3.7), so the caller drains with commit=false first,\n * and only rewrites an empty inbox after the run succeeded.\n */\nexport function inboxDrain(guard: PathGuard, file: string): InboxItem[] {\n const raw = loadEncryptedText(guard, file);\n if (!raw) return [];\n const items: InboxItem[] = [];\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n items.push(JSON.parse(trimmed) as InboxItem);\n } catch {\n // corrupt line: drop (queue is re-derivable from sources)\n }\n }\n return items;\n}\n\nexport function inboxClear(guard: PathGuard, file: string): void {\n saveEncryptedText(guard, file, '');\n}\n\n/** Dedupe key (§3.7): same source kind + same ref must not flood the queue. */\nexport function dedupeItems(items: InboxItem[]): InboxItem[] {\n const seen = new Set<string>();\n const out: InboxItem[] = [];\n for (const it of items) {\n const key = `${it.kind}#${it.ref}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(it);\n }\n return out;\n}\n\nexport function inboxHealthCheck(guard: PathGuard, file: string): { total: number; corrupt: number; duplicates: number } {\n const raw = loadEncryptedText(guard, file) ?? '';\n let total = 0;\n let corrupt = 0;\n const keys = new Set<string>();\n let duplicates = 0;\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const it = JSON.parse(trimmed) as InboxItem;\n total += 1;\n const key = `${it.kind}#${it.ref}`;\n if (keys.has(key)) duplicates += 1;\n keys.add(key);\n } catch {\n corrupt += 1;\n }\n }\n return { total, corrupt, duplicates };\n}\n\nexport function inboxFileExists(guard: PathGuard, file: string): boolean {\n return fs.existsSync(guard.assert(file));\n}\n"],"mappings":";;;;;;AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAK3B,IAAM,iBAAiB;AAEhB,SAAS,cAAc,SAAyB;AACrD,SAAO,KAAK,KAAK,SAAS,qBAAqB;AACjD;AAEA,SAAS,aAAa,MAAsB;AAC1C,QAAM,UAAU,KAAK,QAAQ,UAAU,GAAG,EAAE,KAAK;AACjD,SAAO,QAAQ,SAAS,iBAAiB,QAAQ,MAAM,GAAG,cAAc,IAAI,WAAM;AACpF;AAEO,SAAS,YACd,OACA,MACA,MACW;AACX,QAAM,OAAkB;AAAA,IACtB,IAAI,KAAK,MAAM,WAAW,EAAE,MAAM,GAAG,CAAC;AAAA,IACtC,MAAM,KAAK;AAAA,IACX,IAAI,KAAK;AAAA,IACT,KAAK,KAAK;AAAA,IACV,MAAM,aAAa,KAAK,IAAI;AAAA,EAC9B;AACA,QAAM,OAAO,kBAAkB,OAAO,IAAI,KAAK;AAC/C,oBAAkB,OAAO,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI,IAAI;AACjE,SAAO;AACT;AAEO,SAAS,WAAW,OAAkB,MAAsB;AACjE,QAAM,MAAM,kBAAkB,OAAO,IAAI;AACzC,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE;AACjD;AAOO,SAAS,WAAW,OAAkB,MAA2B;AACtE,QAAM,MAAM,kBAAkB,OAAO,IAAI;AACzC,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,QAAqB,CAAC;AAC5B,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AACd,QAAI;AACF,YAAM,KAAK,KAAK,MAAM,OAAO,CAAc;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,WAAW,OAAkB,MAAoB;AAC/D,oBAAkB,OAAO,MAAM,EAAE;AACnC;AAGO,SAAS,YAAY,OAAiC;AAC3D,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAmB,CAAC;AAC1B,aAAW,MAAM,OAAO;AACtB,UAAM,MAAM,GAAG,GAAG,IAAI,IAAI,GAAG,GAAG;AAChC,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,QAAI,KAAK,EAAE;AAAA,EACb;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,OAAkB,MAAsE;AACvH,QAAM,MAAM,kBAAkB,OAAO,IAAI,KAAK;AAC9C,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,aAAa;AACjB,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,QAAS;AACd,QAAI;AACF,YAAM,KAAK,KAAK,MAAM,OAAO;AAC7B,eAAS;AACT,YAAM,MAAM,GAAG,GAAG,IAAI,IAAI,GAAG,GAAG;AAChC,UAAI,KAAK,IAAI,GAAG,EAAG,eAAc;AACjC,WAAK,IAAI,GAAG;AAAA,IACd,QAAQ;AACN,iBAAW;AAAA,IACb;AAAA,EACF;AACA,SAAO,EAAE,OAAO,SAAS,WAAW;AACtC;AAEO,SAAS,gBAAgB,OAAkB,MAAuB;AACvE,SAAO,GAAG,WAAW,MAAM,OAAO,IAAI,CAAC;AACzC;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/paths.ts","../src/vault/vault.ts"],"sourcesContent":["// Workspace/data directory resolution and package asset location.\n//\n// The data dir is the ONLY directory the plugin writes to at runtime\n// (requirement 8). Resolution order: env override > plugin config > default\n// (packageRoot/data). All fs writes must go through the path guard created\n// from this dir (see core/path-guard.ts).\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface WorkspacePaths {\n /** Guard boundary and the only writable tree at runtime. */\n dataDir: string;\n settingsDir: string;\n logsDir: string;\n tmpDir: string;\n exportsDir: string;\n /** Package root (where package.json lives). Read-only at runtime. */\n packageRoot: string;\n configDir: string;\n assetsDir: string;\n}\n\nexport interface WorkspaceConfigInput {\n dataDir?: string | null;\n}\n\nlet resolved: WorkspacePaths | null = null;\n\n/** Walk up from a module file to the directory containing package.json. */\nexport function findPackageRoot(startFile: string): string {\n let dir = path.dirname(path.resolve(startFile));\n for (;;) {\n if (fs.existsSync(path.join(dir, 'package.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) throw new Error('package.json not found above ' + startFile);\n dir = parent;\n }\n}\n\nexport function packageRoot(): string {\n return findPackageRoot(fileURLToPath(import.meta.url));\n}\n\n/**\n * Resolve and create the workspace layout. Idempotent; must be called once\n * from the plugin entry before any fs write happens.\n */\nexport function initWorkspace(config: WorkspaceConfigInput = {}): WorkspacePaths {\n if (resolved) return resolved;\n const root = packageRoot();\n const dataDir = path.resolve(\n process.env.HEARTBEAT_DATA_DIR || config.dataDir || path.join(root, 'data'),\n );\n const paths: WorkspacePaths = {\n dataDir,\n settingsDir: path.join(dataDir, 'settings'),\n logsDir: path.join(dataDir, 'logs'),\n tmpDir: path.join(dataDir, 'tmp'),\n exportsDir: path.join(dataDir, 'exports'),\n packageRoot: root,\n configDir: path.join(root, 'config'),\n assetsDir: path.join(root, 'assets'),\n };\n // Bootstrap creation happens before the guard exists; creating exactly one\n // directory tree at a known location is the safe window for this.\n for (const dir of [\n paths.dataDir,\n paths.settingsDir,\n paths.logsDir,\n paths.tmpDir,\n paths.exportsDir,\n ]) {\n fs.mkdirSync(dir, { recursive: true });\n }\n resolved = paths;\n return paths;\n}\n\n/** Access already-initialized paths. Throws if initWorkspace was not called. */\nexport function workspace(): WorkspacePaths {\n if (!resolved) throw new Error('workspace not initialized: call initWorkspace() first');\n return resolved;\n}\n\n/** Test-only: forget the resolved singleton so a later init re-resolves. */\nexport function resetWorkspaceForTest(): void {\n resolved = null;\n}\n","// Transparent encryption read/write layer (port of v1 lib/vault.mjs).\n//\n// Mechanism: Node <-> vault.ps1 hand off through temp files (plain text\n// windows are millisecond-scale). v2 hardening over v1:\n// - plaintext temp files live ONLY under data/tmp (never beside targets);\n// - every path passes the workspace guard before reaching PowerShell;\n// - encrypted output lands via same-volume rename (atomic replace).\n// Encryption failure is fail-closed: throw, never fall back to plaintext.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { workspace } from '../core/paths.js';\n\nconst MAGIC = Buffer.from('KHBV1', 'ascii');\nconst VAULT_TIMEOUT_MS = 30_000;\n\nlet cachedScriptPath: string | null = null;\n\nfunction vaultScriptPath(): string {\n if (cachedScriptPath) return cachedScriptPath;\n const script = path.join(workspace().assetsDir, 'vault.ps1');\n if (!fs.existsSync(script)) throw new Error(`vault script missing: ${script}`);\n cachedScriptPath = script;\n return script;\n}\n\nfunction tmpPlainName(target: string): string {\n return path.join(workspace().tmpDir, `.${path.basename(target)}.${randomUUID().slice(0, 8)}.plain`);\n}\n\nfunction tmpEncName(target: string): string {\n return path.join(workspace().tmpDir, `.${path.basename(target)}.${randomUUID().slice(0, 8)}.enc`);\n}\n\nfunction runVault(guard: PathGuard, args: string[]): void {\n const r = spawnSync(\n 'powershell.exe',\n ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', vaultScriptPath(), ...args],\n { timeout: VAULT_TIMEOUT_MS, encoding: 'utf8' },\n );\n if (r.error) throw new Error(`vault spawn failed: ${String(r.error)}`);\n if (r.status !== 0) {\n const detail = (r.stderr || r.stdout || '').trim().split('\\n').slice(-3).join('; ');\n throw new Error(`vault ${args[0]} failed (exit ${r.status}): ${detail}`);\n }\n}\n\n/** KHBV1 header check without reading the whole file. */\nexport function isEncrypted(file: string): boolean {\n if (!fs.existsSync(file)) return false;\n const fd = fs.openSync(file, 'r');\n try {\n const hdr = Buffer.alloc(MAGIC.length);\n const read = fs.readSync(fd, hdr, 0, hdr.length, 0);\n return read === MAGIC.length && hdr.equals(MAGIC);\n } finally {\n fs.closeSync(fd);\n }\n}\n\n/**\n * Read JSON: encrypted files are decrypted to a data/tmp plaintext window\n * that is shredded immediately after parse; plaintext stays compatible.\n */\nexport function loadJson<T>(guard: PathGuard, file: string): T | null {\n const f = guard.assert(file);\n if (!fs.existsSync(f)) return null;\n if (!isEncrypted(f)) {\n try {\n return JSON.parse(fs.readFileSync(f, 'utf8')) as T;\n } catch {\n return null;\n }\n }\n const tmp = tmpPlainName(f);\n try {\n runVault(guard, ['unprotect', '-InFile', f, '-OutFile', tmp]);\n return JSON.parse(fs.readFileSync(tmp, 'utf8')) as T;\n } finally {\n fs.rmSync(tmp, { force: true });\n }\n}\n\n/** Write JSON: plaintext window in data/tmp -> DPAPI encrypt -> atomic rename into place. */\nexport function saveJson(guard: PathGuard, file: string, value: unknown): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const plain = tmpPlainName(f);\n const enc = tmpEncName(f);\n try {\n fs.writeFileSync(plain, JSON.stringify(value, null, 2), 'utf8');\n runVault(guard, ['protect', '-InFile', plain, '-OutFile', enc]);\n fs.renameSync(enc, f);\n } finally {\n fs.rmSync(plain, { force: true });\n fs.rmSync(enc, { force: true });\n }\n}\n\n/** Read a plaintext UTF-8 text file (ledger etc.), with fallback. */\nexport function readText(guard: PathGuard, file: string, fallback = ''): string {\n const f = guard.assert(file);\n try {\n return fs.readFileSync(f, 'utf8');\n } catch {\n return fallback;\n }\n}\n\n/** Write a plaintext UTF-8 text file atomically (ledger etc.). */\nexport function writeText(guard: PathGuard, file: string, content: string): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const tmp = tmpPlainName(f);\n try {\n fs.writeFileSync(tmp, content, 'utf8');\n fs.renameSync(tmp, f);\n } finally {\n fs.rmSync(tmp, { force: true });\n }\n}\n\n/** Read an encrypted UTF-8 text file (e.g. seeds.jsonl). Missing -> null. */\nexport function loadEncryptedText(guard: PathGuard, file: string): string | null {\n const f = guard.assert(file);\n if (!fs.existsSync(f)) return null;\n if (!isEncrypted(f)) return fs.readFileSync(f, 'utf8');\n const tmp = tmpPlainName(f);\n try {\n runVault(guard, ['unprotect', '-InFile', f, '-OutFile', tmp]);\n return fs.readFileSync(tmp, 'utf8');\n } finally {\n fs.rmSync(tmp, { force: true });\n }\n}\n\n/** Write an encrypted UTF-8 text file atomically. */\nexport function saveEncryptedText(guard: PathGuard, file: string, content: string): void {\n const f = guard.assert(file);\n fs.mkdirSync(path.dirname(f), { recursive: true });\n const plain = tmpPlainName(f);\n const enc = tmpEncName(f);\n try {\n fs.writeFileSync(plain, content, 'utf8');\n runVault(guard, ['protect', '-InFile', plain, '-OutFile', enc]);\n fs.renameSync(enc, f);\n } finally {\n fs.rmSync(plain, { force: true });\n fs.rmSync(enc, { force: true });\n }\n}\n\n/** Encrypt any binary file (e.g. screenshot): inFile -> encrypted outFile. */\nexport function encryptFile(guard: PathGuard, inFile: string, outFile: string): void {\n const inPath = guard.assert(inFile);\n const outPath = guard.assert(outFile);\n fs.mkdirSync(path.dirname(outPath), { recursive: true });\n const enc = tmpEncName(outPath);\n try {\n runVault(guard, ['protect', '-InFile', inPath, '-OutFile', enc]);\n fs.renameSync(enc, outPath);\n } finally {\n fs.rmSync(enc, { force: true });\n }\n}\n\n/**\n * Decrypt an encrypted file to a plaintext output (caller must place the\n * output under data/tmp and delete it after use; \"use then burn\" discipline).\n */\nexport function decryptFile(guard: PathGuard, inFile: string, outFile: string): void {\n const inPath = guard.assert(inFile);\n const outPath = guard.assert(outFile);\n fs.mkdirSync(path.dirname(outPath), { recursive: true });\n runVault(guard, ['unprotect', '-InFile', inPath, '-OutFile', outPath]);\n}\n"],"mappings":"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","names":["fs","path","path","fs"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/bindings.ts"],"sourcesContent":["// Session bindings (design doc D13): binding = ①被心跳观察对话事件(inbox\n// 来源)+ ②主动消息投递目标。Canonical storage: data/settings/bindings.json\n// (user layer - survives uninstall/reinstall, D15). Edited via CLI or hand.\n\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { PathGuard } from './path-guard.js';\n\nexport interface SessionBinding {\n sessionId: string;\n /** Deliver heartbeat expressions into this session's inbox (live only). */\n deliver: boolean;\n /** Observe this session's new user messages into the profile inbox. */\n observe: boolean;\n addedAt: string;\n}\n\nexport interface BindingsFile {\n bindings: SessionBinding[];\n}\n\nexport function bindingsFilePath(settingsDir: string): string {\n return path.join(settingsDir, 'bindings.json');\n}\n\nexport function loadBindings(guard: PathGuard, settingsDir: string): BindingsFile {\n const file = bindingsFilePath(settingsDir);\n try {\n const raw = JSON.parse(fs.readFileSync(guard.assert(file), 'utf8')) as BindingsFile;\n return { bindings: Array.isArray(raw.bindings) ? raw.bindings : [] };\n } catch {\n return { bindings: [] };\n }\n}\n\nexport function saveBindings(guard: PathGuard, settingsDir: string, data: BindingsFile): void {\n const file = guard.assert(bindingsFilePath(settingsDir));\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, JSON.stringify(data, null, 2), 'utf8');\n}\n\nexport function addBinding(\n guard: PathGuard,\n settingsDir: string,\n sessionId: string,\n opts: { deliver?: boolean; observe?: boolean } = {},\n): SessionBinding {\n const data = loadBindings(guard, settingsDir);\n const existing = data.bindings.find((b) => b.sessionId === sessionId);\n if (existing) {\n existing.deliver = opts.deliver ?? existing.deliver;\n existing.observe = opts.observe ?? existing.observe;\n saveBindings(guard, settingsDir, data);\n return existing;\n }\n const binding: SessionBinding = {\n sessionId,\n deliver: opts.deliver ?? true,\n observe: opts.observe ?? false,\n addedAt: new Date().toISOString(),\n };\n data.bindings.push(binding);\n saveBindings(guard, settingsDir, data);\n return binding;\n}\n\nexport function removeBinding(guard: PathGuard, settingsDir: string, sessionId: string): boolean {\n const data = loadBindings(guard, settingsDir);\n const before = data.bindings.length;\n data.bindings = data.bindings.filter((b) => b.sessionId !== sessionId);\n if (data.bindings.length === before) return false;\n saveBindings(guard, settingsDir, data);\n return true;\n}\n\nexport function deliverTargets(data: BindingsFile): SessionBinding[] {\n return data.bindings.filter((b) => b.deliver);\n}\n\nexport function observeTargets(data: BindingsFile): SessionBinding[] {\n return data.bindings.filter((b) => b.observe);\n}\n"],"mappings":";AAIA,OAAO,QAAQ;AACf,OAAO,UAAU;AAgBV,SAAS,iBAAiB,aAA6B;AAC5D,SAAO,KAAK,KAAK,aAAa,eAAe;AAC/C;AAEO,SAAS,aAAa,OAAkB,aAAmC;AAChF,QAAM,OAAO,iBAAiB,WAAW;AACzC,MAAI;AACF,UAAM,MAAM,KAAK,MAAM,GAAG,aAAa,MAAM,OAAO,IAAI,GAAG,MAAM,CAAC;AAClE,WAAO,EAAE,UAAU,MAAM,QAAQ,IAAI,QAAQ,IAAI,IAAI,WAAW,CAAC,EAAE;AAAA,EACrE,QAAQ;AACN,WAAO,EAAE,UAAU,CAAC,EAAE;AAAA,EACxB;AACF;AAEO,SAAS,aAAa,OAAkB,aAAqB,MAA0B;AAC5F,QAAM,OAAO,MAAM,OAAO,iBAAiB,WAAW,CAAC;AACvD,KAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACpD,KAAG,cAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG,MAAM;AAC9D;AAEO,SAAS,WACd,OACA,aACA,WACA,OAAiD,CAAC,GAClC;AAChB,QAAM,OAAO,aAAa,OAAO,WAAW;AAC5C,QAAM,WAAW,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,cAAc,SAAS;AACpE,MAAI,UAAU;AACZ,aAAS,UAAU,KAAK,WAAW,SAAS;AAC5C,aAAS,UAAU,KAAK,WAAW,SAAS;AAC5C,iBAAa,OAAO,aAAa,IAAI;AACrC,WAAO;AAAA,EACT;AACA,QAAM,UAA0B;AAAA,IAC9B;AAAA,IACA,SAAS,KAAK,WAAW;AAAA,IACzB,SAAS,KAAK,WAAW;AAAA,IACzB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,EAClC;AACA,OAAK,SAAS,KAAK,OAAO;AAC1B,eAAa,OAAO,aAAa,IAAI;AACrC,SAAO;AACT;AAEO,SAAS,cAAc,OAAkB,aAAqB,WAA4B;AAC/F,QAAM,OAAO,aAAa,OAAO,WAAW;AAC5C,QAAM,SAAS,KAAK,SAAS;AAC7B,OAAK,WAAW,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,cAAc,SAAS;AACrE,MAAI,KAAK,SAAS,WAAW,OAAQ,QAAO;AAC5C,eAAa,OAAO,aAAa,IAAI;AACrC,SAAO;AACT;AAEO,SAAS,eAAe,MAAsC;AACnE,SAAO,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO;AAC9C;AAEO,SAAS,eAAe,MAAsC;AACnE,SAAO,KAAK,SAAS,OAAO,CAAC,MAAM,EAAE,OAAO;AAC9C;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/seeds/pool.ts","../src/seeds/types.ts"],"sourcesContent":["// Material pool engine (design doc §4). All four eviction rules are\n// deterministic code - no LLM involvement (§4.2). Storage: data/seeds.jsonl,\n// one seed per JSON line, DPAPI-encrypted at rest (D14).\n\nimport path from 'node:path';\nimport type { PathGuard } from '../core/path-guard.js';\nimport { loadEncryptedText, saveEncryptedText } from '../vault/vault.js';\nimport type { Policy } from '../config/schema.js';\nimport {\n SEED_SOURCE_DEFAULT_CONFIDENCE,\n emptySeedDb,\n type Seed,\n type SeedCategory,\n type SeedDb,\n type SeedRetireReason,\n type SeedSource,\n type SeedTag,\n} from './types.js';\n\nconst DAY_MS = 86_400_000;\n\n// Per-category pool caps (2026-09-18 spec ⑤): topic stock vs conversation-grown\n// chat material, decoupled from `source`; sum == the global maxActive default.\nexport const SEED_CATEGORY_CAPS: Record<SeedCategory, number> = { topic: 16, chat: 14 };\n\nconst CATEGORY_VALUES: readonly string[] = ['topic', 'chat'];\n\n/** Lenient category coercion for on-disk rows: anything missing/unknown (all\n * pre-v1.5 seeds) counts as topic stock. */\nexport function normalizeCategory(v: unknown): SeedCategory {\n return typeof v === 'string' && (CATEGORY_VALUES as readonly string[]).includes(v)\n ? (v as SeedCategory)\n : 'topic';\n}\n\nexport interface AddSeedInput {\n text: string;\n topic?: string;\n tag?: SeedTag;\n source?: SeedSource;\n /** Loop role (spec ⑤); defaults from source: chat->chat, everything else->topic. */\n category?: SeedCategory;\n confidence?: number;\n}\n\nexport interface AddSeedResult {\n kind: 'added' | 'merged' | 'duplicate';\n seed: Seed;\n evicted?: Seed;\n}\n\nexport interface GcReport {\n consumed: number;\n expired: number;\n coldBench: number;\n activeAfter: number;\n}\n\nexport const TTL_KEYS: readonly SeedTag[] = ['news', 'fandom', 'scene', 'promise'];\n\nexport function normalizeTag(tag: string | undefined): SeedTag {\n if (tag && (TTL_KEYS as readonly string[]).includes(tag)) return tag as SeedTag;\n return 'scene';\n}\n\nfunction parseIso(v: string): number {\n const t = Date.parse(v);\n return Number.isFinite(t) ? t : 0;\n}\n\n// ── storage ─────────────────────────────────────────────────────────────\n\nexport function seedsFilePath(dataDir: string): string {\n return path.join(dataDir, 'seeds.jsonl');\n}\n\nexport function loadPool(guard: PathGuard, file: string): SeedDb {\n const raw = loadEncryptedText(guard, file);\n if (raw === null) return emptySeedDb();\n const db = emptySeedDb();\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const obj = JSON.parse(trimmed) as Seed;\n if (typeof obj.id === 'string' && obj.id.startsWith('s')) {\n // spec ⑤ loadPool tolerance: only the id is load-bearing; a missing or\n // unknown category (every pre-v1.5 row) falls back to topic stock.\n obj.category = normalizeCategory(obj.category);\n db.seeds.push(obj);\n const n = Number(obj.id.slice(1));\n if (Number.isFinite(n) && n > db.seq) db.seq = n;\n }\n } catch {\n // corrupt line: skip (pool is a cache; journal-level audit lives elsewhere)\n }\n }\n return db;\n}\n\nexport function savePool(guard: PathGuard, file: string, db: SeedDb): void {\n const body = db.seeds.map((s) => JSON.stringify(s)).join('\\n');\n saveEncryptedText(guard, file, body ? body + '\\n' : '');\n}\n\n// ── queries ─────────────────────────────────────────────────────────────\n\nexport const activeSeeds = (db: SeedDb): Seed[] => db.seeds.filter((s) => s.status === 'active');\nexport const archivedSeeds = (db: SeedDb): Seed[] => db.seeds.filter((s) => s.status === 'archived');\n\n// ── eviction scoring (rule 4) ───────────────────────────────────────────\n\n/**\n * Comprehensive score (§4.2): freshness x0.4 + unused x0.3 + confidence x0.3.\n * Higher = keep. Lowest-scoring active is evicted when the pool is full.\n */\nexport function evictionScore(seed: Seed, policy: Policy, now: number): number {\n const ttlDays = policy.seeds.ttlDays[seed.tag] || 14;\n const daysStale = Math.max(0, (now - parseIso(seed.lastEvidenceAt)) / DAY_MS);\n const freshness = Math.max(0, 1 - daysStale / ttlDays);\n const unused = seed.used === 0 ? 1 : 1 / seed.used;\n const w = policy.seeds.scoreWeights;\n return freshness * w.freshness + unused * w.unused + seed.confidence * w.confidence;\n}\n\n/** Pick the eviction victim: unprotected actives first (§4.2 加权保护); with\n * `category`, only that category's actives compete (spec ⑤ per-category caps). */\nexport function pickEvictionVictim(db: SeedDb, policy: Policy, now: number, category?: SeedCategory): Seed | null {\n const actives = activeSeeds(db).filter((s) => category === undefined || normalizeCategory(s.category) === category);\n if (actives.length === 0) return null;\n const unprotected = actives.filter((s) => !s.protected);\n const candidates = unprotected.length > 0 ? unprotected : actives;\n let worst: Seed | null = null;\n let worstScore = Number.POSITIVE_INFINITY;\n for (const s of candidates) {\n const score = evictionScore(s, policy, now);\n if (score < worstScore) {\n worstScore = score;\n worst = s;\n }\n }\n return worst;\n}\n\nfunction archiveSeed(seed: Seed, reason: SeedRetireReason, now: number): void {\n seed.status = 'archived';\n seed.retireReason = reason;\n seed.retiredAt = new Date(now).toISOString();\n}\n\n/** Spec ④: chat-grown material is consumed ONCE (conversation freshness);\n * every other category follows the policy-wide retireAfterUsed. */\nfunction retireLimit(s: Seed, policy: Policy): number {\n return normalizeCategory(s.category) === 'chat' ? 1 : policy.seeds.retireAfterUsed;\n}\n\n// ── operations ──────────────────────────────────────────────────────────\n\nexport function addSeed(\n guard: PathGuard,\n file: string,\n policy: Policy,\n input: AddSeedInput,\n now = Date.now(),\n): AddSeedResult {\n const db = loadPool(guard, file);\n const text = input.text.trim();\n if (!text) throw new Error('seed text must not be empty');\n const tag = normalizeTag(input.tag);\n const source: SeedSource = input.source ?? 'chat';\n const category = normalizeCategory(input.category ?? (source === 'chat' ? 'chat' : 'topic'));\n const confidence = input.confidence ?? SEED_SOURCE_DEFAULT_CONFIDENCE[source];\n\n // Exact-text dedup against actives.\n const dup = activeSeeds(db).find((s) => s.text === text);\n if (dup) return { kind: 'duplicate', seed: dup };\n\n const nowIso = new Date(now).toISOString();\n const ttlMs = (policy.seeds.ttlDays[tag] || 14) * DAY_MS;\n\n // Same-topic merge (§4.3): collapse all same-topic actives + the new item.\n const sameTopic = activeSeeds(db).filter((s) => s.topic === (input.topic ?? text.slice(0, 24)));\n let seed: Seed;\n if (sameTopic.length > 0) {\n const kept = sameTopic[0]!;\n kept.text = text; // brief takes the latest\n kept.tag = tag;\n kept.source = source;\n kept.category = normalizeCategory(input.category ?? kept.category ?? category);\n kept.confidence = Math.max(kept.confidence, confidence);\n kept.used = sameTopic.reduce((acc, s) => acc + s.used, 0); // used accumulates\n kept.lastEvidenceAt = [kept.lastEvidenceAt, nowIso, ...sameTopic.map((s) => s.lastEvidenceAt)]\n .reduce((a, b) => (parseIso(b) > parseIso(a) ? b : a));\n kept.expiresAt = new Date(Math.max(parseIso(kept.expiresAt), now + ttlMs)).toISOString();\n kept.protected = kept.protected || source === 'hand' || (source === 'profile' && confidence >= 0.7);\n // remove the other same-topic actives (merged into kept)\n for (const extra of sameTopic.slice(1)) {\n extra.status = 'archived';\n extra.retireReason = 'completed';\n extra.retiredAt = nowIso;\n }\n seed = kept;\n // capacity still applies after growth check below if kept is somehow over cap\n if (activeSeeds(db).length > policy.seeds.maxActive) {\n const victim = pickEvictionVictim(db, policy, now, normalizeCategory(kept.category));\n if (victim && victim.id !== kept.id) {\n archiveSeed(victim, 'pool_cap', now);\n savePool(guard, file, db);\n return { kind: 'merged', seed: kept, evicted: victim };\n }\n }\n savePool(guard, file, db);\n return { kind: 'merged', seed: kept };\n }\n\n // Capacity first (rules 4 + spec ⑤): per-category cap evicts within its own\n // pool; the global cap evicts in the incoming seed's category (a full topic\n // stock must not push chat material out and vice versa).\n let evicted: Seed | undefined;\n const catCount = activeSeeds(db).filter((s) => normalizeCategory(s.category) === category).length;\n if (catCount >= SEED_CATEGORY_CAPS[category]) {\n const victim = pickEvictionVictim(db, policy, now, category);\n if (victim) {\n archiveSeed(victim, 'pool_cap', now);\n evicted = victim;\n }\n }\n if (activeSeeds(db).length >= policy.seeds.maxActive) {\n const victim = pickEvictionVictim(db, policy, now, category);\n if (victim) {\n archiveSeed(victim, 'pool_cap', now);\n evicted = evicted ?? victim;\n }\n }\n\n db.seq += 1;\n seed = {\n id: `s${db.seq}`,\n text,\n topic: input.topic ?? text.slice(0, 24),\n tag,\n source,\n category,\n confidence,\n protected: source === 'hand' || (source === 'profile' && confidence >= 0.7),\n used: 0,\n bornAt: nowIso,\n expiresAt: new Date(now + ttlMs).toISOString(),\n lastUsedAt: null,\n lastEvidenceAt: nowIso,\n status: 'active',\n };\n db.seeds.push(seed);\n savePool(guard, file, db);\n return { kind: 'added', seed, evicted };\n}\n\n/**\n * Deterministic gc (rules 1-3 of §4.2). Rule 4 (pool cap) fires on add only.\n */\nexport function gcPool(guard: PathGuard, file: string, policy: Policy, now = Date.now()): GcReport {\n const db = loadPool(guard, file);\n const report: GcReport = { consumed: 0, expired: 0, coldBench: 0, activeAfter: 0 };\n for (const s of activeSeeds(db)) {\n const ageMs = now - parseIso(s.bornAt);\n const sinceEvidence = parseIso(s.lastEvidenceAt);\n const sinceUsed = s.lastUsedAt ? parseIso(s.lastUsedAt) : 0;\n if (s.used >= retireLimit(s, policy) && sinceEvidence <= sinceUsed) {\n archiveSeed(s, 'consumed', now);\n report.consumed += 1;\n } else if (now > parseIso(s.expiresAt)) {\n archiveSeed(s, 'expired', now);\n report.expired += 1;\n } else if (s.used === 0 && ageMs >= policy.seeds.coldBenchDays * DAY_MS) {\n archiveSeed(s, 'cold_bench', now);\n report.coldBench += 1;\n }\n }\n report.activeAfter = activeSeeds(db).length;\n savePool(guard, file, db);\n return report;\n}\n\n/** The seed surfaced in a real expression: count + maybe retire (rule 1). */\nexport function surfaceSeed(\n guard: PathGuard,\n file: string,\n policy: Policy,\n id: string,\n now = Date.now(),\n): Seed | null {\n const db = loadPool(guard, file);\n const s = db.seeds.find((x) => x.id === id && x.status === 'active');\n if (!s) return null;\n s.used += 1;\n s.lastUsedAt = new Date(now).toISOString();\n if (s.used >= retireLimit(s, policy) && parseIso(s.lastEvidenceAt) <= parseIso(s.lastUsedAt)) {\n archiveSeed(s, 'consumed', now);\n }\n savePool(guard, file, db);\n return s;\n}\n\n/** Deliberate retirement (item completed / no longer relevant). */\nexport function archiveSeedById(\n guard: PathGuard,\n file: string,\n id: string,\n reason: SeedRetireReason = 'completed',\n now = Date.now(),\n): Seed | null {\n const db = loadPool(guard, file);\n const s = db.seeds.find((x) => x.id === id && x.status === 'active');\n if (!s) return null;\n archiveSeed(s, reason, now);\n savePool(guard, file, db);\n return s;\n}\n\n/** Restore an archived seed to the active pool (UI operation; resets TTL). */\nexport function restoreSeed(\n guard: PathGuard,\n file: string,\n policy: Policy,\n id: string,\n now = Date.now(),\n): { ok: true; seed: Seed } | { ok: false; reason: string } {\n const db = loadPool(guard, file);\n const s = db.seeds.find((x) => x.id === id && x.status === 'archived');\n if (!s) return { ok: false, reason: 'archived seed not found' };\n if (activeSeeds(db).length >= policy.seeds.maxActive) {\n return { ok: false, reason: `pool full (${policy.seeds.maxActive}); archive something first` };\n }\n s.status = 'active';\n s.retireReason = undefined;\n s.retiredAt = undefined;\n s.expiresAt = new Date(now + (policy.seeds.ttlDays[s.tag] || 14) * DAY_MS).toISOString();\n s.lastEvidenceAt = new Date(now).toISOString();\n savePool(guard, file, db);\n return { ok: true, seed: s };\n}\n\n/** Hard delete (UI explicit action with confirm; audit lives in the CLI/log). */\nexport function deleteSeed(guard: PathGuard, file: string, id: string): boolean {\n const db = loadPool(guard, file);\n const before = db.seeds.length;\n db.seeds = db.seeds.filter((x) => x.id !== id);\n if (db.seeds.length === before) return false;\n savePool(guard, file, db);\n return true;\n}\n","// Material pool (\"素材池\") types. Charter (axiom 3): this pool is a CACHE,\n// not memory - volatile, evictable, burnable; long-term memory only absorbs\n// conversation-validated content. Everything here may be destroyed.\n\nexport type SeedTag = 'news' | 'fandom' | 'scene' | 'promise';\nexport type SeedSource = 'chat' | 'screen' | 'browse' | 'hand' | 'profile';\nexport type SeedCategory = 'topic' | 'chat';\nexport type SeedRetireReason = 'consumed' | 'expired' | 'cold_bench' | 'pool_cap' | 'completed';\n\nexport interface Seed {\n id: string;\n /** Short brief shown to the reflection prompt. */\n text: string;\n /** Merge key (§4.3): same-topic actives collapse into one. */\n topic: string;\n tag: SeedTag;\n source: SeedSource;\n /** Loop role, decoupled from source (2026-09-18 spec ⑤): 'topic' = wander/\n * hand/profile stock (random pick), 'chat' = conversation-grown, consumed\n * once. Absent on pre-v1.5 rows; loadPool defaults them to 'topic'. */\n category?: SeedCategory;\n /** 0..1 trust level; handwriting and high-confidence profile sources gain eviction protection. */\n confidence: number;\n /** Eviction protection (§4.2 pool-cap rule): handwritten / high-confidence profile sources. */\n protected: boolean;\n /** Times this seed surfaced in an actual expression. */\n used: number;\n bornAt: string;\n /** bornAt + ttlDays[tag]; archived when passed (rule 2). */\n expiresAt: string;\n lastUsedAt: string | null;\n /** Fresh-evidence watermark: rule 1 spares seeds with evidence newer than last use. */\n lastEvidenceAt: string;\n status: 'active' | 'archived';\n retireReason?: SeedRetireReason;\n retiredAt?: string;\n}\n\nexport interface SeedDb {\n seq: number;\n seeds: Seed[];\n}\n\nexport function emptySeedDb(): SeedDb {\n return { seq: 0, seeds: [] };\n}\n\nexport const SEED_SOURCE_DEFAULT_CONFIDENCE: Record<SeedSource, number> = {\n hand: 1.0,\n profile: 0.7,\n chat: 0.6,\n screen: 0.4,\n browse: 0.4,\n};\n"],"mappings":"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","names":[]}
|