@sesamespace/hivemind 0.5.14 → 0.5.16

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/README.md CHANGED
@@ -67,6 +67,36 @@ cd packages/memory && cargo build --release && cd ../..
67
67
  | L3 | Semantic Memory | Promoted knowledge (high-access patterns) |
68
68
  | L4 | External Memory | Git, files, APIs (on-demand) |
69
69
 
70
+ ## Dashboard
71
+
72
+ Local web UI for debugging memory and LLM request formation.
73
+
74
+ ```
75
+ http://localhost:9485
76
+ ```
77
+
78
+ - **Request Inspector:** View every LLM call with full prompt breakdown (identity files, L3 knowledge, L2 episodes with scores, L1 history, config snapshot, token estimates)
79
+ - **Memory Browser:** Search L2 episodes, view/delete L3 entries per context
80
+ - **Context Overview:** List contexts with episode counts
81
+
82
+ Bound to localhost only, no auth required. Starts automatically with the agent.
83
+
84
+ ## Agent Introspection Commands
85
+
86
+ Use `hm:` prefix in DMs (not group chats — may collide with other agents' platforms):
87
+
88
+ | Command | Description |
89
+ |---------|-------------|
90
+ | `hm:status` | Agent health, memory daemon, model, active context |
91
+ | `hm:config` | Full configuration details |
92
+ | `hm:contexts` | All contexts with L1/L2 info |
93
+ | `hm:memory stats` | Episode counts and L3 entries per context |
94
+ | `hm:memory search <query>` | Search L2 memories with similarity scores |
95
+ | `hm:memory l3 [context]` | View promoted L3 knowledge entries |
96
+ | `hm:help` | List all available commands |
97
+
98
+ These bypass the LLM — deterministic, direct daemon queries.
99
+
70
100
  ## CLI Commands
71
101
 
72
102
  | Command | Description |
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FleetManager
3
- } from "./chunk-AGFKYYWT.js";
3
+ } from "./chunk-ZDL7HL6V.js";
4
4
 
5
5
  // packages/cli/src/commands/fleet.ts
6
6
  function formatUptime(seconds) {
@@ -183,4 +183,4 @@ Commands:
183
183
  export {
184
184
  runFleetCommand
185
185
  };
186
- //# sourceMappingURL=chunk-TRXLCIB4.js.map
186
+ //# sourceMappingURL=chunk-4QICI4U6.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  Watchdog
3
- } from "./chunk-AGFKYYWT.js";
3
+ } from "./chunk-ZDL7HL6V.js";
4
4
  import {
5
5
  defaultSentinelConfig,
6
6
  loadConfig
7
- } from "./chunk-QAOBO4W4.js";
7
+ } from "./chunk-BQCSFDSS.js";
8
8
 
9
9
  // packages/cli/src/commands/watchdog.ts
10
10
  import { resolve } from "path";
@@ -76,4 +76,4 @@ Options:
76
76
  export {
77
77
  runWatchdogCommand
78
78
  };
79
- //# sourceMappingURL=chunk-YMQR5I6C.js.map
79
+ //# sourceMappingURL=chunk-7DZAH77W.js.map
@@ -546,6 +546,7 @@ ${charter}
546
546
  Messages are prefixed with [sender_handle]: or [sender_handle in group chat]: to tell you who's talking.
547
547
  In group chats, multiple people (humans and agents) may be present. Address them by name when relevant.
548
548
  Don't repeat or quote these prefixes in your responses \u2014 just respond naturally.
549
+ Keep responses concise \u2014 especially in group chats. A few sentences is usually enough. Don't write essays.
549
550
  If you decide not to respond to a group message, reply with exactly: __SKIP__
