nothumanallowed 5.0.0 → 6.0.1

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,7 +1,7 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "5.0.0",
4
- "description": "NotHumanAllowed — 38 AI agents for security, code, DevOps, data & daily ops. Ask agents directly, plan your day with 5 specialist agents, manage tasks, connect Gmail + Calendar.",
3
+ "version": "6.0.1",
4
+ "description": "NotHumanAllowed — 38 AI agents for security, code, DevOps, data & daily ops. Per-agent memory, Telegram + Discord auto-responder, proactive intelligence daemon, voice chat, plugin system.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "nha": "./bin/nha.mjs",
@@ -31,7 +31,14 @@
31
31
  "anthropic",
32
32
  "openai",
33
33
  "gemini",
34
- "deepseek"
34
+ "deepseek",
35
+ "telegram-bot",
36
+ "discord-bot",
37
+ "voice-assistant",
38
+ "daily-ops",
39
+ "gmail",
40
+ "calendar",
41
+ "proactive"
35
42
  ],
36
43
  "author": "Nicola Cucurachi <ados.labsproject@gmail.com>",
37
44
  "license": "MIT",
package/src/cli.mjs CHANGED
@@ -12,6 +12,7 @@ import { cmdAsk } from './commands/ask.mjs';
12
12
  import { cmdPlan } from './commands/plan.mjs';
13
13
  import { cmdTasks } from './commands/tasks.mjs';
14
14
  import { cmdOps } from './commands/ops.mjs';
15
+ import { cmdAutostart } from './commands/autostart.mjs';
15
16
  import { cmdChat } from './commands/chat.mjs';
16
17
  import { cmdUI } from './commands/ui.mjs';
17
18
  import { cmdGoogle } from './commands/google-auth.mjs';
@@ -60,6 +61,12 @@ export async function main(argv) {
60
61
  case 'ops':
61
62
  return cmdOps(args);
62
63
 
64
+ case 'autostart':
65
+ return cmdAutostart(args);
66
+
67
+ case 'responder':
68
+ return cmdResponder(args);
69
+
63
70
  case 'chat':
64
71
  return cmdChat(args);
65
72
 
@@ -131,6 +138,73 @@ export async function main(argv) {
131
138
  }
132
139
  }
133
140
 
141
+ // ── nha responder ─────────────────────────────────────────────────────────
142
+ async function cmdResponder(args) {
143
+ const sub = args[0] || 'status';
144
+ const config = loadConfig();
145
+
146
+ switch (sub) {
147
+ case 'start': {
148
+ const { isRunning } = await import('./services/ops-daemon.mjs');
149
+ if (!isRunning()) {
150
+ warn('Daemon is not running. The responder runs inside the daemon.');
151
+ info('Start it with: nha ops start');
152
+ info('Or enable autostart: nha autostart enable');
153
+ return;
154
+ }
155
+ info('The responder starts automatically with the daemon when tokens are configured.');
156
+ info('Configure tokens:');
157
+ console.log(' nha config set telegram-bot-token YOUR_BOT_TOKEN');
158
+ console.log(' nha config set discord-bot-token YOUR_BOT_TOKEN');
159
+ info('Then restart the daemon: nha ops stop && nha ops start');
160
+ return;
161
+ }
162
+
163
+ case 'stop': {
164
+ info('The responder stops when the daemon stops.');
165
+ info('Run: nha ops stop');
166
+ return;
167
+ }
168
+
169
+ case 'status': {
170
+ console.log(`\n ${BOLD}Message Responder Status${NC}\n`);
171
+
172
+ const telegramToken = config.responder?.telegram?.token;
173
+ const discordToken = config.responder?.discord?.token;
174
+ const autoRoute = config.responder?.autoRoute !== false;
175
+
176
+ console.log(` Telegram: ${telegramToken ? G + 'configured' + NC : D + '(not set)' + NC}`);
177
+ if (telegramToken) {
178
+ const chatIds = config.responder?.telegram?.allowedChatIds || [];
179
+ console.log(` Chat filter: ${chatIds.length > 0 ? Y + chatIds.join(', ') + NC : D + 'all chats' + NC}`);
180
+ }
181
+
182
+ console.log(` Discord: ${discordToken ? G + 'configured' + NC : D + '(not set)' + NC}`);
183
+ if (discordToken) {
184
+ const channelIds = config.responder?.discord?.allowedChannelIds || [];
185
+ console.log(` Channel filter: ${channelIds.length > 0 ? Y + channelIds.join(', ') + NC : D + 'all channels (mention/command only)' + NC}`);
186
+ }
187
+
188
+ console.log(` Auto-route: ${autoRoute ? G + 'keyword routing' + NC : D + 'CONDUCTOR only' + NC}`);
189
+
190
+ const { isRunning: isDaemonRunning } = await import('./services/ops-daemon.mjs');
191
+ console.log(` Daemon: ${isDaemonRunning() ? G + 'running' + NC : R + 'stopped' + NC}`);
192
+ console.log('');
193
+
194
+ if (!telegramToken && !discordToken) {
195
+ info('Configure a bot token to enable:');
196
+ console.log(' nha config set telegram-bot-token YOUR_TOKEN');
197
+ console.log(' nha config set discord-bot-token YOUR_TOKEN');
198
+ }
199
+ return;
200
+ }
201
+
202
+ default:
203
+ fail(`Unknown: nha responder ${sub}`);
204
+ info('Commands: start, stop, status');
205
+ }
206
+ }
207
+
134
208
  // ── nha run ────────────────────────────────────────────────────────────────
