niahere 0.2.40 → 0.2.42
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/channels/index.ts +1 -1
- package/src/channels/registry.ts +4 -0
- package/src/chat/repl.ts +3 -6
- package/src/cli/index.ts +10 -6
- package/src/core/daemon.ts +25 -13
- package/src/db/models/message.ts +40 -1
- package/src/db/models/session.ts +29 -0
- package/src/mcp/index.ts +5 -5
- package/src/mcp/server.ts +33 -0
- package/src/mcp/tools.ts +18 -0
- package/src/prompts/environment.md +13 -0
- package/src/types/index.ts +1 -1
- package/src/types/message.ts +16 -0
package/package.json
CHANGED
package/src/channels/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { log } from "../utils/log";
|
|
|
4
4
|
import { createTelegramChannel } from "./telegram";
|
|
5
5
|
import { createSlackChannel } from "./slack";
|
|
6
6
|
|
|
7
|
-
export { getChannel } from "./registry";
|
|
7
|
+
export { getChannel, getStarted } from "./registry";
|
|
8
8
|
|
|
9
9
|
/** Register all built-in channel factories. Call once at startup. */
|
|
10
10
|
export function registerAllChannels(): void {
|
package/src/channels/registry.ts
CHANGED
package/src/chat/repl.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as readline from "readline";
|
|
|
2
2
|
import { createChatEngine } from "./engine";
|
|
3
3
|
import { runMigrations } from "../db/migrate";
|
|
4
4
|
import { closeDb } from "../db/connection";
|
|
5
|
-
import { getMcpServers,
|
|
5
|
+
import { getMcpServers, setMcpFactory } from "../mcp";
|
|
6
6
|
import { createNiaMcpServer } from "../mcp/server";
|
|
7
7
|
import { Session } from "../db/models";
|
|
8
8
|
import { relativeTime } from "../utils/format";
|
|
@@ -113,12 +113,9 @@ export async function startRepl(mode: ChatMode = "continue", simulateChannel?: s
|
|
|
113
113
|
process.exit(1);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
// Initialize MCP server if not already set (standalone chat mode)
|
|
116
|
+
// Initialize MCP server factory if not already set (standalone chat mode)
|
|
117
117
|
if (!getMcpServers()) {
|
|
118
|
-
|
|
119
|
-
const mcpConfig = createNiaMcpServer();
|
|
120
|
-
setMcpServers({ nia: mcpConfig });
|
|
121
|
-
} catch {}
|
|
118
|
+
setMcpFactory(() => ({ nia: createNiaMcpServer() }));
|
|
122
119
|
}
|
|
123
120
|
|
|
124
121
|
// Determine session to use
|
package/src/cli/index.ts
CHANGED
|
@@ -381,12 +381,16 @@ switch (command) {
|
|
|
381
381
|
case "channels": {
|
|
382
382
|
const sub = process.argv[3];
|
|
383
383
|
const { updateRawConfig } = await import("../utils/config");
|
|
384
|
-
if (sub === "on") {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
384
|
+
if (sub === "on" || sub === "off") {
|
|
385
|
+
const enabled = sub === "on";
|
|
386
|
+
updateRawConfig({ channels: { enabled } });
|
|
387
|
+
const pid = readPid();
|
|
388
|
+
if (pid && isRunning()) {
|
|
389
|
+
process.kill(pid, "SIGHUP");
|
|
390
|
+
console.log(`channels ${enabled ? "enabled" : "disabled"}`);
|
|
391
|
+
} else {
|
|
392
|
+
console.log(`channels ${enabled ? "enabled" : "disabled"} — start nia to apply`);
|
|
393
|
+
}
|
|
390
394
|
} else {
|
|
391
395
|
console.log(`channels: ${getConfig().channels.enabled ? "on" : "off"}`);
|
|
392
396
|
}
|
package/src/core/daemon.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
2
2
|
import { dirname } from "path";
|
|
3
3
|
import { getPaths } from "../utils/paths";
|
|
4
|
-
import { getConfig } from "../utils/config";
|
|
4
|
+
import { getConfig, resetConfig } from "../utils/config";
|
|
5
5
|
import { log } from "../utils/log";
|
|
6
6
|
import { ActiveEngine } from "../db/models";
|
|
7
7
|
import { runMigrations } from "../db/migrate";
|
|
8
8
|
import { closeDb, getSql } from "../db/connection";
|
|
9
|
-
import { registerAllChannels, startChannels, stopChannels } from "../channels";
|
|
9
|
+
import { registerAllChannels, startChannels, stopChannels, getStarted } from "../channels";
|
|
10
10
|
import type { Channel } from "../types";
|
|
11
11
|
import { startScheduler, stopScheduler, recomputeAllNextRuns } from "./scheduler";
|
|
12
12
|
import { startAlive, stopAlive } from "./alive";
|
|
13
13
|
import { createNiaMcpServer } from "../mcp/server";
|
|
14
|
-
import {
|
|
14
|
+
import { setMcpFactory } from "../mcp";
|
|
15
15
|
|
|
16
16
|
export function writePid(pid: number): void {
|
|
17
17
|
const { pid: pidPath } = getPaths();
|
|
@@ -218,14 +218,9 @@ export async function runDaemon(): Promise<void> {
|
|
|
218
218
|
log.info({ recovered }, "recovered stale running jobs");
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// Initialize MCP server (
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
setMcpServers({ nia: mcpConfig });
|
|
225
|
-
log.info("MCP server initialized");
|
|
226
|
-
} catch (err) {
|
|
227
|
-
log.error({ err }, "failed to initialize MCP server");
|
|
228
|
-
}
|
|
221
|
+
// Initialize MCP server factory (each query gets its own Protocol instance)
|
|
222
|
+
setMcpFactory(() => ({ nia: createNiaMcpServer() }));
|
|
223
|
+
log.info("MCP server factory initialized");
|
|
229
224
|
|
|
230
225
|
// Register and start channels
|
|
231
226
|
registerAllChannels();
|
|
@@ -265,9 +260,26 @@ export async function runDaemon(): Promise<void> {
|
|
|
265
260
|
log.warn({ err }, "could not subscribe to nia_jobs, falling back to SIGHUP only");
|
|
266
261
|
}
|
|
267
262
|
|
|
268
|
-
// SIGHUP
|
|
263
|
+
// SIGHUP: reload config, reconcile channels, recompute jobs
|
|
269
264
|
process.on("SIGHUP", async () => {
|
|
270
|
-
log.info("received SIGHUP,
|
|
265
|
+
log.info("received SIGHUP, reloading config");
|
|
266
|
+
resetConfig();
|
|
267
|
+
const fresh = getConfig();
|
|
268
|
+
|
|
269
|
+
const running = getStarted();
|
|
270
|
+
const wantChannels = fresh.channels.enabled;
|
|
271
|
+
const haveChannels = running.length > 0;
|
|
272
|
+
|
|
273
|
+
if (wantChannels && !haveChannels) {
|
|
274
|
+
log.info("SIGHUP: starting channels");
|
|
275
|
+
const result = await startChannels();
|
|
276
|
+
channels = result.started;
|
|
277
|
+
} else if (!wantChannels && haveChannels) {
|
|
278
|
+
log.info("SIGHUP: stopping channels");
|
|
279
|
+
await stopChannels(running);
|
|
280
|
+
channels = [];
|
|
281
|
+
}
|
|
282
|
+
|
|
271
283
|
await recomputeAllNextRuns().catch(() => {});
|
|
272
284
|
});
|
|
273
285
|
|
package/src/db/models/message.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSql } from "../connection";
|
|
2
|
-
import type { SaveMessageParams, RoomStats, RecentMessage } from "../../types";
|
|
2
|
+
import type { SaveMessageParams, RoomStats, RecentMessage, SearchResult, SessionMessage } from "../../types";
|
|
3
3
|
|
|
4
4
|
export async function save(params: SaveMessageParams): Promise<void> {
|
|
5
5
|
const sql = getSql();
|
|
@@ -29,6 +29,45 @@ export async function getRecent(limit = 20, room?: string): Promise<RecentMessag
|
|
|
29
29
|
}));
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
export async function search(query: string, limit = 20, room?: string): Promise<SearchResult[]> {
|
|
33
|
+
const sql = getSql();
|
|
34
|
+
const pattern = `%${query}%`;
|
|
35
|
+
const rows = room
|
|
36
|
+
? await sql`
|
|
37
|
+
SELECT session_id, room, sender, content, created_at
|
|
38
|
+
FROM messages WHERE content ILIKE ${pattern} AND room = ${room}
|
|
39
|
+
ORDER BY created_at DESC LIMIT ${limit}
|
|
40
|
+
`
|
|
41
|
+
: await sql`
|
|
42
|
+
SELECT session_id, room, sender, content, created_at
|
|
43
|
+
FROM messages WHERE content ILIKE ${pattern}
|
|
44
|
+
ORDER BY created_at DESC LIMIT ${limit}
|
|
45
|
+
`;
|
|
46
|
+
return rows.map((r) => ({
|
|
47
|
+
sessionId: r.session_id,
|
|
48
|
+
room: r.room,
|
|
49
|
+
sender: r.sender,
|
|
50
|
+
content: r.content,
|
|
51
|
+
createdAt: String(r.created_at),
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getBySession(sessionId: string): Promise<SessionMessage[]> {
|
|
56
|
+
const sql = getSql();
|
|
57
|
+
const rows = await sql`
|
|
58
|
+
SELECT room, sender, content, is_from_agent, created_at
|
|
59
|
+
FROM messages WHERE session_id = ${sessionId}
|
|
60
|
+
ORDER BY created_at ASC
|
|
61
|
+
`;
|
|
62
|
+
return rows.map((r) => ({
|
|
63
|
+
room: r.room,
|
|
64
|
+
sender: r.sender,
|
|
65
|
+
content: r.content,
|
|
66
|
+
isFromAgent: r.is_from_agent,
|
|
67
|
+
createdAt: String(r.created_at),
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
|
|
32
71
|
export async function getRoomStats(): Promise<RoomStats[]> {
|
|
33
72
|
const sql = getSql();
|
|
34
73
|
const rows = await sql`
|
package/src/db/models/session.ts
CHANGED
|
@@ -49,6 +49,35 @@ export async function getRecent(room: string, limit = 10): Promise<SessionSummar
|
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
export async function listRecent(limit = 10, room?: string): Promise<SessionSummary[]> {
|
|
53
|
+
if (room) return getRecent(room, limit);
|
|
54
|
+
const sql = getSql();
|
|
55
|
+
const rows = await sql`
|
|
56
|
+
SELECT
|
|
57
|
+
s.id,
|
|
58
|
+
s.room,
|
|
59
|
+
s.created_at,
|
|
60
|
+
s.updated_at,
|
|
61
|
+
(
|
|
62
|
+
SELECT content FROM messages m
|
|
63
|
+
WHERE m.session_id = s.id AND m.sender = 'user'
|
|
64
|
+
ORDER BY m.created_at ASC LIMIT 1
|
|
65
|
+
) AS preview,
|
|
66
|
+
(SELECT COUNT(*)::int FROM messages m WHERE m.session_id = s.id) AS message_count
|
|
67
|
+
FROM sessions s
|
|
68
|
+
ORDER BY s.updated_at DESC
|
|
69
|
+
LIMIT ${limit}
|
|
70
|
+
`;
|
|
71
|
+
return rows.map((r) => ({
|
|
72
|
+
id: r.id,
|
|
73
|
+
room: r.room,
|
|
74
|
+
createdAt: String(r.created_at),
|
|
75
|
+
updatedAt: String(r.updated_at),
|
|
76
|
+
preview: r.preview ? String(r.preview) : null,
|
|
77
|
+
messageCount: r.message_count,
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
52
81
|
export async function create(id: string, room: string): Promise<void> {
|
|
53
82
|
const sql = getSql();
|
|
54
83
|
await sql`INSERT INTO sessions (id, room) VALUES (${id}, ${room})`;
|
package/src/mcp/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
let
|
|
1
|
+
/** Factory for per-query MCP servers — each query gets its own Protocol instance. */
|
|
2
|
+
let _mcpFactory: (() => Record<string, unknown>) | null = null;
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
|
|
4
|
+
export function setMcpFactory(factory: () => Record<string, unknown>): void {
|
|
5
|
+
_mcpFactory = factory;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export function getMcpServers(): Record<string, unknown> | undefined {
|
|
9
|
-
return
|
|
9
|
+
return _mcpFactory?.() ?? undefined;
|
|
10
10
|
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -99,6 +99,39 @@ export function createNiaMcpServer() {
|
|
|
99
99
|
content: [{ type: "text" as const, text: await handlers.listMessages(args.limit, args.room) }],
|
|
100
100
|
}),
|
|
101
101
|
),
|
|
102
|
+
tool(
|
|
103
|
+
"list_sessions",
|
|
104
|
+
"Browse past conversation sessions with previews. Returns session IDs you can pass to read_session.",
|
|
105
|
+
{
|
|
106
|
+
room: z.string().optional().describe("Filter by room name"),
|
|
107
|
+
limit: z.number().default(10).describe("Number of sessions to return"),
|
|
108
|
+
},
|
|
109
|
+
async (args) => ({
|
|
110
|
+
content: [{ type: "text" as const, text: await handlers.listSessions(args.limit, args.room) }],
|
|
111
|
+
}),
|
|
112
|
+
),
|
|
113
|
+
tool(
|
|
114
|
+
"search_messages",
|
|
115
|
+
"Search across all past messages by keyword. Returns matching messages with session IDs for deeper reading.",
|
|
116
|
+
{
|
|
117
|
+
query: z.string().describe("Text to search for in message content"),
|
|
118
|
+
room: z.string().optional().describe("Filter by room name"),
|
|
119
|
+
limit: z.number().default(20).describe("Max results to return"),
|
|
120
|
+
},
|
|
121
|
+
async (args) => ({
|
|
122
|
+
content: [{ type: "text" as const, text: await handlers.searchMessages(args.query, args.limit, args.room) }],
|
|
123
|
+
}),
|
|
124
|
+
),
|
|
125
|
+
tool(
|
|
126
|
+
"read_session",
|
|
127
|
+
"Load the full transcript of a specific conversation session. Use list_sessions or search_messages to find session IDs.",
|
|
128
|
+
{
|
|
129
|
+
session_id: z.string().describe("Session ID to read"),
|
|
130
|
+
},
|
|
131
|
+
async (args) => ({
|
|
132
|
+
content: [{ type: "text" as const, text: await handlers.readSession(args.session_id) }],
|
|
133
|
+
}),
|
|
134
|
+
),
|
|
102
135
|
tool(
|
|
103
136
|
"add_watch_channel",
|
|
104
137
|
"Add or update a Slack watch channel. Watch channels receive ALL messages (not just @mentions) and act based on the behavior prompt. Takes effect on next message (hot-reloads).",
|
package/src/mcp/tools.ts
CHANGED
|
@@ -244,6 +244,24 @@ export async function listMessages(limit = 20, room?: string): Promise<string> {
|
|
|
244
244
|
return JSON.stringify(messages, null, 2);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
+
export async function listSessions(limit = 10, room?: string): Promise<string> {
|
|
248
|
+
const sessions = await Session.listRecent(limit, room);
|
|
249
|
+
if (sessions.length === 0) return "No sessions found.";
|
|
250
|
+
return JSON.stringify(sessions, null, 2);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function searchMessages(query: string, limit = 20, room?: string): Promise<string> {
|
|
254
|
+
const results = await Message.search(query, limit, room);
|
|
255
|
+
if (results.length === 0) return "No matching messages found.";
|
|
256
|
+
return JSON.stringify(results, null, 2);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function readSession(sessionId: string): Promise<string> {
|
|
260
|
+
const messages = await Message.getBySession(sessionId);
|
|
261
|
+
if (messages.length === 0) return "Session not found or has no messages.";
|
|
262
|
+
return JSON.stringify(messages, null, 2);
|
|
263
|
+
}
|
|
264
|
+
|
|
247
265
|
export function addRule(rule: string): string {
|
|
248
266
|
const { selfDir } = getPaths();
|
|
249
267
|
const rulesPath = join(selfDir, "rules.md");
|
|
@@ -31,6 +31,9 @@ You have MCP tools for managing jobs directly (preferred over CLI for speed):
|
|
|
31
31
|
- **run_job** — trigger a job to run immediately
|
|
32
32
|
- **send_message** — send a message to the user (via telegram, slack, or default channel). Supports `media_path` to send images/files.
|
|
33
33
|
- **list_messages** — read recent chat history
|
|
34
|
+
- **list_sessions** — browse past conversation sessions with previews and message counts. Returns session IDs.
|
|
35
|
+
- **search_messages** — keyword search across all past messages. Find when something was discussed.
|
|
36
|
+
- **read_session** — load the full transcript of a specific session by ID.
|
|
34
37
|
- **add_watch_channel** — add a Slack channel for proactive monitoring. Specify channel key (`channel_id#name`) and behavior prompt. Hot-reloads.
|
|
35
38
|
- **remove_watch_channel** — stop watching a Slack channel. Hot-reloads.
|
|
36
39
|
- **enable_watch_channel** / **disable_watch_channel** — toggle a watch channel on/off without removing it. Hot-reloads.
|
|
@@ -73,6 +76,16 @@ Config reference:
|
|
|
73
76
|
- `channels.slack.watch` — per-channel proactive monitoring. Keys are `channel_id#channel_name` format.
|
|
74
77
|
{{slackWatch}}
|
|
75
78
|
|
|
79
|
+
## Conversation History
|
|
80
|
+
|
|
81
|
+
You have access to all prior conversations stored in the database:
|
|
82
|
+
|
|
83
|
+
- **list_sessions** — browse past sessions (with previews and message counts). Use to find a conversation.
|
|
84
|
+
- **search_messages** — search across all past messages by keyword. Returns session IDs for deeper reading.
|
|
85
|
+
- **read_session** — load the full transcript of a session by ID.
|
|
86
|
+
|
|
87
|
+
Use these when the user asks "did we talk about...", "what did I say about...", or when you need context from a prior conversation. Combine with `read_memory` for a complete picture.
|
|
88
|
+
|
|
76
89
|
## Persona & Memory
|
|
77
90
|
|
|
78
91
|
Your persona files live in {{selfDir}}/:
|
package/src/types/index.ts
CHANGED
|
@@ -7,5 +7,5 @@ export type { Channel, ChannelFactory } from "./channel";
|
|
|
7
7
|
export type { ChatState } from "./chat-state";
|
|
8
8
|
export type { Config, ChannelsConfig, TelegramConfig, SlackConfig } from "./config";
|
|
9
9
|
export type { Paths } from "./paths";
|
|
10
|
-
export type { SaveMessageParams, RoomStats, RecentMessage } from "./message";
|
|
10
|
+
export type { SaveMessageParams, RoomStats, RecentMessage, SearchResult, SessionMessage } from "./message";
|
|
11
11
|
export type { AgentInfo } from "./agent";
|
package/src/types/message.ts
CHANGED
|
@@ -19,3 +19,19 @@ export interface RecentMessage {
|
|
|
19
19
|
content: string;
|
|
20
20
|
createdAt: string;
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
export interface SearchResult {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
room: string;
|
|
26
|
+
sender: string;
|
|
27
|
+
content: string;
|
|
28
|
+
createdAt: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface SessionMessage {
|
|
32
|
+
room: string;
|
|
33
|
+
sender: string;
|
|
34
|
+
content: string;
|
|
35
|
+
isFromAgent: boolean;
|
|
36
|
+
createdAt: string;
|
|
37
|
+
}
|