550
551
  `;
551
552
  let contextInfo = "";
@@ -750,7 +751,7 @@ var Agent = class {
750
751
  let stripped = message.replace(/^\[.+?\s+in\s+group\s+chat\]:\s*/, "");
751
752
  stripped = stripped.replace(/^\[.+?\]:\s*/, "");
752
753
  const cmdText = stripped;
753
- if (/^\/status\b/i.test(cmdText)) {
754
+ if (/^hm[:\s]status\b/i.test(cmdText)) {
754
755
  const memoryOk = await this.memory.healthCheck();
755
756
  const contexts = this.contextManager.listContexts();
756
757
  let contextStats = "";
@@ -778,7 +779,7 @@ var Agent = class {
778
779
  ].join("\n");
779
780
  return { content: response, model: "system", context: activeCtx };
780
781
  }
781
- if (/^\/memory\s+stats\b/i.test(cmdText)) {
782
+ if (/^hm[:\s]memory\s+stats\b/i.test(cmdText)) {
782
783
  try {
783
784
  const contexts = await this.memory.listContexts();
784
785
  let totalEpisodes = 0;
@@ -810,7 +811,7 @@ var Agent = class {
810
811
  return { content: `Memory stats failed: ${err.message}`, model: "system", context: activeCtx };
811
812
  }
812
813
  }
813
- const memSearchMatch = cmdText.match(/^\/memory\s+search\s+(.+)/i);
814
+ const memSearchMatch = cmdText.match(/^hm[:\s]memory\s+search\s+(.+)/i);
814
815
  if (memSearchMatch) {
815
816
  const query = memSearchMatch[1].trim();
816
817
  try {
@@ -831,7 +832,7 @@ var Agent = class {
831
832
  return { content: `Memory search failed: ${err.message}`, model: "system", context: activeCtx };
832
833
  }
833
834
  }
834
- const l3Match = cmdText.match(/^\/memory\s+l3(?:\s+(\S+))?/i);
835
+ const l3Match = cmdText.match(/^hm[:\s]memory\s+l3(?:\s+(\S+))?/i);
835
836
  if (l3Match) {
836
837
  const targetCtx = l3Match[1] || activeCtx;
837
838
  try {
@@ -852,7 +853,7 @@ var Agent = class {
852
853
  return { content: `L3 query failed: ${err.message}`, model: "system", context: activeCtx };
853
854
  }
854
855
  }
855
- if (/^\/config\b/i.test(cmdText)) {
856
+ if (/^hm[:\s]config\b/i.test(cmdText)) {
856
857
  const response = [
857
858
  `## Configuration`,
858
859
  ``,
@@ -878,7 +879,7 @@ var Agent = class {
878
879
  ].join("\n");
879
880
  return { content: response, model: "system", context: activeCtx };
880
881
  }