135
209
  async function cmdRun(args) {
136
210
  if (args.length === 0) {
@@ -252,6 +326,8 @@ function cmdConfig(args) {
252
326
  info(' verbose, immersive, deliberation, rounds, convergence, tribunal, knowledge');
253
327
  info(' google-client-id, google-client-secret');
254
328
  info(' microsoft-client-id, microsoft-client-secret, microsoft-tenant');
329
+ info(' telegram-bot-token, discord-bot-token, responder-auto-route');
330
+ info(' proactive, proactive-email, proactive-meeting, proactive-patterns, proactive-deadlines');
255
331
  return;
256
332
  }
257
333
  const success = setConfigValue(key, value);
@@ -264,9 +340,8 @@ function cmdConfig(args) {
264
340
  }
265
341
 
266
342
  if (sub === 'reset') {
267
- const { saveConfig } = loadConfig; // re-import
268
343
  fs.rmSync(path.join(NHA_DIR, 'config.json'), { force: true });
269
- ok('Config reset to defaults');
344
+ ok('Config reset to defaults. Run any command to regenerate.');
270
345
  return;
271
346
  }
272
347
 
@@ -322,6 +397,23 @@ function cmdConfig(args) {
322
397
  if (config.voice.language) console.log(` Language: ${D}${config.voice.language}${NC}`);
323
398
  }
324
399
 
400
+ if (config.responder) {
401
+ console.log(`\n ${C}Message Responder${NC}`);
402
+ console.log(` Telegram: ${config.responder.telegram?.token ? G + 'configured' : D + '(not set)'}${NC}`);
403
+ console.log(` Discord: ${config.responder.discord?.token ? G + 'configured' : D + '(not set)'}${NC}`);
404
+ console.log(` Auto-route: ${config.responder.autoRoute !== false ? G + 'yes' : D + 'no'}${NC}`);
405
+ }
406
+
407
+ const proactive = config.ops?.proactive;
408
+ if (proactive) {
409
+ console.log(`\n ${C}Proactive Intelligence${NC}`);
410
+ console.log(` Enabled: ${proactive.enabled !== false ? G + 'yes' : D + 'no'}${NC}`);
411
+ console.log(` Email follow-up:${proactive.emailFollowUp !== false ? G + ' on' : D + ' off'}${NC}`);
412
+ console.log(` Meeting prep: ${proactive.meetingPrep !== false ? G + 'on' : D + 'off'}${NC}`);
413
+ console.log(` Patterns: ${proactive.patterns !== false ? G + 'on' : D + 'off'}${NC}`);
414
+ console.log(` Deadlines: ${proactive.deadlines !== false ? G + 'on' : D + 'off'}${NC}`);
415
+ }
416
+
325
417
  console.log('');
326
418
  }
327
419
 
@@ -431,7 +523,23 @@ function cmdHelp() {
431
523
  console.log(` tasks week Week overview`);
432
524
  console.log(` ops start Start background daemon (auto-alerts + WebSocket)`);
433
525
  console.log(` ops stop Stop daemon`);
434
- console.log(` ops status Daemon status\n`);
526
+ console.log(` ops status Daemon status`);
527
+ console.log(` autostart enable Auto-start daemon on login (launchd/systemd)`);
528
+ console.log(` autostart disable Remove OS autostart`);
529
+ console.log(` autostart status Check autostart configuration\n`);
530
+
531
+ console.log(` ${C}Message Responder${NC} ${D}(Telegram + Discord auto-reply)${NC}`);
532
+ console.log(` responder status Show responder configuration`);
533
+ console.log(` config set telegram-bot-token TOKEN`);
534
+ console.log(` config set discord-bot-token TOKEN`);
535
+ console.log(` ${D}Routes messages to agents via keyword matching (zero LLM overhead)${NC}\n`);
536
+
537
+ console.log(` ${C}Proactive Intelligence${NC} ${D}(runs inside daemon)${NC}`);
538
+ console.log(` ${D}Email follow-ups, meeting prep, pattern detection, deadline tracking${NC}`);
539
+ console.log(` config set proactive true/false Toggle all proactive features`);
540
+ console.log(` config set proactive-email true/false Email follow-up reminders`);
541
+ console.log(` config set proactive-meeting true/false Auto meeting briefs`);
542
+ console.log(` config set proactive-deadlines true/false Deadline alerts\n`);
435
543
 
436
544
  console.log(` ${C}Google Integration${NC}`);
437
545
  console.log(` google auth Connect Gmail + Calendar`);
@@ -0,0 +1,342 @@
1
+ /**
2
+ * nha autostart — OS-level daemon autostart management.
3
+ *
4
+ * macOS: launchd plist at ~/Library/LaunchAgents/com.nha.daemon.plist
5
+ * Linux: systemd user service at ~/.config/systemd/user/nha-daemon.service
6
+ *
7
+ * Zero dependencies.
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import os from 'os';
12
+ import path from 'path';
13
+ import { execSync, spawnSync } from 'child_process';
14
+ import { NHA_DIR, DAEMON_SCRIPT } from '../constants.mjs';
15
+ import { info, ok, fail, warn, C, G, Y, D, W, BOLD, NC, R } from '../ui.mjs';
16
+
17
+ const PLATFORM = os.platform();
18
+ const HOME = os.homedir();
19
+
20
+ // ── Paths ────────────────────────────────────────────────────────────────────
21
+
22
+ // DAEMON_SCRIPT is resolved relative to the installed package (via constants.mjs),
23
+ // so it works correctly both in development and after npm install -g.
24
+ const DAEMON_SCRIPT_ABS = DAEMON_SCRIPT;
25
+ const NODE_BIN = process.execPath;
26
+
27
+ const LAUNCHD_LABEL = 'com.nha.daemon';
28
+ const LAUNCHD_PLIST = path.join(HOME, 'Library', 'LaunchAgents', `${LAUNCHD_LABEL}.plist`);
29
+ const LAUNCHD_LOG_DIR = path.join(NHA_DIR, 'ops', 'daemon');
30
+
31
+ const SYSTEMD_DIR = path.join(HOME, '.config', 'systemd', 'user');
32
+ const SYSTEMD_UNIT = 'nha-daemon.service';
33
+ const SYSTEMD_FILE = path.join(SYSTEMD_DIR, SYSTEMD_UNIT);
34
+
35
+ // ── launchd (macOS) ──────────────────────────────────────────────────────────
36
+
37
+ function generateLaunchdPlist() {
38
+ // launchd XML plist — KeepAlive restarts on crash, RunAtLoad starts on login
39
+ return `<?xml version="1.0" encoding="UTF-8"?>
40
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
41
+ <plist version="1.0">
42
+ <dict>
43
+ <key>Label</key>
44
+ <string>${LAUNCHD_LABEL}</string>
45
+
46
+ <key>ProgramArguments</key>
47
+ <array>
48
+ <string>${NODE_BIN}</string>
49
+ <string>${DAEMON_SCRIPT_ABS}</string>
50
+ <string>--daemon-loop</string>
51
+ </array>
52
+
53
+ <key>RunAtLoad</key>
54
+ <true/>
55
+
56
+ <key>KeepAlive</key>
57
+ <dict>
58
+ <key>SuccessfulExit</key>
59
+ <false/>
60
+ </dict>
61
+
62
+ <key>ThrottleInterval</key>
63
+ <integer>10</integer>
64
+
65
+ <key>StandardOutPath</key>
66
+ <string>${path.join(LAUNCHD_LOG_DIR, 'daemon.log')}</string>
67
+
68
+ <key>StandardErrorPath</key>
69
+ <string>${path.join(LAUNCHD_LOG_DIR, 'daemon.log')}</string>
70
+
71
+ <key>EnvironmentVariables</key>
72
+ <dict>
73
+ <key>NHA_DAEMON</key>
74
+ <string>1</string>
75
+ <key>HOME</key>
76
+ <string>${HOME}</string>
77
+ <key>PATH</key>
78
+ <string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin:${path.dirname(NODE_BIN)}</string>
79
+ </dict>
80
+
81
+ <key>ProcessType</key>
82
+ <string>Background</string>
83
+
84
+ <key>LowPriorityIO</key>
85
+ <true/>
86
+ </dict>
87
+ </plist>
88
+ `;
89
+ }
90
+
91
+ function launchdInstall() {
92
+ const agentsDir = path.join(HOME, 'Library', 'LaunchAgents');
93
+ fs.mkdirSync(agentsDir, { recursive: true });
94
+ fs.mkdirSync(LAUNCHD_LOG_DIR, { recursive: true });
95
+
96
+ fs.writeFileSync(LAUNCHD_PLIST, generateLaunchdPlist(), { mode: 0o644 });
97
+
98
+ // Load the agent (starts it immediately)
99
+ const result = spawnSync('launchctl', ['load', '-w', LAUNCHD_PLIST], { encoding: 'utf-8' });
100
+ if (result.status !== 0) {
101
+ const stderr = (result.stderr || '').trim();
102
+ // Already loaded is not a real error
103
+ if (!stderr.includes('already loaded') && !stderr.includes('service already loaded')) {
104
+ throw new Error(`launchctl load failed: ${stderr}`);
105
+ }
106
+ }
107
+ }
108
+
109
+ function launchdUninstall() {
110
+ if (!fs.existsSync(LAUNCHD_PLIST)) {
111
+ return false;
112
+ }
113
+
114
+ // Unload (stops the daemon)
115
+ spawnSync('launchctl', ['unload', '-w', LAUNCHD_PLIST], { encoding: 'utf-8' });
116
+
117
+ fs.rmSync(LAUNCHD_PLIST, { force: true });
118
+ return true;
119
+ }
120
+
121
+ function launchdStatus() {
122
+ const installed = fs.existsSync(LAUNCHD_PLIST);
123
+ if (!installed) {
124
+ return { installed: false, running: false, pid: null };
125
+ }
126
+
127
+ // Check if service is loaded and running
128
+ const result = spawnSync('launchctl', ['list'], { encoding: 'utf-8' });
129
+ const lines = (result.stdout || '').split('\n');
130
+ const match = lines.find(l => l.includes(LAUNCHD_LABEL));
131
+
132
+ if (!match) {
133
+ return { installed: true, running: false, pid: null };
134
+ }
135
+
136
+ // Format: PID Status Label
137
+ const parts = match.trim().split(/\s+/);
138
+ const pid = parts[0] === '-' ? null : parseInt(parts[0], 10);
139
+ const running = pid !== null && !isNaN(pid);
140
+
141
+ return { installed: true, running, pid };
142
+ }
143
+
144
+ // ── systemd (Linux) ──────────────────────────────────────────────────────────
145
+
146
+ function generateSystemdUnit() {
147
+ return `[Unit]
148
+ Description=NHA PAO Background Daemon
149
+ Documentation=https://nothumanallowed.com/docs/cli
150
+ After=network-online.target
151
+ Wants=network-online.target
152
+
153
+ [Service]
154
+ Type=simple
155
+ ExecStart=${NODE_BIN} ${DAEMON_SCRIPT_ABS} --daemon-loop
156
+ Environment=NHA_DAEMON=1
157
+ Environment=HOME=${HOME}
158
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin:${path.dirname(NODE_BIN)}
159
+
160
+ Restart=on-failure
161
+ RestartSec=10
162
+ StartLimitIntervalSec=300
163
+ StartLimitBurst=5
164
+
165
+ StandardOutput=append:${path.join(LAUNCHD_LOG_DIR, 'daemon.log')}
166
+ StandardError=append:${path.join(LAUNCHD_LOG_DIR, 'daemon.log')}
167
+
168
+ # Security hardening
169
+ NoNewPrivileges=true
170
+ ProtectSystem=strict
171
+ ProtectHome=read-only
172
+ ReadWritePaths=${NHA_DIR}
173
+
174
+ [Install]
175
+ WantedBy=default.target
176
+ `;
177
+ }
178
+
179
+ function systemdInstall() {
180
+ fs.mkdirSync(SYSTEMD_DIR, { recursive: true });
181
+ fs.mkdirSync(LAUNCHD_LOG_DIR, { recursive: true });
182
+
183
+ fs.writeFileSync(SYSTEMD_FILE, generateSystemdUnit(), { mode: 0o644 });
184
+
185
+ // Reload systemd user daemon to pick up new unit
186
+ spawnSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8' });
187
+
188
+ // Enable (auto-start on login) and start immediately
189
+ const enableResult = spawnSync('systemctl', ['--user', 'enable', '--now', SYSTEMD_UNIT], {
190
+ encoding: 'utf-8',
191
+ });
192
+
193
+ if (enableResult.status !== 0) {
194
+ const stderr = (enableResult.stderr || '').trim();
195
+ throw new Error(`systemctl enable failed: ${stderr}`);
196
+ }
197
+
198
+ // Enable lingering so the user service runs even when not logged in (optional, best-effort)
199
+ spawnSync('loginctl', ['enable-linger', os.userInfo().username], { encoding: 'utf-8' });
200
+ }
201
+
202
+ function systemdUninstall() {
203
+ if (!fs.existsSync(SYSTEMD_FILE)) {
204
+ return false;
205
+ }
206
+
207
+ // Stop and disable
208
+ spawnSync('systemctl', ['--user', 'disable', '--now', SYSTEMD_UNIT], { encoding: 'utf-8' });
209
+
210
+ fs.rmSync(SYSTEMD_FILE, { force: true });
211
+
212
+ // Reload to clean up
213
+ spawnSync('systemctl', ['--user', 'daemon-reload'], { encoding: 'utf-8' });
214
+
215
+ return true;
216
+ }
217
+
218
+ function systemdStatus() {
219
+ const installed = fs.existsSync(SYSTEMD_FILE);
220
+ if (!installed) {
221
+ return { installed: false, running: false, pid: null };
222
+ }
223
+
224
+ const result = spawnSync('systemctl', ['--user', 'is-active', SYSTEMD_UNIT], {
225
+ encoding: 'utf-8',
226
+ });
227
+ const active = (result.stdout || '').trim() === 'active';
228
+
229
+ let pid = null;
230
+ if (active) {
231
+ const showResult = spawnSync('systemctl', ['--user', 'show', SYSTEMD_UNIT, '--property=MainPID'], {
232
+ encoding: 'utf-8',
233
+ });
234
+ const pidMatch = (showResult.stdout || '').match(/MainPID=(\d+)/);
235
+ if (pidMatch) pid = parseInt(pidMatch[1], 10);
236
+ }
237
+
238
+ return { installed: true, running: active, pid };
239
+ }
240
+
241
+ // ── Platform Dispatcher ──────────────────────────────────────────────────────
242
+
243
+ function getPlatformAdapter() {
244
+ if (PLATFORM === 'darwin') {
245
+ return { install: launchdInstall, uninstall: launchdUninstall, status: launchdStatus, name: 'launchd' };
246
+ }
247
+ if (PLATFORM === 'linux') {
248
+ return { install: systemdInstall, uninstall: systemdUninstall, status: systemdStatus, name: 'systemd' };
249
+ }
250
+ return null;
251
+ }
252
+
253
+ // ── Command Handler ──────────────────────────────────────────────────────────
254
+
255
+ export async function cmdAutostart(args) {
256
+ const sub = args[0] || 'status';
257
+ const adapter = getPlatformAdapter();
258
+
259
+ if (!adapter) {
260
+ fail(`Autostart is not supported on ${PLATFORM}`);
261
+ info('Supported platforms: macOS (launchd), Linux (systemd)');
262
+ return;
263
+ }
264
+
265
+ switch (sub) {
266
+ case 'enable': {
267
+ // Verify the daemon script exists
268
+ if (!fs.existsSync(DAEMON_SCRIPT_ABS)) {
269
+ fail(`Daemon script not found at: ${DAEMON_SCRIPT_ABS}`);
270
+ info('Run "nha update" to re-download core files.');
271
+ return;
272
+ }
273
+
274
+ const currentStatus = adapter.status();
275
+ if (currentStatus.installed && currentStatus.running) {
276
+ warn('Autostart is already enabled and running.');
277
+ return;
278
+ }
279
+
280
+ try {
281
+ adapter.install();
282
+ const newStatus = adapter.status();
283
+ ok(`Autostart enabled via ${adapter.name}`);
284
+ if (newStatus.running) {
285
+ ok(`Daemon started (PID ${newStatus.pid})`);
286
+ } else {
287
+ info('Daemon will start on next login.');
288
+ }
289
+
290
+ if (adapter.name === 'launchd') {
291
+ info(`Plist: ${LAUNCHD_PLIST}`);
292
+ } else {
293
+ info(`Unit: ${SYSTEMD_FILE}`);
294
+ }
295
+ info('The daemon auto-restarts on crash (10s cooldown).');
296
+ } catch (err) {
297
+ fail(`Failed to enable autostart: ${err.message}`);
298
+ }
299
+ return;
300
+ }
301
+
302
+ case 'disable': {
303
+ try {
304
+ const removed = adapter.uninstall();
305
+ if (removed) {
306
+ ok('Autostart disabled. Daemon stopped and service removed.');
307
+ } else {
308
+ warn('Autostart was not enabled.');
309
+ }
310
+ } catch (err) {
311
+ fail(`Failed to disable autostart: ${err.message}`);
312
+ }
313
+ return;
314
+ }
315
+
316
+ case 'status': {
317
+ const status = adapter.status();
318
+ console.log(`\n ${BOLD}Autostart Status${NC} ${D}(${adapter.name})${NC}\n`);
319
+ console.log(` Installed: ${status.installed ? G + 'yes' + NC : D + 'no' + NC}`);
320
+ console.log(` Running: ${status.running ? G + 'yes' + NC + (status.pid ? ` (PID ${status.pid})` : '') : R + 'no' + NC}`);
321
+
322
+ if (adapter.name === 'launchd') {
323
+ console.log(` Plist: ${D}${LAUNCHD_PLIST}${NC}`);
324
+ } else {
325
+ console.log(` Unit: ${D}${SYSTEMD_FILE}${NC}`);
326
+ }
327
+
328
+ console.log(` Daemon: ${D}${DAEMON_SCRIPT_ABS}${NC}`);
329
+ console.log(` Node: ${D}${NODE_BIN}${NC}`);
330
+ console.log('');
331
+
332
+ if (!status.installed) {
333
+ info('Enable with: nha autostart enable');
334
+ }
335
+ return;
336
+ }
337
+
338
+ default:
339
+ fail(`Unknown: nha autostart ${sub}`);
340
+ info('Commands: enable, disable, status');
341
+ }
342
+ }
@@ -11,6 +11,7 @@
11
11
  import readline from 'readline';
12
12
  import { loadConfig } from '../config.mjs';
13
13
  import { callLLM } from '../services/llm.mjs';
14
+ import { loadChatHistory, saveChatHistory, extractMemory } from '../services/memory.mjs';
14
15
  import { fail, info, ok, warn, C, G, Y, D, W, BOLD, NC, M, R, B } from '../ui.mjs';
15
16
 
16
17
  // ── Mail + Calendar imports (unified router — Google or Microsoft) ───────────
@@ -493,8 +494,9 @@ async function handleSlashCommand(input, config, history) {
493
494
 
494
495
  if (trimmed === '/clear') {
495
496
  history.length = 0;
497
+ try { saveChatHistory([]); } catch { /* non-critical */ }
496
498
  console.clear();
497
- console.log(` ${G}Conversation cleared.${NC}`);
499
+ console.log(` ${G}Conversation cleared (memory preserved, chat history reset).${NC}`);
498
500
  return true;
499
501
  }
