niahere 0.2.46 → 0.2.48
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/package.json +1 -1
- package/src/chat/engine.ts +27 -7
- package/src/core/consolidator.ts +5 -12
- package/src/core/runner.ts +50 -6
- package/src/core/summarizer.ts +3 -6
- package/src/db/migrations/010_message_metadata.ts +10 -0
- package/src/db/migrations/011_session_metadata.ts +7 -0
- package/src/db/models/message.ts +4 -3
- package/src/db/models/session.ts +38 -0
package/package.json
CHANGED
package/src/chat/engine.ts
CHANGED
|
@@ -332,22 +332,42 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
|
|
|
332
332
|
}
|
|
333
333
|
|
|
334
334
|
if (message.type === "result" && pending) {
|
|
335
|
+
const msg = message as any;
|
|
335
336
|
if (!message.is_error) {
|
|
336
|
-
const resultText =
|
|
337
|
-
const costUsd =
|
|
338
|
-
const turns =
|
|
337
|
+
const resultText = msg.result as string;
|
|
338
|
+
const costUsd = msg.total_cost_usd as number;
|
|
339
|
+
const turns = msg.num_turns as number;
|
|
340
|
+
|
|
341
|
+
const metadata: Record<string, unknown> = {
|
|
342
|
+
cost_usd: costUsd,
|
|
343
|
+
turns,
|
|
344
|
+
duration_ms: msg.duration_ms,
|
|
345
|
+
duration_api_ms: msg.duration_api_ms,
|
|
346
|
+
stop_reason: msg.stop_reason,
|
|
347
|
+
session_id: msg.session_id,
|
|
348
|
+
subtype: msg.subtype,
|
|
349
|
+
usage: msg.usage,
|
|
350
|
+
model_usage: msg.modelUsage,
|
|
351
|
+
};
|
|
339
352
|
|
|
340
353
|
let messageId: number | undefined;
|
|
341
354
|
if (sessionId && resultText) {
|
|
342
|
-
|
|
355
|
+
const saveParams = {
|
|
343
356
|
sessionId,
|
|
344
357
|
room,
|
|
345
358
|
sender: "nia",
|
|
346
359
|
content: resultText,
|
|
347
360
|
isFromAgent: true,
|
|
348
|
-
deliveryStatus: "pending",
|
|
349
|
-
|
|
361
|
+
deliveryStatus: "pending" as const,
|
|
362
|
+
metadata,
|
|
363
|
+
};
|
|
364
|
+
try {
|
|
365
|
+
messageId = await Message.save(saveParams);
|
|
366
|
+
} catch {
|
|
367
|
+
messageId = await Message.save({ ...saveParams, metadata: undefined });
|
|
368
|
+
}
|
|
350
369
|
await Session.touch(sessionId);
|
|
370
|
+
await Session.accumulateMetadata(sessionId, { ...metadata, channel }).catch(() => {});
|
|
351
371
|
}
|
|
352
372
|
|
|
353
373
|
await ActiveEngine.unregister(room);
|
|
@@ -356,7 +376,7 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
|
|
|
356
376
|
pending = null;
|
|
357
377
|
resetIdleTimer();
|
|
358
378
|
} else {
|
|
359
|
-
const errors =
|
|
379
|
+
const errors = msg.errors;
|
|
360
380
|
const errorText = `[error] ${errors?.join(", ") || "unknown error"}`;
|
|
361
381
|
await ActiveEngine.unregister(room);
|
|
362
382
|
clearLongRunningTimer();
|
package/src/core/consolidator.ts
CHANGED
|
@@ -18,10 +18,8 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { Message } from "../db/models";
|
|
21
|
-
import {
|
|
22
|
-
import { runJobWithClaude } from "./runner";
|
|
21
|
+
import { runTask } from "./runner";
|
|
23
22
|
import { log } from "../utils/log";
|
|
24
|
-
import { homedir } from "os";
|
|
25
23
|
import type { SessionMessage } from "../types";
|
|
26
24
|
|
|
27
25
|
/** Track sessions already consolidated to prevent double runs. */
|
|
@@ -83,15 +81,10 @@ Do NOT message the user about this. Save silently and report a brief summary of
|
|
|
83
81
|
|
|
84
82
|
/** Run the consolidation agent loop. */
|
|
85
83
|
async function runConsolidation(transcript: string, source: string): Promise<void> {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (output.error) {
|
|
91
|
-
log.error({ source, error: output.error }, "consolidator: extraction failed");
|
|
92
|
-
} else {
|
|
93
|
-
log.info({ source, resultChars: output.agentText.length }, "consolidator: done");
|
|
94
|
-
}
|
|
84
|
+
await runTask({
|
|
85
|
+
name: "consolidator",
|
|
86
|
+
prompt: buildConsolidationPrompt(transcript, source),
|
|
87
|
+
});
|
|
95
88
|
}
|
|
96
89
|
|
|
97
90
|
/**
|
package/src/core/runner.ts
CHANGED
|
@@ -9,6 +9,9 @@ import { getConfig } from "../utils/config";
|
|
|
9
9
|
import { buildSystemPrompt } from "../chat/identity";
|
|
10
10
|
import { scanAgents } from "./agents";
|
|
11
11
|
import { truncate, formatToolUse } from "../utils/format-activity";
|
|
12
|
+
import { getMcpServers } from "../mcp";
|
|
13
|
+
import { ActiveEngine } from "../db/models";
|
|
14
|
+
import { log } from "../utils/log";
|
|
12
15
|
|
|
13
16
|
export type ActivityCallback = (line: string) => void;
|
|
14
17
|
|
|
@@ -87,14 +90,21 @@ export async function runJobWithClaude(
|
|
|
87
90
|
};
|
|
88
91
|
}
|
|
89
92
|
|
|
93
|
+
const options: Record<string, unknown> = {
|
|
94
|
+
systemPrompt,
|
|
95
|
+
cwd,
|
|
96
|
+
permissionMode: "bypassPermissions",
|
|
97
|
+
sessionId,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mcpServers = getMcpServers();
|
|
101
|
+
if (mcpServers) {
|
|
102
|
+
options.mcpServers = mcpServers;
|
|
103
|
+
}
|
|
104
|
+
|
|
90
105
|
const handle = query({
|
|
91
106
|
prompt: singleMessage() as any,
|
|
92
|
-
options:
|
|
93
|
-
systemPrompt,
|
|
94
|
-
cwd,
|
|
95
|
-
permissionMode: "bypassPermissions",
|
|
96
|
-
sessionId,
|
|
97
|
-
} as any,
|
|
107
|
+
options: options as any,
|
|
98
108
|
});
|
|
99
109
|
|
|
100
110
|
let agentText = "";
|
|
@@ -178,6 +188,40 @@ export async function runJobWithClaude(
|
|
|
178
188
|
return { agentText, sessionId: actualSessionId };
|
|
179
189
|
}
|
|
180
190
|
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Background task runner — tracked one-shot agent with full Nia personality
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
export interface TaskOptions {
|
|
196
|
+
/** Task name — used for ActiveEngine tracking as _system/{name}. */
|
|
197
|
+
name: string;
|
|
198
|
+
/** The prompt/instruction for the task. */
|
|
199
|
+
prompt: string;
|
|
200
|
+
/** System prompt override. Defaults to buildSystemPrompt("job"). */
|
|
201
|
+
systemPrompt?: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Run a background agent task with ActiveEngine tracking and MCP tools.
|
|
206
|
+
* Use for consolidator, summarizer, and any future background work.
|
|
207
|
+
*/
|
|
208
|
+
export async function runTask(opts: TaskOptions): Promise<RunnerOutput> {
|
|
209
|
+
const room = `_system/${opts.name}`;
|
|
210
|
+
await ActiveEngine.register(room, "system").catch(() => {});
|
|
211
|
+
try {
|
|
212
|
+
const systemPrompt = opts.systemPrompt || buildSystemPrompt("job");
|
|
213
|
+
const output = await runJobWithClaude(systemPrompt, opts.prompt, homedir());
|
|
214
|
+
if (output.error) {
|
|
215
|
+
log.error({ task: opts.name, error: output.error }, "task failed");
|
|
216
|
+
} else {
|
|
217
|
+
log.info({ task: opts.name, resultChars: output.agentText.length }, "task completed");
|
|
218
|
+
}
|
|
219
|
+
return output;
|
|
220
|
+
} finally {
|
|
221
|
+
await ActiveEngine.unregister(room).catch(() => {});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
181
225
|
// ---------------------------------------------------------------------------
|
|
182
226
|
// Public API
|
|
183
227
|
// ---------------------------------------------------------------------------
|
package/src/core/summarizer.ts
CHANGED
|
@@ -11,10 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { Message, Session } from "../db/models";
|
|
14
|
-
import {
|
|
15
|
-
import { runJobWithClaude } from "./runner";
|
|
14
|
+
import { runTask } from "./runner";
|
|
16
15
|
import { log } from "../utils/log";
|
|
17
|
-
import { homedir } from "os";
|
|
18
16
|
import type { SessionMessage } from "../types";
|
|
19
17
|
|
|
20
18
|
/** Track sessions already summarized to prevent double runs. */
|
|
@@ -46,10 +44,9 @@ export async function summarizeSession(sessionId: string, room: string): Promise
|
|
|
46
44
|
|
|
47
45
|
log.info({ sessionId, room, messageCount: messages.length }, "summarizer: generating session summary");
|
|
48
46
|
|
|
49
|
-
const systemPrompt = buildSystemPrompt("job");
|
|
50
47
|
const transcript = formatTranscript(messages);
|
|
51
48
|
|
|
52
|
-
const
|
|
49
|
+
const prompt = `Job: session-summary (triggered by session idle in ${room})
|
|
53
50
|
|
|
54
51
|
Generate a brief session summary. This will be shown to your future self at the start of the next session for continuity.
|
|
55
52
|
|
|
@@ -64,7 +61,7 @@ Write a 2-4 sentence summary covering:
|
|
|
64
61
|
|
|
65
62
|
Keep it concise — a handoff note, not a report. Output ONLY the summary text.`;
|
|
66
63
|
|
|
67
|
-
const output = await
|
|
64
|
+
const output = await runTask({ name: "summarizer", prompt });
|
|
68
65
|
|
|
69
66
|
if (output.error) {
|
|
70
67
|
log.error({ sessionId, room, error: output.error }, "summarizer: failed");
|
package/src/db/models/message.ts
CHANGED
|
@@ -3,12 +3,13 @@ import type { SaveMessageParams, RoomStats, RecentMessage, SearchResult, Session
|
|
|
3
3
|
|
|
4
4
|
export type DeliveryStatus = "pending" | "sent" | "failed";
|
|
5
5
|
|
|
6
|
-
export async function save(params: SaveMessageParams & { deliveryStatus?: DeliveryStatus }): Promise<number> {
|
|
6
|
+
export async function save(params: SaveMessageParams & { deliveryStatus?: DeliveryStatus; metadata?: Record<string, unknown> }): Promise<number> {
|
|
7
7
|
const sql = getSql();
|
|
8
8
|
const status = params.deliveryStatus || "sent";
|
|
9
|
+
const meta = params.metadata ? JSON.stringify(params.metadata) : null;
|
|
9
10
|
const rows = await sql`
|
|
10
|
-
INSERT INTO messages (session_id, room, sender, content, is_from_agent, delivery_status)
|
|
11
|
-
VALUES (${params.sessionId}, ${params.room}, ${params.sender}, ${params.content}, ${params.isFromAgent}, ${status})
|
|
11
|
+
INSERT INTO messages (session_id, room, sender, content, is_from_agent, delivery_status, metadata)
|
|
12
|
+
VALUES (${params.sessionId}, ${params.room}, ${params.sender}, ${params.content}, ${params.isFromAgent}, ${status}, ${meta})
|
|
12
13
|
RETURNING id
|
|
13
14
|
`;
|
|
14
15
|
return rows[0].id;
|
package/src/db/models/session.ts
CHANGED
|
@@ -113,6 +113,44 @@ export async function getRecentSummaries(room: string, limit = 3): Promise<Array
|
|
|
113
113
|
}));
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
export async function accumulateMetadata(id: string, resultMeta: Record<string, unknown>): Promise<void> {
|
|
117
|
+
const sql = getSql();
|
|
118
|
+
const rows = await sql`SELECT metadata FROM sessions WHERE id = ${id}`;
|
|
119
|
+
const existing = (rows[0]?.metadata as Record<string, unknown>) || {};
|
|
120
|
+
|
|
121
|
+
const modelUsage = resultMeta.model_usage as Record<string, Record<string, number>> | undefined;
|
|
122
|
+
const modelsUsed = new Set<string>((existing.models_used as string[]) || []);
|
|
123
|
+
let inputTokens = 0;
|
|
124
|
+
let outputTokens = 0;
|
|
125
|
+
let cacheReadTokens = 0;
|
|
126
|
+
let cacheCreationTokens = 0;
|
|
127
|
+
if (modelUsage) {
|
|
128
|
+
for (const [model, usage] of Object.entries(modelUsage)) {
|
|
129
|
+
modelsUsed.add(model);
|
|
130
|
+
inputTokens += usage.inputTokens || 0;
|
|
131
|
+
outputTokens += usage.outputTokens || 0;
|
|
132
|
+
cacheReadTokens += usage.cacheReadInputTokens || 0;
|
|
133
|
+
cacheCreationTokens += usage.cacheCreationInputTokens || 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const updated: Record<string, unknown> = {
|
|
138
|
+
total_cost_usd: ((existing.total_cost_usd as number) || 0) + ((resultMeta.cost_usd as number) || 0),
|
|
139
|
+
total_turns: ((existing.total_turns as number) || 0) + ((resultMeta.turns as number) || 0),
|
|
140
|
+
total_duration_ms: ((existing.total_duration_ms as number) || 0) + ((resultMeta.duration_ms as number) || 0),
|
|
141
|
+
total_duration_api_ms: ((existing.total_duration_api_ms as number) || 0) + ((resultMeta.duration_api_ms as number) || 0),
|
|
142
|
+
total_input_tokens: ((existing.total_input_tokens as number) || 0) + inputTokens,
|
|
143
|
+
total_output_tokens: ((existing.total_output_tokens as number) || 0) + outputTokens,
|
|
144
|
+
total_cache_read_tokens: ((existing.total_cache_read_tokens as number) || 0) + cacheReadTokens,
|
|
145
|
+
total_cache_creation_tokens: ((existing.total_cache_creation_tokens as number) || 0) + cacheCreationTokens,
|
|
146
|
+
message_count: ((existing.message_count as number) || 0) + 1,
|
|
147
|
+
models_used: [...modelsUsed],
|
|
148
|
+
channel: existing.channel || resultMeta.channel,
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
await sql`UPDATE sessions SET metadata = ${JSON.stringify(updated)} WHERE id = ${id}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
116
154
|
export async function getLatestRoomIndex(prefix: string): Promise<number> {
|
|
117
155
|
const sql = getSql();
|
|
118
156
|
const rows = await sql`
|