881
- if (/^\/contexts\b/i.test(cmdText)) {
882
+ if (/^hm[:\s]contexts\b/i.test(cmdText)) {
882
883
  const localContexts = this.contextManager.listContexts();
883
884
  let daemonInfo = "";
884
885
  try {
@@ -905,17 +906,18 @@ var Agent = class {
905
906
  ].join("\n");
906
907
  return { content: response, model: "system", context: activeCtx };
907
908
  }
908
- if (/^\/help\b/i.test(cmdText)) {
909
+ if (/^hm[:\s]help\b/i.test(cmdText)) {
909
910
  const response = [
910
911
  `## Available Commands`,
911
912
  ``,
912
913
  `### Introspection`,
913
- `- \`/status\` \u2014 Agent health, memory status, config summary`,
914
- `- \`/config\` \u2014 Full configuration details`,
915
- `- \`/contexts\` \u2014 List all contexts with L1/L2 info`,
916
- `- \`/memory stats\` \u2014 Episode counts, L3 entries per context`,
917
- `- \`/memory search <query>\` \u2014 Search L2 memories with scores`,
918
- `- \`/memory l3 [context]\` \u2014 Show L3 knowledge entries`,
914
+ `- \`hm:status\` \u2014 Agent health, memory status, config summary`,
915
+ `- \`hm:config\` \u2014 Full configuration details`,
916
+ `- \`hm:contexts\` \u2014 List all contexts with L1/L2 info`,
917
+ `- \`hm:memory stats\` \u2014 Episode counts, L3 entries per context`,
918
+ `- \`hm:memory search <query>\` \u2014 Search L2 memories with scores`,
919
+ `- \`hm:memory l3 [context]\` \u2014 Show L3 knowledge entries`,
920
+ `- \`hm:help\` \u2014 This help message`,
919
921
  ``,
920
922
  `### Context Management`,
921
923
  `- \`switch to <name>\` \u2014 Switch active context`,
@@ -1997,58 +1999,42 @@ var HEALTH_PORT = 9484;
1997
1999
  var HEALTH_PATH = "/health";
1998
2000
 
1999
2001
  // packages/runtime/src/request-logger.ts
2000
- import Database from "better-sqlite3";
2001
2002
  import { randomUUID } from "crypto";
2002
- import { mkdirSync, existsSync as existsSync3 } from "fs";
2003
+ import { mkdirSync, existsSync as existsSync3, appendFileSync, readFileSync as readFileSync4, writeFileSync } from "fs";
2003
2004
  import { dirname as dirname3 } from "path";
2004
2005
  var RequestLogger = class {
2005
- db;
2006
+ logPath;
2007
+ maxAgeDays = 7;
2006
2008
  constructor(dbPath) {
2007
- const dir = dirname3(dbPath);
2009
+ this.logPath = dbPath.replace(/\.db$/, ".jsonl");
2010
+ if (this.logPath === dbPath) this.logPath = dbPath + ".jsonl";
2011
+ const dir = dirname3(this.logPath);
2008
2012
  if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
2009
- this.db = new Database(dbPath);
2010
- this.db.pragma("journal_mode = WAL");
2011
- this.db.pragma("synchronous = NORMAL");
2012
- this.init();
2013
2013
  this.prune();
2014
2014
  }
2015
- init() {
2016
- this.db.exec(`
2017
- CREATE TABLE IF NOT EXISTS request_logs (
2018
- id TEXT PRIMARY KEY,
2019
- timestamp TEXT NOT NULL,
2020
- context TEXT NOT NULL,
2021
- context_switched INTEGER NOT NULL DEFAULT 0,
2022
- routing_reason TEXT NOT NULL DEFAULT '',
2023
- channel_id TEXT NOT NULL DEFAULT '',
2024
- channel_kind TEXT NOT NULL DEFAULT '',
2025
- sender_handle TEXT NOT NULL DEFAULT '',
2026
- raw_message TEXT NOT NULL,
2027
- system_prompt_components TEXT NOT NULL,
2028
- conversation_history TEXT NOT NULL,
2029
- user_message TEXT NOT NULL,
2030
- response_content TEXT NOT NULL,
2031
- response_model TEXT NOT NULL,
2032
- response_latency_ms INTEGER NOT NULL,
2033
- response_skipped INTEGER NOT NULL DEFAULT 0,
2034
- config_snapshot TEXT NOT NULL,
2035
- token_est_system INTEGER NOT NULL DEFAULT 0,
2036
- token_est_history INTEGER NOT NULL DEFAULT 0,
2037
- token_est_user INTEGER NOT NULL DEFAULT 0,
2038
- token_est_total INTEGER NOT NULL DEFAULT 0
2039
- )
2040
- `);
2041
- this.db.exec(`
2042
- CREATE INDEX IF NOT EXISTS idx_request_logs_timestamp ON request_logs(timestamp DESC);
2043
- CREATE INDEX IF NOT EXISTS idx_request_logs_context ON request_logs(context);
2044
- CREATE INDEX IF NOT EXISTS idx_request_logs_sender ON request_logs(sender_handle);
2045
- `);
2046
- }
2047
2015
  prune() {
2048
- const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString();
2049
- const result = this.db.prepare("DELETE FROM request_logs WHERE timestamp < ?").run(cutoff);
2050
- if (result.changes > 0) {
2051
- console.log(`[dashboard] Pruned ${result.changes} old request logs`);
2016
+ if (!existsSync3(this.logPath)) return;
2017
+ const cutoff = new Date(Date.now() - this.maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
2018
+ try {
2019
+ const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2020
+ const kept = [];
2021
+ let pruned = 0;
2022
+ for (const line of lines) {
2023
+ try {
2024
+ const entry = JSON.parse(line);
2025
+ if (entry.timestamp >= cutoff) {
2026
+ kept.push(line);
2027
+ } else {
2028
+ pruned++;
2029
+ }
2030
+ } catch {
2031
+ }
2032
+ }
2033
+ if (pruned > 0) {
2034
+ writeFileSync(this.logPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
2035
+ console.log(`[dashboard] Pruned ${pruned} old request logs`);
2036
+ }
2037
+ } catch {
2052
2038
  }
2053
2039
  }
2054
2040
  log(entry) {
@@ -2059,78 +2045,73 @@ var RequestLogger = class {
2059
2045
  entry.conversationHistory.reduce((sum, m) => sum + m.content.length, 0) / 4
2060
2046
  );
2061
2047
  const userTokens = Math.ceil(entry.userMessage.length / 4);
2062
- this.db.prepare(
2063
- `INSERT INTO request_logs (
2064
- id, timestamp, context, context_switched, routing_reason,
2065
- channel_id, channel_kind, sender_handle, raw_message,
2066
- system_prompt_components, conversation_history, user_message,
2067
- response_content, response_model, response_latency_ms, response_skipped,
2068
- config_snapshot,
2069
- token_est_system, token_est_history, token_est_user, token_est_total
2070
- ) VALUES (
2071
- ?, ?, ?, ?, ?,
2072
- ?, ?, ?, ?,
2073
- ?, ?, ?,
2074
- ?, ?, ?, ?,
2075
- ?,
2076
- ?, ?, ?, ?
2077
- )`
2078
- ).run(
2048
+ const record = {
2079
2049
  id,
2080
2050
  timestamp,
2081
- entry.context,
2082
- entry.contextSwitched ? 1 : 0,
2083
- entry.routingReason,
2084
- entry.channelId ?? "",
2085
- entry.channelKind ?? "",
2086
- entry.senderHandle ?? "",
2087
- entry.rawMessage,
2088
- JSON.stringify(entry.systemPromptComponents),
2089
- JSON.stringify(entry.conversationHistory),
2090
- entry.userMessage,
2091
- entry.response.content,
2092
- entry.response.model,
2093
- entry.response.latencyMs,
2094
- entry.response.skipped ? 1 : 0,
2095
- JSON.stringify(entry.config),
2096
- sysTokens,
2097
- histTokens,
2098
- userTokens,
2099
- sysTokens + histTokens + userTokens
2100
- );
2051
+ context: entry.context,
2052
+ context_switched: entry.contextSwitched,
2053
+ routing_reason: entry.routingReason,
2054
+ channel_id: entry.channelId ?? "",
2055
+ channel_kind: entry.channelKind ?? "",
2056
+ sender_handle: entry.senderHandle ?? "",
2057
+ raw_message: entry.rawMessage,
2058
+ system_prompt_components: JSON.stringify(entry.systemPromptComponents),
2059
+ conversation_history: JSON.stringify(entry.conversationHistory),
2060
+ user_message: entry.userMessage,
2061
+ response_content: entry.response.content,
2062
+ response_model: entry.response.model,
2063
+ response_latency_ms: entry.response.latencyMs,
2064
+ response_skipped: entry.response.skipped,
2065
+ config_snapshot: JSON.stringify(entry.config),
2066
+ token_est_system: sysTokens,
2067
+ token_est_history: histTokens,
2068
+ token_est_user: userTokens,
2069
+ token_est_total: sysTokens + histTokens + userTokens
2070
+ };
2071
+ appendFileSync(this.logPath, JSON.stringify(record) + "\n");
2101
2072
  return id;
2102
2073
  }
2103
2074
  getRequests(opts = {}) {
2104
2075
  const limit = opts.limit ?? 50;
2105
2076
  const offset = opts.offset ?? 0;
2106
- const conditions = [];
2107
- const params = [];
2077
+ let entries = this.readAll();
2108
2078
  if (opts.context) {
2109
- conditions.push("context = ?");
2110
- params.push(opts.context);
2079
+ entries = entries.filter((e) => e.context === opts.context);
2111
2080
  }
2112
2081
  if (opts.sender) {
2113
- conditions.push("sender_handle = ?");
2114
- params.push(opts.sender);
2115
- }
2116
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
2117
- const total = this.db.prepare(`SELECT COUNT(*) as count FROM request_logs ${where}`).get(...params).count;
2118
- const requests = this.db.prepare(
2119
- `SELECT * FROM request_logs ${where} ORDER BY timestamp DESC LIMIT ? OFFSET ?`
2120
- ).all(...params, limit, offset);
2082
+ entries = entries.filter((e) => e.sender_handle === opts.sender);
2083
+ }
2084
+ entries.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
2085
+ const total = entries.length;
2086
+ const requests = entries.slice(offset, offset + limit);
2121
2087
  return { requests, total };
2122
2088
  }
2123
2089
  getRequest(id) {
2124
- return this.db.prepare("SELECT * FROM request_logs WHERE id = ?").get(id);
2090
+ return this.readAll().find((e) => e.id === id);
2125
2091
  }
2126
2092
  close() {
2127
- this.db.close();
2093
+ }
2094
+ readAll() {
2095
+ if (!existsSync3(this.logPath)) return [];
2096
+ try {
2097
+ const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2098
+ const entries = [];
2099
+ for (const line of lines) {
2100
+ try {
2101
+ entries.push(JSON.parse(line));
2102
+ } catch {
2103
+ }
2104
+ }
2105
+ return entries;
2106
+ } catch {
2107
+ return [];
2108
+ }
2128
2109
  }
2129
2110
  };
2130
2111
 
2131
2112
  // packages/runtime/src/dashboard.ts
2132
2113
  import { createServer } from "http";
2133
- import { readFileSync as readFileSync4 } from "fs";
2114
+ import { readFileSync as readFileSync5 } from "fs";
2134
2115
  import { resolve as resolve4, dirname as dirname4 } from "path";
2135
2116
  import { fileURLToPath as fileURLToPath2 } from "url";
2136
2117
  var __dirname = dirname4(fileURLToPath2(import.meta.url));
@@ -2140,7 +2121,7 @@ function getSpaHtml() {
2140
2121
  if (!spaHtml) {
2141
2122
  for (const dir of [__dirname, resolve4(__dirname, "../src")]) {
2142
2123
  try {
2143
- spaHtml = readFileSync4(resolve4(dir, "dashboard.html"), "utf-8");
2124
+ spaHtml = readFileSync5(resolve4(dir, "dashboard.html"), "utf-8");
2144
2125
  break;
2145
2126
  } catch {
2146
2127
  }
@@ -2262,13 +2243,13 @@ function startDashboardServer(requestLogger, memoryConfig) {
2262
2243
  }
2263
2244
 
2264
2245
  // packages/runtime/src/pipeline.ts
2265
- import { readFileSync as readFileSync5, writeFileSync, unlinkSync } from "fs";
2246
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync } from "fs";
2266
2247
  import { resolve as resolve5, dirname as dirname5 } from "path";
2267
2248
  import { fileURLToPath as fileURLToPath3 } from "url";
2268
2249
  var PACKAGE_VERSION = "unknown";
2269
2250
  try {
2270
2251
  const __dirname2 = dirname5(fileURLToPath3(import.meta.url));
2271
- const pkg = JSON.parse(readFileSync5(resolve5(__dirname2, "../package.json"), "utf-8"));
2252
+ const pkg = JSON.parse(readFileSync6(resolve5(__dirname2, "../package.json"), "utf-8"));
2272
2253
  PACKAGE_VERSION = pkg.version ?? "unknown";
2273
2254
  } catch {
2274
2255
  }
@@ -2299,7 +2280,7 @@ function startHealthServer(port) {
2299
2280
  return server;
2300
2281
  }
2301
2282
  function writePidFile(path) {
2302
- writeFileSync(path, String(process.pid));
2283
+ writeFileSync2(path, String(process.pid));
2303
2284
  console.log(`[hivemind] PID file written: ${path}`);
2304
2285
  }
2305
2286
  function cleanupPidFile(path) {
@@ -2361,8 +2342,41 @@ async function startSesameLoop(config, agent) {
2361
2342
  };
2362
2343
  process.on("SIGTERM", () => shutdown("SIGTERM"));
2363
2344
  process.on("SIGINT", () => shutdown("SIGINT"));
2345
+ const processedIds = /* @__PURE__ */ new Set();
2346
+ const MAX_SEEN = 500;
2347
+ let processing = false;
2348
+ const messageQueue = [];
2349
+ async function processQueue() {
2350
+ if (processing || messageQueue.length === 0) return;
2351
+ processing = true;
2352
+ while (messageQueue.length > 0) {
2353
+ const msg = messageQueue.shift();
2354
+ await handleMessage(msg);
2355
+ }
2356
+ processing = false;
2357
+ }
2364
2358
  sesame.onMessage(async (msg) => {
2365
2359
  if (shuttingDown) return;
2360
+ if (processedIds.has(msg.id)) {
2361
+ console.log(`[sesame] Skipping duplicate message ${msg.id}`);
2362
+ return;
2363
+ }
2364
+ processedIds.add(msg.id);
2365
+ if (processedIds.size > MAX_SEEN) {
2366
+ const iter = processedIds.values();
2367
+ for (let i = 0; i < 100; i++) iter.next();
2368
+ const keep = /* @__PURE__ */ new Set();
2369
+ for (const id of processedIds) {
2370
+ if (keep.size >= MAX_SEEN - 100) break;
2371
+ keep.add(id);
2372
+ }
2373
+ processedIds.clear();
2374
+ for (const id of keep) processedIds.add(id);
2375
+ }
2376
+ messageQueue.push(msg);
2377
+ processQueue();
2378
+ });
2379
+ async function handleMessage(msg) {
2366
2380
  console.log(`[sesame] ${msg.author.handle} (${msg.channelKind}): ${msg.content}`);
2367
2381
  sesame.startTyping(msg.channelId);
2368
2382
  sesame.updatePresence("thinking", { detail: `Replying to ${msg.author.handle}`, emoji: "\u{1F4AD}" });
@@ -2397,7 +2411,7 @@ async function startSesameLoop(config, agent) {
2397
2411
  sesame.updatePresence("online", { emoji: "\u{1F7E2}" });
2398
2412
  console.error("[sesame] Error processing message:", err.message);
2399
2413
  }
2400
- });
2414
+ }
2401
2415
  await sesame.connect();
2402
2416
  sesameConnected = true;
2403
2417
  console.log("[hivemind] Listening for Sesame messages");
@@ -2944,4 +2958,4 @@ smol-toml/dist/index.js:
2944
2958
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2945
2959
  *)
2946
2960
  */
2947
- //# sourceMappingURL=chunk-QAOBO4W4.js.map
2961
+ //# sourceMappingURL=chunk-BQCSFDSS.js.map