niahere 0.2.66 → 0.2.67

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.66",
3
+ "version": "0.2.67",
4
4
  "description": "A personal AI assistant daemon — chat, scheduled jobs, persona system, extensible via skills.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -79,20 +79,26 @@ class SlackChannel implements Channel {
79
79
  return `slack-${key}-${index}`;
80
80
  }
81
81
 
82
- async function getState(key: string): Promise<ChatState> {
82
+ async function getState(key: string, watchBehavior?: { channel: string; behavior: string }): Promise<ChatState> {
83
83
  let state = chats.get(key);
84
84
  if (!state) {
85
85
  const prefix = roomPrefix(key);
86
86
  const idx = await Session.getLatestRoomIndex(prefix);
87
87
  const room = roomName(key, idx);
88
- const engine = await createChatEngine({ room, channel: "slack", resume: true, mcpServers: getMcpServers() });
88
+ const engine = await createChatEngine({
89
+ room,
90
+ channel: "slack",
91
+ resume: true,
92
+ mcpServers: getMcpServers(),
93
+ watchBehavior,
94
+ });
89
95
  state = { engine, roomIndex: idx, lock: Promise.resolve() };
90
96
  chats.set(key, state);
91
97
  }
92
98
  return state;
93
99
  }
94
100
 
95
- async function restartChat(key: string): Promise<ChatState> {
101
+ async function restartChat(key: string, watchBehavior?: { channel: string; behavior: string }): Promise<ChatState> {
96
102
  const old = chats.get(key);
97
103
  if (old) old.engine.close();
98
104
 
@@ -106,7 +112,13 @@ class SlackChannel implements Channel {
106
112
  await Session.create(`placeholder-${room}`, room);
107
113
 
108
114
  log.info({ key, room }, "slack: creating chat engine");
109
- const engine = await createChatEngine({ room, channel: "slack", resume: false, mcpServers: getMcpServers() });
115
+ const engine = await createChatEngine({
116
+ room,
117
+ channel: "slack",
118
+ resume: false,
119
+ mcpServers: getMcpServers(),
120
+ watchBehavior,
121
+ });
110
122
  const state: ChatState = { engine, roomIndex: newIdx, lock: Promise.resolve() };
111
123
  chats.set(key, state);
112
124
  log.info({ key, room, activeSessions: chats.size }, "slack: engine ready");
@@ -504,11 +516,10 @@ class SlackChannel implements Channel {
504
516
  }
505
517
  }
506
518
 
507
- // Prepend watch behavior context for watched channels
508
- if (watchConfig) {
509
- const behaviorLine = watchConfig.behavior ? `Behavior: ${watchConfig.behavior}\n` : "";
510
- text = `[Watch mode — #${watchConfig.name}]\n${behaviorLine}Respond with [NO_REPLY] if no action needed.\n\n${text}`;
511
- }
519
+ // Build watch behavior for system prompt injection (if watched channel)
520
+ const watchBehavior = watchConfig?.behavior
521
+ ? { channel: watchConfig.name, behavior: watchConfig.behavior }
522
+ : undefined;
512
523
 
513
524
  log.info(
514
525
  {
@@ -524,7 +535,7 @@ class SlackChannel implements Channel {
524
535
 
525
536
  let state: ChatState;
526
537
  try {
527
- state = await getState(key);
538
+ state = await getState(key, watchBehavior);
528
539
  } catch (err) {
529
540
  log.error({ err, key }, "slack: failed to create chat engine");
530
541
  return;
@@ -12,7 +12,7 @@ function loadFile(dir: string, name: string): string {
12
12
  return readFileSync(filePath, "utf8").trim();
13
13
  }
14
14
 
15
- export function buildEmployeePrompt(name: string): string {
15
+ export function buildEmployeePrompt(name: string, mode: "chat" | "job" = "chat"): string {
16
16
  const employee = getEmployee(name);
17
17
  if (!employee) return "";
18
18
 
@@ -25,7 +25,7 @@ export function buildEmployeePrompt(name: string): string {
25
25
  // Environment + mode + capabilities
26
26
  parts.push(getEnvironmentPrompt());
27
27
 
28
- const modePrompt = getModePrompt("chat");
28
+ const modePrompt = getModePrompt(mode);
29
29
  if (modePrompt) parts.push(modePrompt);
30
30
 
31
31
  const skills = getSkillsSummary();
@@ -5,7 +5,7 @@ import { existsSync } from "fs";
5
5
  import { join } from "path";
6
6
  import { homedir } from "os";
7
7
  import { randomUUID } from "crypto";
8
- import { buildSystemPrompt, getSessionContext } from "./identity";
8
+ import { buildSystemPrompt, buildContextSuffix, getSessionContext } from "./identity";
9
9
  import { buildEmployeePrompt } from "./employee-prompt";
10
10
  import { getEmployee } from "../core/employees";
11
11
  import { getAgentDefinitions, scanAgents } from "../core/agents";
@@ -146,7 +146,7 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
146
146
  } else if (opts.agent) {
147
147
  const agents = scanAgents();
148
148
  const agentDef = agents.find((a) => a.name === opts.agent);
149
- if (agentDef) systemPrompt = agentDef.body;
149
+ if (agentDef) systemPrompt = agentDef.body + "\n\n" + buildContextSuffix("chat");
150
150
  } else if (opts.job) {
151
151
  // Job chat: load job and use its context
152
152
  const jobData = await Job.get(opts.job);
@@ -158,15 +158,21 @@ export async function createChatEngine(opts: EngineOptions): Promise<ChatEngine>
158
158
  const emp = getEmployee(jobData.employee);
159
159
  if (emp?.repo && existsSync(emp.repo)) cwd = emp.repo;
160
160
  } else if (jobData.agent) {
161
- // If job has an agent, use agent prompt
161
+ // If job has an agent, use agent prompt + context
162
162
  const agents = scanAgents();
163
163
  const agentDef = agents.find((a) => a.name === jobData.agent);
164
- if (agentDef) systemPrompt = agentDef.body;
164
+ if (agentDef) systemPrompt = agentDef.body + "\n\n" + buildContextSuffix("chat");
165
165
  }
166
166
  systemPrompt += `\n\n## Job Context\nYou are chatting in the context of job "${jobData.name}" (schedule: ${jobData.schedule}).\n\nJob prompt:\n${jobData.prompt}`;
167
167
  }
168
168
  }
169
169
 
170
+ // Watch mode: inject behavior into system prompt
171
+ if (opts.watchBehavior) {
172
+ const { channel: watchChannel, behavior } = opts.watchBehavior;
173
+ systemPrompt += `\n\n## Watch Mode — #${watchChannel}\n\nYou are monitoring this Slack channel. Follow the behavior instructions below.\nRespond with [NO_REPLY] if no action is needed — do not explain why.\n\n${behavior}`;
174
+ }
175
+
170
176
  let sessionId: string | null = null;
171
177
  if (typeof resume === "string") {
172
178
  // Specific session ID provided
@@ -52,6 +52,30 @@ export function buildSystemPrompt(mode: Mode = "chat", channel: string = "termin
52
52
  return parts.join("\n\n");
53
53
  }
54
54
 
55
+ /**
56
+ * Build the context suffix (env + mode + skills + agents + employees) that should
57
+ * be appended to any custom system prompt (agent body, watch behavior, etc).
58
+ * Does NOT include Nia's identity — that's the caller's responsibility.
59
+ */
60
+ export function buildContextSuffix(mode: Mode = "chat"): string {
61
+ const parts: string[] = [];
62
+ parts.push(getEnvironmentPrompt());
63
+
64
+ const modePrompt = getModePrompt(mode);
65
+ if (modePrompt) parts.push(modePrompt);
66
+
67
+ const skills = getSkillsSummary();
68
+ if (skills) parts.push(skills);
69
+
70
+ const agents = getAgentsSummary();
71
+ if (agents) parts.push(agents);
72
+
73
+ const employees = getEmployeesSummary();
74
+ if (employees) parts.push(employees);
75
+
76
+ return parts.join("\n\n");
77
+ }
78
+
55
79
  /**
56
80
  * Load recent session summaries for a room and format as a context block.
57
81
  * Returns empty string if no summaries are available.
@@ -7,7 +7,7 @@ import type { JobInput, JobResult } from "../types";
7
7
  import { appendAudit, readState, writeState } from "../utils/logger";
8
8
  import type { AuditEntry, JobState } from "../types";
9
9
  import { getConfig } from "../utils/config";
10
- import { buildSystemPrompt } from "../chat/identity";
10
+ import { buildSystemPrompt, buildContextSuffix } from "../chat/identity";
11
11
  import { buildEmployeePrompt } from "../chat/employee-prompt";
12
12
  import { getEmployee } from "./employees";
13
13
  import { scanAgents } from "./agents";
@@ -309,7 +309,7 @@ export async function runJob(job: JobInput, onActivity?: ActivityCallback): Prom
309
309
  let systemPrompt: string;
310
310
  let agentModel: string | undefined;
311
311
  if (job.employee) {
312
- const empPrompt = buildEmployeePrompt(job.employee);
312
+ const empPrompt = buildEmployeePrompt(job.employee, "job");
313
313
  if (empPrompt) {
314
314
  systemPrompt = empPrompt;
315
315
  } else {
@@ -322,7 +322,7 @@ export async function runJob(job: JobInput, onActivity?: ActivityCallback): Prom
322
322
  const agents = scanAgents();
323
323
  const agentDef = agents.find((a) => a.name === job.agent);
324
324
  if (agentDef) {
325
- systemPrompt = agentDef.body;
325
+ systemPrompt = agentDef.body + "\n\n" + buildContextSuffix("job");
326
326
  agentModel = agentDef.model;
327
327
  } else {
328
328
  systemPrompt = buildSystemPrompt("job");
@@ -34,4 +34,6 @@ export interface EngineOptions {
34
34
  employee?: string;
35
35
  agent?: string;
36
36
  job?: string;
37
+ /** Watch channel behavior — injected into system prompt for watch-mode engines. */
38
+ watchBehavior?: { channel: string; behavior: string };
37
39
  }