niahere 0.2.47 → 0.2.49

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "description": "A personal AI assistant daemon — scheduled jobs, chat across Telegram and Slack, persona system, and visual identity.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -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 = (message as any).result as string;
337
- const costUsd = (message as any).total_cost_usd as number;
338
- const turns = (message as any).num_turns as number;
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
- messageId = await Message.save({
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
+ 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 = (message as any).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/cli/index.ts CHANGED
@@ -276,11 +276,11 @@ switch (command) {
276
276
 
277
277
  case "chat": {
278
278
  const chatArgs = process.argv.slice(3);
279
- const mode = (chatArgs.includes("--new") || chatArgs.includes("-n"))
280
- ? "new" as const
279
+ const mode = (chatArgs.includes("--continue") || chatArgs.includes("-c"))
280
+ ? "continue" as const
281
281
  : (chatArgs.includes("--resume") || chatArgs.includes("-r"))
282
282
  ? "pick" as const
283
- : "continue" as const;
283
+ : "new" as const;
284
284
  const chIdx = chatArgs.indexOf("--channel");
285
285
  const simChannel = chIdx !== -1 && chatArgs[chIdx + 1] ? chatArgs[chIdx + 1] : undefined;
286
286
  await startRepl(mode, simChannel);
@@ -0,0 +1,10 @@
1
+ import type postgres from "postgres";
2
+
3
+ export const name = "010_message_metadata";
4
+
5
+ export async function up(sql: postgres.Sql): Promise<void> {
6
+ await sql`
7
+ ALTER TABLE messages
8
+ ADD COLUMN IF NOT EXISTS metadata JSONB
9
+ `;
10
+ }
@@ -0,0 +1,7 @@
1
+ import type postgres from "postgres";
2
+
3
+ export const name = "011_session_metadata";
4
+
5
+ export async function up(sql: postgres.Sql): Promise<void> {
6
+ await sql`ALTER TABLE sessions ADD COLUMN IF NOT EXISTS metadata JSONB`;
7
+ }
@@ -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;
@@ -113,6 +113,58 @@ 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
+
119
+ const modelUsage = resultMeta.model_usage as Record<string, Record<string, number>> | undefined;
120
+ let inputTokens = 0;
121
+ let outputTokens = 0;
122
+ let cacheReadTokens = 0;
123
+ let cacheCreationTokens = 0;
124
+ const newModels: string[] = [];
125
+ if (modelUsage) {
126
+ for (const [model, usage] of Object.entries(modelUsage)) {
127
+ newModels.push(model);
128
+ inputTokens += usage.inputTokens || 0;
129
+ outputTokens += usage.outputTokens || 0;
130
+ cacheReadTokens += usage.cacheReadInputTokens || 0;
131
+ cacheCreationTokens += usage.cacheCreationInputTokens || 0;
132
+ }
133
+ }
134
+
135
+ const delta = JSON.stringify({
136
+ total_cost_usd: (resultMeta.cost_usd as number) || 0,
137
+ total_turns: (resultMeta.turns as number) || 0,
138
+ total_duration_ms: (resultMeta.duration_ms as number) || 0,
139
+ total_duration_api_ms: (resultMeta.duration_api_ms as number) || 0,
140
+ total_input_tokens: inputTokens,
141
+ total_output_tokens: outputTokens,
142
+ total_cache_read_tokens: cacheReadTokens,
143
+ total_cache_creation_tokens: cacheCreationTokens,
144
+ message_count: 1,
145
+ models_used: newModels,
146
+ channel: resultMeta.channel || null,
147
+ });
148
+
149
+ // Atomic accumulate — no read-then-write race
150
+ await sql`
151
+ UPDATE sessions SET metadata = jsonb_build_object(
152
+ 'total_cost_usd', COALESCE((metadata->>'total_cost_usd')::real, 0) + (${delta}::jsonb->>'total_cost_usd')::real,
153
+ 'total_turns', COALESCE((metadata->>'total_turns')::int, 0) + (${delta}::jsonb->>'total_turns')::int,
154
+ 'total_duration_ms', COALESCE((metadata->>'total_duration_ms')::real, 0) + (${delta}::jsonb->>'total_duration_ms')::real,
155
+ 'total_duration_api_ms', COALESCE((metadata->>'total_duration_api_ms')::real, 0) + (${delta}::jsonb->>'total_duration_api_ms')::real,
156
+ 'total_input_tokens', COALESCE((metadata->>'total_input_tokens')::int, 0) + (${delta}::jsonb->>'total_input_tokens')::int,
157
+ 'total_output_tokens', COALESCE((metadata->>'total_output_tokens')::int, 0) + (${delta}::jsonb->>'total_output_tokens')::int,
158
+ 'total_cache_read_tokens', COALESCE((metadata->>'total_cache_read_tokens')::int, 0) + (${delta}::jsonb->>'total_cache_read_tokens')::int,
159
+ 'total_cache_creation_tokens', COALESCE((metadata->>'total_cache_creation_tokens')::int, 0) + (${delta}::jsonb->>'total_cache_creation_tokens')::int,
160
+ 'message_count', COALESCE((metadata->>'message_count')::int, 0) + 1,
161
+ 'models_used', COALESCE(metadata->'models_used', '[]'::jsonb) || ${JSON.stringify(newModels)}::jsonb,
162
+ 'channel', COALESCE(metadata->>'channel', ${(resultMeta.channel as string) || null})
163
+ )
164
+ WHERE id = ${id}
165
+ `;
166
+ }
167
+
116
168
  export async function getLatestRoomIndex(prefix: string): Promise<number> {
117
169
  const sql = getSql();
118
170
  const rows = await sql`