cortico-world-screen-diary 0.2.0 → 0.6.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.
- package/CHANGELOG.md +55 -0
- package/README.md +77 -0
- package/describe.json +83 -2
- package/dist/webui.js +1 -1
- package/dist/webui.js.map +3 -3
- package/package.json +32 -8
- package/src/ENV_PROMPT.en.md +1 -1
- package/src/ENV_PROMPT.md +1 -1
- package/src/apps.ts +88 -0
- package/src/book.ts +14 -0
- package/src/config.ts +268 -212
- package/src/definition.ts +12 -1
- package/src/flow.ts +178 -0
- package/src/model-text.ts +58 -42
- package/src/state.ts +28 -1
- package/src/tokens.ts +286 -0
- package/src/webui/client.ts +483 -206
- package/src/world.ts +714 -369
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortico-world-screen-diary",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "活动日记:把窥屏助手的事件流整理成平滑的活动记忆与二维状态;细节会淡忘,只留大概。先安装并启用 cortico-world-screen-sense。",
|
|
6
6
|
"author": "Wwhois163wW",
|
|
7
7
|
"license": "AGPL-3.0-or-later",
|
|
8
8
|
"repository": {
|
|
@@ -17,28 +17,52 @@
|
|
|
17
17
|
"registry": "https://registry.npmjs.org/",
|
|
18
18
|
"access": "public"
|
|
19
19
|
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
|
|
20
|
+
"keywords": [
|
|
21
|
+
"cortico-world",
|
|
22
|
+
"cortico",
|
|
23
|
+
"diary",
|
|
24
|
+
"activity",
|
|
25
|
+
"screen",
|
|
26
|
+
"screen-sense"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=22"
|
|
30
|
+
},
|
|
22
31
|
"packageManager": "pnpm@11.28.6",
|
|
23
32
|
"main": "./src/index.ts",
|
|
24
|
-
"exports": {
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./src/index.ts"
|
|
35
|
+
},
|
|
25
36
|
"cortico": {
|
|
26
37
|
"kind": "world",
|
|
27
38
|
"api": 6,
|
|
28
39
|
"displayName": "活动日记",
|
|
29
|
-
"tags": [
|
|
40
|
+
"tags": [
|
|
41
|
+
"memory",
|
|
42
|
+
"requires-extension",
|
|
43
|
+
"sensitive-data",
|
|
44
|
+
"experimental"
|
|
45
|
+
],
|
|
30
46
|
"icon": "assets/icon.svg",
|
|
31
47
|
"locales": {
|
|
32
48
|
"en": {
|
|
33
49
|
"displayName": "Activity Diary",
|
|
34
|
-
"description": "
|
|
50
|
+
"description": "Activity Diary turns the Screen Sense event stream into smoothed activity memory and a two-axis state; details fade, only the gist remains. Install and enable cortico-world-screen-sense first."
|
|
35
51
|
}
|
|
36
52
|
},
|
|
37
53
|
"describe": "describe.json",
|
|
38
54
|
"webUiClient": "dist/webui.js",
|
|
39
55
|
"webUiStyle": "dist/webui.css"
|
|
40
56
|
},
|
|
41
|
-
"files": [
|
|
57
|
+
"files": [
|
|
58
|
+
"src",
|
|
59
|
+
"assets",
|
|
60
|
+
"dist",
|
|
61
|
+
"describe.json",
|
|
62
|
+
"README.md",
|
|
63
|
+
"CHANGELOG.md",
|
|
64
|
+
"LICENSE"
|
|
65
|
+
],
|
|
42
66
|
"scripts": {
|
|
43
67
|
"build": "node scripts/build-webui.mjs",
|
|
44
68
|
"test": "vitest run",
|
package/src/ENV_PROMPT.en.md
CHANGED
|
@@ -9,4 +9,4 @@ Rules:
|
|
|
9
9
|
- Use `diary_search` only when exact detail matters. Reaction and thinking do not need precise recall; a rough picture with small inaccuracies is fine.
|
|
10
10
|
- Communication apps are filtered; AI chats are blurred to neutral by default. Do not ask about or guess those.
|
|
11
11
|
- The diary keeps no images; when a picture matters, use the producer's `screen_photo_show`.
|
|
12
|
-
- An empty diary means screen-sense is not installed, not enabled, or has no events yet; do not retry in a loop.
|
|
12
|
+
- screen-diary.reengage means the person came back after quiet time; a light greeting fits, without follow-up questions.\n- screen-diary.checkin means the flow depleted and they may be focused or away; offer a break or a soft check-in once.\n- An empty diary means screen-sense is not installed, not enabled, or has no events yet; do not retry in a loop.
|
package/src/ENV_PROMPT.md
CHANGED
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
- 只有需要精确细节时才用 `diary_search`。平时的反应与思考不需要精确回忆,允许概括与轻微出入。
|
|
10
10
|
- 通讯类应用已被过滤,AI 对话默认模糊为中性;不要追问或猜测这些内容。
|
|
11
11
|
- 日记不保存原图;需要画面细节时用窥屏助手的 `screen_photo_show`。
|
|
12
|
-
- 日记为空说明窥屏助手未安装、未启用或还没产生事件;不要反复重试。
|
|
12
|
+
- screen-diary.reengage 表示对方在安静一段时间后回来了,适合轻声打招呼,不要连续追问。\n- screen-diary.checkin 表示流枯竭、可能在专注或离开;可以提醒休息或轻声确认,一次就够。\n- 日记为空说明窥屏助手未安装、未启用或还没产生事件;不要反复重试。
|
package/src/apps.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seen applications and their local policy overrides.
|
|
3
|
+
*
|
|
4
|
+
* The default projection comes from `state.classify`; a per-app override can change the category
|
|
5
|
+
* ("word class") or the flow-decay window. Only app names, counters and overrides are stored here;
|
|
6
|
+
* window titles are never kept.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import type { Category } from './state.ts';
|
|
11
|
+
|
|
12
|
+
export type AppCategory = Category | 'filtered';
|
|
13
|
+
|
|
14
|
+
export interface AppRecord {
|
|
15
|
+
app: string;
|
|
16
|
+
count: number;
|
|
17
|
+
lastSeen: string;
|
|
18
|
+
/** What the built-in classifier says; refreshed on sight. */
|
|
19
|
+
defaultCategory: AppCategory;
|
|
20
|
+
/** User/pet override; undefined means follow `defaultCategory`. */
|
|
21
|
+
category?: AppCategory;
|
|
22
|
+
/** Per-app flow decay in minutes; undefined means the category default. */
|
|
23
|
+
decayMinutes?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const FILE = 'apps.json';
|
|
27
|
+
|
|
28
|
+
export class AppCatalog {
|
|
29
|
+
private records = new Map<string, AppRecord>();
|
|
30
|
+
|
|
31
|
+
constructor(private readonly dir: string) {
|
|
32
|
+
this.load();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Count one sighting and refresh the default category. */
|
|
36
|
+
observe(app: string, at: number, defaultCategory: AppCategory): void {
|
|
37
|
+
if (!app) return;
|
|
38
|
+
const seen = new Date(at).toISOString();
|
|
39
|
+
const record = this.records.get(app);
|
|
40
|
+
this.records.set(app, record
|
|
41
|
+
? { ...record, count: record.count + 1, lastSeen: seen, defaultCategory }
|
|
42
|
+
: { app, count: 1, lastSeen: seen, defaultCategory });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
policy(app: string): AppRecord | undefined {
|
|
46
|
+
return this.records.get(app);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Set or clear the per-app overrides; returns the updated record. */
|
|
50
|
+
setPolicy(app: string, category: AppCategory | null | undefined, decayMinutes: number | null | undefined): AppRecord | undefined {
|
|
51
|
+
const record = this.records.get(app);
|
|
52
|
+
if (!record) return undefined;
|
|
53
|
+
const next: AppRecord = { ...record };
|
|
54
|
+
if (category === null) delete next.category;
|
|
55
|
+
else if (category !== undefined) next.category = category;
|
|
56
|
+
if (decayMinutes === null || decayMinutes === 0) delete next.decayMinutes;
|
|
57
|
+
else if (decayMinutes !== undefined && decayMinutes > 0) next.decayMinutes = Math.min(180, Math.max(1, Math.round(decayMinutes)));
|
|
58
|
+
this.records.set(app, next);
|
|
59
|
+
this.save();
|
|
60
|
+
return next;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Records sorted by most recently seen. */
|
|
64
|
+
list(): AppRecord[] {
|
|
65
|
+
return [...this.records.values()].sort((a, b) => b.lastSeen.localeCompare(a.lastSeen));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
save(): void {
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(join(this.dir, FILE), JSON.stringify([...this.records.values()], null, 2), 'utf8');
|
|
71
|
+
} catch {
|
|
72
|
+
/* the catalog is helpful, not essential */
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private load(): void {
|
|
77
|
+
const path = join(this.dir, FILE);
|
|
78
|
+
if (!existsSync(path)) return;
|
|
79
|
+
try {
|
|
80
|
+
const rows = JSON.parse(readFileSync(path, 'utf8')) as AppRecord[];
|
|
81
|
+
for (const row of rows) {
|
|
82
|
+
if (row && typeof row.app === 'string') this.records.set(row.app, row);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
/* a broken catalog starts empty */
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/src/book.ts
CHANGED
|
@@ -130,6 +130,20 @@ export class DiaryBook {
|
|
|
130
130
|
return this.entries.filter((entry) => localParts(entry.ts, this.timezone).day === key);
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/** All detail entries, oldest first (used by the 0.1 → 0.2 state backfill). */
|
|
134
|
+
chronological(): DiaryEntry[] {
|
|
135
|
+
return this.entries.slice().sort((a, b) => a.cursor - b.cursor);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Drop entries that fail the predicate (privacy migration) and keep the cursor. */
|
|
139
|
+
retainEntries(keep: (entry: DiaryEntry) => boolean): number {
|
|
140
|
+
const removed = this.entries.filter((entry) => !keep(entry));
|
|
141
|
+
if (removed.length === 0) return 0;
|
|
142
|
+
this.entries = this.entries.filter((entry) => keep(entry));
|
|
143
|
+
writeFileSync(join(this.dir, ENTRIES), this.entries.map((entry) => `${JSON.stringify(entry)}\n`).join(''), 'utf8');
|
|
144
|
+
return removed.length;
|
|
145
|
+
}
|
|
146
|
+
|
|
133
147
|
/** Newest detail entries first (explicit recall only). */
|
|
134
148
|
recent(limit = 100): DiaryEntry[] {
|
|
135
149
|
return this.entries.slice(-Math.max(1, limit)).reverse();
|