niahere 0.2.83 → 0.2.84

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 CHANGED
@@ -43,8 +43,8 @@ nia start # starts daemon + registers OS service
43
43
  - **Skills** — loads skills from multiple directories, invokable as slash commands
44
44
  - **Cross-platform service** — launchd (macOS), systemd (Linux), service-aware restart
45
45
  - **MCP tools** — 20 tools for job management, messaging, memory, rules, and channel control
46
- - **Background memory consolidation** — extracts memories from conversations and job runs automatically
47
- - **Session summaries** — handoff notes between sessions for continuity
46
+ - **Background memory consolidation** — stages memory candidates from conversations automatically
47
+ - **Session summaries** — optional handoff notes between sessions for continuity
48
48
  - **Backups** — `nia backup` with auto-backup before updates
49
49
  - **Optional integrations** — add Gmail, Discord, and more via skills
50
50
 
@@ -119,6 +119,17 @@ All config and data lives in `~/.niahere/`:
119
119
  nia.pid, daemon.log, cron-state.json, cron-audit.jsonl
120
120
  ```
121
121
 
122
+ Post-session background LLM work can be disabled in `config.yaml`:
123
+
124
+ ```yaml
125
+ session_finalization:
126
+ enabled: true
127
+ memory_consolidation: true
128
+ summaries: true
129
+ ```
130
+
131
+ Use `nia config set session_finalization.memory_consolidation false` to stop memory staging, `nia config set session_finalization.summaries false` to stop session summaries, or `nia config set session_finalization.enabled false` to disable both.
132
+
122
133
  ## Contributing
123
134
 
124
135
  **Don't add features. Add skills.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.83",
3
+ "version": "0.2.84",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -85,6 +85,29 @@ export function validateConfig(): Result {
85
85
  messages.push(`${PASS} runner: ${runner}`);
86
86
  }
87
87
 
88
+ // Session finalization
89
+ const sf = raw.session_finalization as Record<string, unknown> | undefined;
90
+ if (sf) {
91
+ let sessionFinalizationOk = true;
92
+ for (const key of ["enabled", "memory_consolidation", "summaries"]) {
93
+ const val = sf[key];
94
+ if (val !== undefined && typeof val !== "boolean") {
95
+ messages.push(`${FAIL} session_finalization.${key} must be true or false`);
96
+ ok = false;
97
+ sessionFinalizationOk = false;
98
+ }
99
+ }
100
+ if (sessionFinalizationOk) {
101
+ const enabled = sf.enabled !== false;
102
+ const memoryConsolidation = sf.memory_consolidation !== false;
103
+ const summaries = sf.summaries !== false;
104
+ messages.push(
105
+ `${PASS} session_finalization: ${enabled ? "enabled" : "disabled"} ` +
106
+ `(memory_consolidation=${memoryConsolidation}, summaries=${summaries})`,
107
+ );
108
+ }
109
+ }
110
+
88
111
  // Channels
89
112
  const ch = raw.channels as Record<string, unknown> | undefined;
90
113
  if (ch) {
@@ -10,10 +10,25 @@
10
10
  import { getSql } from "../db/connection";
11
11
  import { consolidateSession } from "./consolidator";
12
12
  import { summarizeSession } from "./summarizer";
13
+ import { loadConfig } from "../utils/config";
13
14
  import { log } from "../utils/log";
14
15
 
16
+ type FinalizationTask = "consolidate" | "summarize";
17
+
18
+ function getEnabledTasks(): FinalizationTask[] {
19
+ const { sessionFinalization } = loadConfig();
20
+ if (!sessionFinalization.enabled) return [];
21
+
22
+ const tasks: FinalizationTask[] = [];
23
+ if (sessionFinalization.memoryConsolidation) tasks.push("consolidate");
24
+ if (sessionFinalization.summaries) tasks.push("summarize");
25
+ return tasks;
26
+ }
27
+
15
28
  /** Enqueue a session for finalization. Always returns immediately. */
16
29
  export async function finalizeSession(sessionId: string, room: string): Promise<void> {
30
+ if (getEnabledTasks().length === 0) return;
31
+
17
32
  const sql = getSql();
18
33
 
19
34
  // Get current message count for idempotency
@@ -77,19 +92,30 @@ async function processOne(sessionId: string, room: string, messageCount: number)
77
92
  if (claimed.length === 0) return; // Already claimed or cancelled
78
93
 
79
94
  const requestId = claimed[0].id;
95
+ const tasks = getEnabledTasks();
96
+ if (tasks.length === 0) {
97
+ await sql`
98
+ UPDATE finalization_requests
99
+ SET status = 'done', updated_at = NOW()
100
+ WHERE id = ${requestId}
101
+ `;
102
+ log.info({ sessionId, room, messageCount }, "finalizer: skipped because all tasks are disabled");
103
+ return;
104
+ }
80
105
 
81
106
  try {
82
- const [consolidateResult, summarizeResult] = await Promise.allSettled([
83
- consolidateSession(sessionId, room),
84
- summarizeSession(sessionId, room),
85
- ]);
107
+ const results = await Promise.allSettled(
108
+ tasks.map((task) =>
109
+ task === "consolidate" ? consolidateSession(sessionId, room) : summarizeSession(sessionId, room),
110
+ ),
111
+ );
86
112
 
87
113
  const errors: string[] = [];
88
- if (consolidateResult.status === "rejected") {
89
- errors.push(`consolidate: ${formatRejection(consolidateResult.reason)}`);
90
- }
91
- if (summarizeResult.status === "rejected") {
92
- errors.push(`summarize: ${formatRejection(summarizeResult.reason)}`);
114
+ for (let i = 0; i < results.length; i++) {
115
+ const result = results[i];
116
+ if (result.status === "rejected") {
117
+ errors.push(`${tasks[i]}: ${formatRejection(result.reason)}`);
118
+ }
93
119
  }
94
120
 
95
121
  const finalStatus = errors.length === 0 ? "done" : "failed";
@@ -35,6 +35,12 @@ export interface ChannelsConfig {
35
35
  slack: SlackConfig;
36
36
  }
37
37
 
38
+ export interface SessionFinalizationConfig {
39
+ enabled: boolean;
40
+ memoryConsolidation: boolean;
41
+ summaries: boolean;
42
+ }
43
+
38
44
  export interface Config {
39
45
  model: string;
40
46
  runner: "claude" | "codex";
@@ -43,5 +49,6 @@ export interface Config {
43
49
  database_url: string;
44
50
  log_level: string;
45
51
  gemini_api_key: string | null;
52
+ sessionFinalization: SessionFinalizationConfig;
46
53
  channels: ChannelsConfig;
47
54
  }
@@ -16,6 +16,11 @@ const DEFAULTS: Config = {
16
16
  database_url: DEFAULT_DATABASE_URL,
17
17
  log_level: "info",
18
18
  gemini_api_key: null,
19
+ sessionFinalization: {
20
+ enabled: true,
21
+ memoryConsolidation: true,
22
+ summaries: true,
23
+ },
19
24
  channels: {
20
25
  enabled: true,
21
26
  default: "telegram",
@@ -100,6 +105,14 @@ export function loadConfig(): Config {
100
105
  const gemini_api_key =
101
106
  process.env.GEMINI_API_KEY || (typeof raw.gemini_api_key === "string" ? raw.gemini_api_key : null);
102
107
 
108
+ // Session finalization — controls post-session background LLM work.
109
+ const sf = (raw.session_finalization || {}) as Record<string, unknown>;
110
+ const sessionFinalization = {
111
+ enabled: sf.enabled !== false,
112
+ memoryConsolidation: sf.memory_consolidation !== false,
113
+ summaries: sf.summaries !== false,
114
+ };
115
+
103
116
  // --- Channels (nested under `channels:` in yaml) ---
104
117
  const ch = (raw.channels || {}) as Record<string, unknown>;
105
118
  const chTg = (ch.telegram || {}) as Record<string, unknown>;
@@ -159,6 +172,7 @@ export function loadConfig(): Config {
159
172
  database_url,
160
173
  log_level,
161
174
  gemini_api_key,
175
+ sessionFinalization,
162
176
  channels: {
163
177
  enabled: channelsEnabled,
164
178
  default: defaultChannel,