500
502
 
@@ -582,7 +584,11 @@ export async function cmdChat(args) {
582
584
  terminal: true,
583
585
  });
584
586
 
585
- const history = [];
587
+ // Load persisted chat history from previous sessions
588
+ const history = loadChatHistory();
589
+ if (history.length > 0) {
590
+ ok(`Loaded ${Math.floor(history.length / 2)} previous conversation turns from memory.`);
591
+ }
586
592
  const systemPrompt = buildSystemPrompt(initialContext);
587
593
 
588
594
  // ── Graceful exit ───────────────────────────────────────────────────────
@@ -695,6 +701,10 @@ export async function cmdChat(args) {
695
701
  history.shift();
696
702
  history.shift();
697
703
  }
704
+
705
+ // Persist chat history and extract episodic memory
706
+ try { saveChatHistory(history); } catch { /* non-critical */ }
707
+ try { extractMemory('chat', input, response); } catch { /* non-critical */ }
698
708
  } catch (err) {
699
709
  process.stdout.write('\r' + ' '.repeat(40) + '\r');
700
710
  console.log(`\n ${R}LLM error: ${err.message}${NC}\n`);
@@ -17,6 +17,20 @@ export async function cmdOps(args) {
17
17
  if (result.ok) {
18
18
  ok(`PAO daemon started (PID ${result.pid})`);
19
19
  info('Monitoring Gmail + Calendar. Notifications enabled.');
20
+
21
+ const config = loadConfig();
22
+ const hasTelegram = !!config.responder?.telegram?.token;
23
+ const hasDiscord = !!config.responder?.discord?.token;
24
+ if (hasTelegram || hasDiscord) {
25
+ const platforms = [hasTelegram && 'Telegram', hasDiscord && 'Discord'].filter(Boolean).join(' + ');
26
+ info(`Message responder active: ${platforms}`);
27
+ }
28
+
29
+ const proactive = config.ops?.proactive?.enabled !== false;
30
+ if (proactive) {
31
+ info('Proactive intelligence enabled (follow-ups, meeting prep, deadlines).');
32
+ }
33
+
20
34
  info('Run "nha ops status" to check. "nha ops stop" to halt.');
21
35
  } else {
22
36
  warn(result.message);
@@ -36,6 +50,8 @@ export async function cmdOps(args) {
36
50
 
37
51
  case 'status': {
38
52
  const status = getDaemonStatus();
53
+ const config = loadConfig();
54
+
39
55
  console.log(`\n ${BOLD}PAO Daemon Status${NC}\n`);
40
56
  console.log(` Running: ${status.running ? G + 'yes' + NC + ` (PID ${status.pid})` : R + 'no' + NC}`);
41
57
  if (status.startedAt) console.log(` Started: ${D}${status.startedAt}${NC}`);
@@ -43,6 +59,27 @@ export async function cmdOps(args) {
43
59
  if (status.lastCalendarCheck) console.log(` Last cal check: ${D}${status.lastCalendarCheck}${NC}`);
44
60
  if (status.lastPlanGenerated) console.log(` Last plan: ${D}${status.lastPlanGenerated}${NC}`);
45
61
  if (status.errors > 0) console.log(` Errors: ${Y}${status.errors}${NC}`);
62
+
63
+ // Proactive Intelligence Engine status
64
+ const proactive = status.proactive || {};
65
+ console.log(`\n ${BOLD}Proactive Intelligence${NC}\n`);
66
+ console.log(` Enabled: ${proactive.enabled !== false ? G + 'yes' + NC : D + 'no' + NC}`);
67
+ console.log(` Email follow-up: ${proactive.emailFollowUp !== false ? G + 'on' + NC : D + 'off' + NC}`);
68
+ console.log(` Meeting prep: ${proactive.meetingPrep !== false ? G + 'on' + NC : D + 'off' + NC}`);
69
+ console.log(` Pattern detect: ${proactive.patterns !== false ? G + 'on' + NC : D + 'off' + NC}`);
70
+ console.log(` Deadline alerts: ${proactive.deadlines !== false ? G + 'on' + NC : D + 'off' + NC}`);
71
+ if (status.lastProactiveCheck) console.log(` Last check: ${D}${status.lastProactiveCheck}${NC}`);
72
+ if (status.lastPatternDetection) console.log(` Last patterns: ${D}${status.lastPatternDetection}${NC}`);
73
+
74
+ // Message Responder status
75
+ const responder = status.responder || {};
76
+ const telegramConfigured = !!config.responder?.telegram?.token;
77
+ const discordConfigured = !!config.responder?.discord?.token;
78
+ console.log(`\n ${BOLD}Message Responder${NC}\n`);
79
+ console.log(` Telegram: ${responder.telegram ? G + 'active' + NC : telegramConfigured ? Y + 'configured (daemon restart needed)' + NC : D + 'not configured' + NC}`);
80
+ console.log(` Discord: ${responder.discord ? G + 'active' + NC : discordConfigured ? Y + 'configured (daemon restart needed)' + NC : D + 'not configured' + NC}`);
81
+ console.log(` Auto-route: ${config.responder?.autoRoute !== false ? G + 'keyword routing' + NC : D + 'CONDUCTOR only' + NC}`);
82
+
46
83
  console.log('');
47
84
  return;
48
85
  }