@unblocklabs/unblock-memory 0.9.0 → 0.10.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 +2 -2
- package/dist/src/config.js +6 -3
- package/dist/src/diagnostics.d.ts +2 -2
- package/dist/src/emotion-baselines.d.ts +133 -0
- package/dist/src/emotion-baselines.js +428 -0
- package/dist/src/inside-out-runtime.js +58 -9
- package/dist/src/inside-out-store.d.ts +61 -0
- package/dist/src/inside-out-store.js +174 -0
- package/dist/src/inside-out.d.ts +142 -7
- package/dist/src/inside-out.js +42 -66
- package/dist/src/manager.d.ts +2 -2
- package/dist/src/plugin.js +5 -0
- package/dist/src/retrieval-telemetry.d.ts +1 -1
- package/docs/configuration.md +126 -10
- package/docs/peoplesql.md +7 -0
- package/docs/retrieval.md +6 -3
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ import { resolveAgentDir, resolveStateDir } from "openclaw/plugin-sdk/memory-cor
|
|
|
3
3
|
import { listAgentIds } from "openclaw/plugin-sdk/agent-runtime";
|
|
4
4
|
import { MEMORY_DATABASE } from "./memory-database.js";
|
|
5
5
|
import { dateOption } from "./date-option.js";
|
|
6
|
-
import { linkInsideOutPeople, repairInsideOutIdentities, reportInsideOut, runInsideOut } from "./inside-out.js";
|
|
6
|
+
import { linkInsideOutPeople, purgeInsideOutPerson, repairInsideOutIdentities, reportInsideOut, runInsideOut, refreshInsideOutBaselinesAsync, reportInsideOutBaselines } from "./inside-out.js";
|
|
7
7
|
export function registerInsideOut(api, config) {
|
|
8
8
|
const paths = (cfg, agentId) => {
|
|
9
9
|
if (!listAgentIds(cfg).includes(agentId))
|
|
@@ -21,6 +21,7 @@ export function registerInsideOut(api, config) {
|
|
|
21
21
|
const p = paths(cfg, opts.agent);
|
|
22
22
|
const repair = opts.repairIdentities ? await repairInsideOutIdentities(p) : undefined;
|
|
23
23
|
console.log(JSON.stringify({ ...repair, ...linkInsideOutPeople(p.storePath) }, null, 2));
|
|
24
|
+
await refreshInsideOutBaselinesAsync(p.storePath, { minInteractions: config.insideOut.baselineMinInteractions, link: false });
|
|
24
25
|
if (repair?.errors.length)
|
|
25
26
|
process.exitCode = 1;
|
|
26
27
|
});
|
|
@@ -29,24 +30,45 @@ export function registerInsideOut(api, config) {
|
|
|
29
30
|
.action(async (opts) => {
|
|
30
31
|
const result = await runInsideOut({ ...paths(cfg, opts.agent), config, retry: opts.retry, sessionId: opts.session });
|
|
31
32
|
console.log(JSON.stringify(result, null, 2));
|
|
32
|
-
if ("failed" in result && (result.failed || result.errors.length))
|
|
33
|
+
if ("failed" in result && (result.failed || result.errors.length || result.baselineError))
|
|
33
34
|
process.exitCode = 1;
|
|
34
35
|
});
|
|
36
|
+
root.command("baseline-refresh").description("Update local weekly baselines and missing comparisons; no Jev calls")
|
|
37
|
+
.option("--agent <id>", "Agent id", "main")
|
|
38
|
+
.option("--recompare", "Append revised comparisons using newly available historical grades; preserve earlier results")
|
|
39
|
+
.action(async (opts) => {
|
|
40
|
+
console.log(JSON.stringify(await refreshInsideOutBaselinesAsync(paths(cfg, opts.agent).storePath, { minInteractions: config.insideOut.baselineMinInteractions, recompare: opts.recompare }), null, 2));
|
|
41
|
+
});
|
|
42
|
+
root.command("baselines").description("Read person-linked weekly and cumulative baseline history")
|
|
43
|
+
.option("--agent <id>", "Agent id", "main").option("--person <id>", "Exact PeopleSQL person ID")
|
|
44
|
+
.option("--since <date>", "Inclusive UTC period-end date YYYY-MM-DD").option("--limit <n>", "Maximum weekly snapshots, 1–1000", "100")
|
|
45
|
+
.action((opts) => {
|
|
46
|
+
console.log(JSON.stringify(reportInsideOutBaselines(paths(cfg, opts.agent).storePath, { personId: opts.person, since: opts.since ? dateOption(opts.since, 0) : undefined, limit: Number(opts.limit) }), null, 2));
|
|
47
|
+
});
|
|
48
|
+
root.command("purge").description("Delete one person's emotion baselines and assessment history; no Jev calls")
|
|
49
|
+
.requiredOption("--person <id>", "Exact PeopleSQL person ID").option("--agent <id>", "Agent id", "main")
|
|
50
|
+
.option("--grades", "Also erase this person's raw emotion grades; tombstones keep those replies from being regraded")
|
|
51
|
+
.action((opts) => {
|
|
52
|
+
console.log(JSON.stringify(purgeInsideOutPerson(paths(cfg, opts.agent).storePath, opts.person, { grades: opts.grades }), null, 2));
|
|
53
|
+
});
|
|
35
54
|
for (const command of ["report", "export"]) {
|
|
36
55
|
root.command(command).option("--agent <id>", "Agent id", "main")
|
|
37
56
|
.option("--session <id>", "Filter one session")
|
|
38
57
|
.option("--sender <id>", "Human sender ID").option("--since <date>", "Inclusive UTC date YYYY-MM-DD")
|
|
58
|
+
.option("--person <id>", "Exact PeopleSQL person ID").option("--deviation-history", "Include previous personal comparisons (export)")
|
|
39
59
|
.option("--emotion <name>", "Filter an emotion probability").option("--min <probability>", "Minimum probability", "0")
|
|
40
60
|
.option("--bucket <period>", "day or week (report)", "day")
|
|
41
61
|
.action((opts) => {
|
|
42
62
|
console.log(JSON.stringify(reportInsideOut(paths(cfg, opts.agent).storePath, { ...opts,
|
|
43
|
-
sessionId: opts.session,
|
|
63
|
+
sessionId: opts.session, personId: opts.person, personal: command === "export",
|
|
64
|
+
since: opts.since ? dateOption(opts.since, 0) : undefined, min: Number(opts.min), summary: command === "report" }), null, 2));
|
|
44
65
|
});
|
|
45
66
|
}
|
|
46
67
|
}, { descriptors: [{ name: "memory-emotions", description: "Inside Out emotion reviews and reports", hasSubcommands: true }] });
|
|
47
|
-
if (api.registrationMode === "cli-metadata" || !config.insideOut.enabled
|
|
68
|
+
if (api.registrationMode === "cli-metadata" || !config.insideOut.enabled)
|
|
48
69
|
return;
|
|
49
|
-
let timer, running;
|
|
70
|
+
let timer, baselineTimer, running;
|
|
71
|
+
let refreshing;
|
|
50
72
|
let lifetime = new AbortController();
|
|
51
73
|
const tick = () => {
|
|
52
74
|
if (running)
|
|
@@ -59,6 +81,8 @@ export function registerInsideOut(api, config) {
|
|
|
59
81
|
const result = await runInsideOut({ ...paths(api.config, agentId), config, signal: lifetime.signal });
|
|
60
82
|
if ("failed" in result && (result.failed || result.errors.length))
|
|
61
83
|
api.logger.warn("Inside Out: some reviews or sources failed; inspect memory-emotions export");
|
|
84
|
+
if ("baselineError" in result && result.baselineError)
|
|
85
|
+
api.logger.warn(`Inside Out baselines: ${result.baselineError}`);
|
|
62
86
|
}
|
|
63
87
|
catch (error) {
|
|
64
88
|
if (!lifetime.signal.aborted)
|
|
@@ -69,10 +93,35 @@ export function registerInsideOut(api, config) {
|
|
|
69
93
|
};
|
|
70
94
|
api.on("gateway_start", () => {
|
|
71
95
|
lifetime = new AbortController();
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
const refresh = () => {
|
|
97
|
+
if (refreshing)
|
|
98
|
+
return;
|
|
99
|
+
refreshing = (async () => {
|
|
100
|
+
for (const agentId of listAgentIds(api.config)) {
|
|
101
|
+
if (lifetime.signal.aborted)
|
|
102
|
+
break;
|
|
103
|
+
try {
|
|
104
|
+
await refreshInsideOutBaselinesAsync(paths(api.config, agentId).storePath, { minInteractions: config.insideOut.baselineMinInteractions }, lifetime.signal);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!lifetime.signal.aborted)
|
|
108
|
+
api.logger.warn(`Inside Out baselines: ${String(error)}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
})().finally(() => { refreshing = undefined; });
|
|
112
|
+
};
|
|
113
|
+
// Local statistics remain available when inference is manual-only or lacks a key.
|
|
114
|
+
baselineTimer = setInterval(refresh, 86400_000);
|
|
115
|
+
baselineTimer.unref();
|
|
116
|
+
refresh();
|
|
117
|
+
if (config.typesafe.enabled && config.insideOut.intervalMinutes) {
|
|
118
|
+
timer = setInterval(tick, config.insideOut.intervalMinutes * 60_000);
|
|
119
|
+
timer.unref();
|
|
120
|
+
tick();
|
|
121
|
+
}
|
|
75
122
|
});
|
|
123
|
+
// Aborting cancels active baseline workers rather than waiting for a full historical pass.
|
|
76
124
|
api.on("gateway_stop", async () => { if (timer)
|
|
77
|
-
clearInterval(timer);
|
|
125
|
+
clearInterval(timer); if (baselineTimer)
|
|
126
|
+
clearInterval(baselineTimer); lifetime.abort(); await Promise.all([running, refreshing]); });
|
|
78
127
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { DatabaseSync } from "node:sqlite";
|
|
2
|
+
import { refreshEmotionBaselines } from "./emotion-baselines.js";
|
|
3
|
+
export declare const PERSON_MATCH = "SELECT min(i.person_id) person_id FROM person_identities i\n WHERE i.provider=e.channel AND i.external_id=e.sender_id\n AND (i.account_scope=json_extract(e.human_key,'$[1]') OR substr(json_extract(e.human_key,'$[1]'),1,8)='session:')\n HAVING count(DISTINCT i.person_id)=1";
|
|
4
|
+
export declare function openInsideOutStore(path: string): DatabaseSync;
|
|
5
|
+
/**
|
|
6
|
+
* Refresh People links without loading transcripts or calling Jev. Known accounts stay exact.
|
|
7
|
+
* Purged grades stay unlinked. Matching is a read; only changed links are written, in short batches.
|
|
8
|
+
*/
|
|
9
|
+
export declare function linkInsideOutPeople(path: string): {
|
|
10
|
+
updated: number;
|
|
11
|
+
linked: number;
|
|
12
|
+
unlinked: number;
|
|
13
|
+
};
|
|
14
|
+
type BaselineOptions = {
|
|
15
|
+
minInteractions?: number;
|
|
16
|
+
now?: number;
|
|
17
|
+
recompare?: boolean;
|
|
18
|
+
link?: boolean;
|
|
19
|
+
};
|
|
20
|
+
type BaselineResult = ReturnType<typeof refreshEmotionBaselines>;
|
|
21
|
+
/** Rebuild local statistics from existing grades, refreshing exact People links first unless the caller just did. */
|
|
22
|
+
export declare function refreshInsideOutBaselines(path: string, options?: BaselineOptions): BaselineResult;
|
|
23
|
+
/**
|
|
24
|
+
* Worker entry, also behind the synchronous API. `unless`: the fingerprint left by this store's
|
|
25
|
+
* previous refresh; unchanged inputs skip the pass.
|
|
26
|
+
* @public loaded by name from the worker thread
|
|
27
|
+
*/
|
|
28
|
+
export declare function refreshWithFingerprint(path: string, options: BaselineOptions & {
|
|
29
|
+
unless?: string;
|
|
30
|
+
}): {
|
|
31
|
+
result: {
|
|
32
|
+
snapshots: number;
|
|
33
|
+
comparisons: number;
|
|
34
|
+
};
|
|
35
|
+
fingerprint?: undefined;
|
|
36
|
+
} | {
|
|
37
|
+
result: {
|
|
38
|
+
snapshots: number;
|
|
39
|
+
comparisons: number;
|
|
40
|
+
};
|
|
41
|
+
fingerprint: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Keep historical aggregation off the Gateway event loop; serialize refreshes per store, and skip
|
|
45
|
+
* one whose inputs have not changed since this process's previous refresh of that store.
|
|
46
|
+
* Aborting skips queued work and terminates a running worker. Its connection closes with the
|
|
47
|
+
* worker, so SQLite rolls back the open batch; committed batches are idempotent and the next refresh resumes.
|
|
48
|
+
*/
|
|
49
|
+
export declare function refreshInsideOutBaselinesAsync(path: string, options?: BaselineOptions, signal?: AbortSignal): Promise<{
|
|
50
|
+
snapshots: number;
|
|
51
|
+
comparisons: number;
|
|
52
|
+
}>;
|
|
53
|
+
/** Operator erasure for one person; see purgeEmotionProfile. */
|
|
54
|
+
export declare function purgeInsideOutPerson(path: string, personId: string, options?: {
|
|
55
|
+
grades?: boolean;
|
|
56
|
+
}): {
|
|
57
|
+
baselines: number;
|
|
58
|
+
assessments: number;
|
|
59
|
+
grades: number;
|
|
60
|
+
};
|
|
61
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { Worker } from "node:worker_threads";
|
|
3
|
+
import { openMemoryDatabase } from "./memory-database.js";
|
|
4
|
+
import { PURGED_GRADE, baselineFingerprint, purgeEmotionProfile, refreshEmotionBaselines, tableExists } from "./emotion-baselines.js";
|
|
5
|
+
// Imported by the baseline worker with plain Node resolution: host SDK aliases do not
|
|
6
|
+
// reach worker threads, so this module and its imports must stay free of `openclaw`.
|
|
7
|
+
const SCHEMA = `CREATE TABLE IF NOT EXISTS inside_out (
|
|
8
|
+
interaction_id TEXT NOT NULL, rubric TEXT NOT NULL, session_id TEXT NOT NULL, message_id TEXT NOT NULL,
|
|
9
|
+
assistant_id TEXT NOT NULL, human_key TEXT NOT NULL, sender_id TEXT NOT NULL, channel TEXT NOT NULL, source TEXT NOT NULL,
|
|
10
|
+
target_at INTEGER, reviewed_at INTEGER NOT NULL, model TEXT NOT NULL, context_trimmed INTEGER NOT NULL,
|
|
11
|
+
joy REAL, sadness REAL, fear REAL, anger REAL, disgust REAL, surprise REAL, error TEXT, person_id TEXT,
|
|
12
|
+
PRIMARY KEY(interaction_id,rubric)
|
|
13
|
+
) STRICT;
|
|
14
|
+
CREATE INDEX IF NOT EXISTS inside_out_human_time ON inside_out(human_key,target_at);
|
|
15
|
+
CREATE INDEX IF NOT EXISTS inside_out_failed_rubric ON inside_out(rubric,reviewed_at) WHERE error IS NOT NULL;
|
|
16
|
+
CREATE TABLE IF NOT EXISTS inside_out_checkpoints (
|
|
17
|
+
source TEXT NOT NULL, rubric TEXT NOT NULL, session_id TEXT NOT NULL, signature TEXT NOT NULL,
|
|
18
|
+
revision TEXT NOT NULL, last_seq INTEGER NOT NULL,
|
|
19
|
+
PRIMARY KEY(source,rubric)
|
|
20
|
+
) STRICT;`;
|
|
21
|
+
// A missing account may match across scopes only when every candidate names the same person.
|
|
22
|
+
export const PERSON_MATCH = `SELECT min(i.person_id) person_id FROM person_identities i
|
|
23
|
+
WHERE i.provider=e.channel AND i.external_id=e.sender_id
|
|
24
|
+
AND (i.account_scope=json_extract(e.human_key,'$[1]') OR substr(json_extract(e.human_key,'$[1]'),1,8)='session:')
|
|
25
|
+
HAVING count(DISTINCT i.person_id)=1`;
|
|
26
|
+
export function openInsideOutStore(path) {
|
|
27
|
+
const db = openMemoryDatabase(path);
|
|
28
|
+
try {
|
|
29
|
+
db.exec(SCHEMA);
|
|
30
|
+
if (!db.prepare("SELECT 1 FROM pragma_table_info('inside_out') WHERE name='person_id'").get()) {
|
|
31
|
+
db.exec("ALTER TABLE inside_out ADD COLUMN person_id TEXT");
|
|
32
|
+
}
|
|
33
|
+
db.exec("CREATE INDEX IF NOT EXISTS inside_out_person_time ON inside_out(person_id,target_at)");
|
|
34
|
+
return db;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
db.close();
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Refresh People links without loading transcripts or calling Jev. Known accounts stay exact.
|
|
43
|
+
* Purged grades stay unlinked. Matching is a read; only changed links are written, in short batches.
|
|
44
|
+
*/
|
|
45
|
+
export function linkInsideOutPeople(path) {
|
|
46
|
+
const db = openInsideOutStore(path);
|
|
47
|
+
try {
|
|
48
|
+
let updated = 0;
|
|
49
|
+
if (tableExists(db, "person_identities")) {
|
|
50
|
+
const person = `(${PERSON_MATCH})`;
|
|
51
|
+
const changes = db.prepare(`SELECT e.rowid id,${person} person_id FROM inside_out e
|
|
52
|
+
WHERE e.person_id IS NOT ${person} AND e.error IS NOT ?`).all(PURGED_GRADE);
|
|
53
|
+
const link = db.prepare("UPDATE inside_out SET person_id=? WHERE rowid=? AND error IS NOT ?");
|
|
54
|
+
for (let start = 0; start < changes.length; start += 500) {
|
|
55
|
+
db.exec("BEGIN IMMEDIATE");
|
|
56
|
+
try {
|
|
57
|
+
for (const change of changes.slice(start, start + 500))
|
|
58
|
+
updated += Number(link.run(change.person_id, change.id, PURGED_GRADE).changes);
|
|
59
|
+
db.exec("COMMIT");
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
db.exec("ROLLBACK");
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const counts = db.prepare(`SELECT count(person_id) linked,count(*)-count(person_id) unlinked FROM inside_out`).get();
|
|
68
|
+
return { updated, linked: Number(counts.linked), unlinked: Number(counts.unlinked) };
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Rebuild local statistics from existing grades, refreshing exact People links first unless the caller just did. */
|
|
75
|
+
export function refreshInsideOutBaselines(path, options = {}) {
|
|
76
|
+
return refreshWithFingerprint(path, options).result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Worker entry, also behind the synchronous API. `unless`: the fingerprint left by this store's
|
|
80
|
+
* previous refresh; unchanged inputs skip the pass.
|
|
81
|
+
* @public loaded by name from the worker thread
|
|
82
|
+
*/
|
|
83
|
+
export function refreshWithFingerprint(path, options) {
|
|
84
|
+
if (!existsSync(path))
|
|
85
|
+
return { result: { snapshots: 0, comparisons: 0 } };
|
|
86
|
+
if (options.link !== false)
|
|
87
|
+
linkInsideOutPeople(path);
|
|
88
|
+
const db = openInsideOutStore(path);
|
|
89
|
+
try {
|
|
90
|
+
const settings = { ...options, minInteractions: options.minInteractions ?? 20, now: options.now ?? Date.now() };
|
|
91
|
+
if (!options.recompare && options.unless && baselineFingerprint(db, settings) === options.unless) {
|
|
92
|
+
return { result: { snapshots: 0, comparisons: 0 }, fingerprint: options.unless };
|
|
93
|
+
}
|
|
94
|
+
const result = refreshEmotionBaselines(db, settings);
|
|
95
|
+
return { result, fingerprint: baselineFingerprint(db, settings) };
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
db.close();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const baselineJobs = new Map();
|
|
102
|
+
const fingerprints = new Map();
|
|
103
|
+
/**
|
|
104
|
+
* Keep historical aggregation off the Gateway event loop; serialize refreshes per store, and skip
|
|
105
|
+
* one whose inputs have not changed since this process's previous refresh of that store.
|
|
106
|
+
* Aborting skips queued work and terminates a running worker. Its connection closes with the
|
|
107
|
+
* worker, so SQLite rolls back the open batch; committed batches are idempotent and the next refresh resumes.
|
|
108
|
+
*/
|
|
109
|
+
export function refreshInsideOutBaselinesAsync(path, options = {}, signal) {
|
|
110
|
+
const job = (baselineJobs.get(path) ?? Promise.resolve()).catch(() => { }).then(() => {
|
|
111
|
+
signal?.throwIfAborted();
|
|
112
|
+
if (!existsSync(path))
|
|
113
|
+
return { snapshots: 0, comparisons: 0 };
|
|
114
|
+
const settings = { ...options, now: options.now ?? Date.now(), unless: fingerprints.get(path) };
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const worker = new Worker(`
|
|
117
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
118
|
+
// tsx's CLI preload only registers on the main thread. Source-mode
|
|
119
|
+
// development needs explicit registration; compiled installs do not.
|
|
120
|
+
if (workerData.module.endsWith(".ts")) require("node:module").createRequire(workerData.module)("tsx/esm/api").register();
|
|
121
|
+
import(workerData.module).then(({ refreshWithFingerprint }) => {
|
|
122
|
+
parentPort.postMessage(refreshWithFingerprint(workerData.path, workerData.options));
|
|
123
|
+
}).catch(error => { throw error; });
|
|
124
|
+
`, { eval: true, workerData: { module: import.meta.url, path, options: settings } });
|
|
125
|
+
let outcome, aborted = false;
|
|
126
|
+
const abort = () => { aborted = true; void worker.terminate(); };
|
|
127
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
128
|
+
worker.once("message", (value) => { outcome = value; });
|
|
129
|
+
worker.once("error", reject);
|
|
130
|
+
// Settle only after the worker closes its database and exits.
|
|
131
|
+
worker.once("exit", code => {
|
|
132
|
+
signal?.removeEventListener("abort", abort);
|
|
133
|
+
if (aborted)
|
|
134
|
+
reject(signal.reason);
|
|
135
|
+
else if (code === 0 && outcome) {
|
|
136
|
+
if (outcome.fingerprint)
|
|
137
|
+
fingerprints.set(path, outcome.fingerprint);
|
|
138
|
+
resolve(outcome.result);
|
|
139
|
+
}
|
|
140
|
+
else
|
|
141
|
+
reject(new Error(`Inside Out baseline worker exited without a result (code ${code})`));
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}).finally(() => { if (baselineJobs.get(path) === job)
|
|
145
|
+
baselineJobs.delete(path); });
|
|
146
|
+
baselineJobs.set(path, job);
|
|
147
|
+
return job;
|
|
148
|
+
}
|
|
149
|
+
/** Operator erasure for one person; see purgeEmotionProfile. */
|
|
150
|
+
export function purgeInsideOutPerson(path, personId, options = {}) {
|
|
151
|
+
if (!personId.trim())
|
|
152
|
+
throw new Error("A PeopleSQL person ID is required");
|
|
153
|
+
if (!existsSync(path))
|
|
154
|
+
return { baselines: 0, assessments: 0, grades: 0 };
|
|
155
|
+
// Never create Inside Out tables in a store that has none.
|
|
156
|
+
const probe = openMemoryDatabase(path);
|
|
157
|
+
try {
|
|
158
|
+
if (!tableExists(probe, "inside_out"))
|
|
159
|
+
return { baselines: 0, assessments: 0, grades: 0 };
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
probe.close();
|
|
163
|
+
}
|
|
164
|
+
// Erasing grades must cover replies linked to this person since the last refresh.
|
|
165
|
+
if (options.grades)
|
|
166
|
+
linkInsideOutPeople(path);
|
|
167
|
+
const db = openInsideOutStore(path);
|
|
168
|
+
try {
|
|
169
|
+
return purgeEmotionProfile(db, personId, options);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
db.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
package/dist/src/inside-out.d.ts
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
import type { UnblockMemoryConfig } from "./config.js";
|
|
2
2
|
import { readInsideOutSources } from "./inside-out-sources.js";
|
|
3
|
+
import { emotionBaselineHistory } from "./emotion-baselines.js";
|
|
4
|
+
export { linkInsideOutPeople, purgeInsideOutPerson, refreshInsideOutBaselines, refreshInsideOutBaselinesAsync } from "./inside-out-store.js";
|
|
3
5
|
export type InsideOutConfig = {
|
|
4
6
|
enabled: boolean;
|
|
5
7
|
intervalMinutes: number;
|
|
6
8
|
maxInteractions: number;
|
|
7
9
|
maxContextTokens: number;
|
|
10
|
+
baselineMinInteractions: number;
|
|
8
11
|
};
|
|
9
12
|
export declare function resolveInsideOut(value: unknown): InsideOutConfig;
|
|
10
|
-
/** Refresh People links without loading transcripts or calling Jev. Known accounts stay exact. */
|
|
11
|
-
export declare function linkInsideOutPeople(path: string): {
|
|
12
|
-
updated: number;
|
|
13
|
-
linked: number;
|
|
14
|
-
unlinked: number;
|
|
15
|
-
};
|
|
16
13
|
/** Explicit legacy repair: only read sources owning unlinked reviews; never rejudge their text. */
|
|
17
14
|
export declare function repairInsideOutIdentities(options: Parameters<typeof readInsideOutSources>[0] & {
|
|
18
15
|
storePath: string;
|
|
@@ -36,9 +33,144 @@ export declare function runInsideOut(options: {
|
|
|
36
33
|
sources: number;
|
|
37
34
|
skippedSources: number;
|
|
38
35
|
errors: string[];
|
|
36
|
+
baselineError?: string;
|
|
39
37
|
} | {
|
|
40
38
|
status: string;
|
|
41
39
|
}>;
|
|
40
|
+
export declare function reportInsideOutBaselines(path: string, options?: Parameters<typeof emotionBaselineHistory>[1]): {
|
|
41
|
+
id: string;
|
|
42
|
+
person_id: string;
|
|
43
|
+
rubric: string;
|
|
44
|
+
model: string;
|
|
45
|
+
version: string;
|
|
46
|
+
week_start: number;
|
|
47
|
+
week_end: number;
|
|
48
|
+
computed_at: number;
|
|
49
|
+
weekly: {
|
|
50
|
+
joy: {
|
|
51
|
+
count: number;
|
|
52
|
+
mean: number | null;
|
|
53
|
+
min: number | null;
|
|
54
|
+
p10: number | null;
|
|
55
|
+
median: number | null;
|
|
56
|
+
p90: number | null;
|
|
57
|
+
max: number | null;
|
|
58
|
+
distribution: [number, number][];
|
|
59
|
+
};
|
|
60
|
+
sadness: {
|
|
61
|
+
count: number;
|
|
62
|
+
mean: number | null;
|
|
63
|
+
min: number | null;
|
|
64
|
+
p10: number | null;
|
|
65
|
+
median: number | null;
|
|
66
|
+
p90: number | null;
|
|
67
|
+
max: number | null;
|
|
68
|
+
distribution: [number, number][];
|
|
69
|
+
};
|
|
70
|
+
fear: {
|
|
71
|
+
count: number;
|
|
72
|
+
mean: number | null;
|
|
73
|
+
min: number | null;
|
|
74
|
+
p10: number | null;
|
|
75
|
+
median: number | null;
|
|
76
|
+
p90: number | null;
|
|
77
|
+
max: number | null;
|
|
78
|
+
distribution: [number, number][];
|
|
79
|
+
};
|
|
80
|
+
anger: {
|
|
81
|
+
count: number;
|
|
82
|
+
mean: number | null;
|
|
83
|
+
min: number | null;
|
|
84
|
+
p10: number | null;
|
|
85
|
+
median: number | null;
|
|
86
|
+
p90: number | null;
|
|
87
|
+
max: number | null;
|
|
88
|
+
distribution: [number, number][];
|
|
89
|
+
};
|
|
90
|
+
disgust: {
|
|
91
|
+
count: number;
|
|
92
|
+
mean: number | null;
|
|
93
|
+
min: number | null;
|
|
94
|
+
p10: number | null;
|
|
95
|
+
median: number | null;
|
|
96
|
+
p90: number | null;
|
|
97
|
+
max: number | null;
|
|
98
|
+
distribution: [number, number][];
|
|
99
|
+
};
|
|
100
|
+
surprise: {
|
|
101
|
+
count: number;
|
|
102
|
+
mean: number | null;
|
|
103
|
+
min: number | null;
|
|
104
|
+
p10: number | null;
|
|
105
|
+
median: number | null;
|
|
106
|
+
p90: number | null;
|
|
107
|
+
max: number | null;
|
|
108
|
+
distribution: [number, number][];
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
cumulative: {
|
|
112
|
+
joy: {
|
|
113
|
+
count: number;
|
|
114
|
+
mean: number | null;
|
|
115
|
+
min: number | null;
|
|
116
|
+
p10: number | null;
|
|
117
|
+
median: number | null;
|
|
118
|
+
p90: number | null;
|
|
119
|
+
max: number | null;
|
|
120
|
+
distribution: [number, number][];
|
|
121
|
+
};
|
|
122
|
+
sadness: {
|
|
123
|
+
count: number;
|
|
124
|
+
mean: number | null;
|
|
125
|
+
min: number | null;
|
|
126
|
+
p10: number | null;
|
|
127
|
+
median: number | null;
|
|
128
|
+
p90: number | null;
|
|
129
|
+
max: number | null;
|
|
130
|
+
distribution: [number, number][];
|
|
131
|
+
};
|
|
132
|
+
fear: {
|
|
133
|
+
count: number;
|
|
134
|
+
mean: number | null;
|
|
135
|
+
min: number | null;
|
|
136
|
+
p10: number | null;
|
|
137
|
+
median: number | null;
|
|
138
|
+
p90: number | null;
|
|
139
|
+
max: number | null;
|
|
140
|
+
distribution: [number, number][];
|
|
141
|
+
};
|
|
142
|
+
anger: {
|
|
143
|
+
count: number;
|
|
144
|
+
mean: number | null;
|
|
145
|
+
min: number | null;
|
|
146
|
+
p10: number | null;
|
|
147
|
+
median: number | null;
|
|
148
|
+
p90: number | null;
|
|
149
|
+
max: number | null;
|
|
150
|
+
distribution: [number, number][];
|
|
151
|
+
};
|
|
152
|
+
disgust: {
|
|
153
|
+
count: number;
|
|
154
|
+
mean: number | null;
|
|
155
|
+
min: number | null;
|
|
156
|
+
p10: number | null;
|
|
157
|
+
median: number | null;
|
|
158
|
+
p90: number | null;
|
|
159
|
+
max: number | null;
|
|
160
|
+
distribution: [number, number][];
|
|
161
|
+
};
|
|
162
|
+
surprise: {
|
|
163
|
+
count: number;
|
|
164
|
+
mean: number | null;
|
|
165
|
+
min: number | null;
|
|
166
|
+
p10: number | null;
|
|
167
|
+
median: number | null;
|
|
168
|
+
p90: number | null;
|
|
169
|
+
max: number | null;
|
|
170
|
+
distribution: [number, number][];
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
}[];
|
|
42
174
|
export declare function reportInsideOut(path: string, options?: {
|
|
43
175
|
summary?: boolean;
|
|
44
176
|
sender?: string;
|
|
@@ -47,4 +179,7 @@ export declare function reportInsideOut(path: string, options?: {
|
|
|
47
179
|
emotion?: string;
|
|
48
180
|
min?: number;
|
|
49
181
|
bucket?: string;
|
|
50
|
-
|
|
182
|
+
personId?: string;
|
|
183
|
+
personal?: boolean;
|
|
184
|
+
deviationHistory?: boolean;
|
|
185
|
+
}): Record<string, unknown>[];
|