@sesamespace/hivemind 0.6.0 → 0.7.0

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.
@@ -37,27 +37,43 @@ var LLMClient = class {
37
37
  body.tools = tools;
38
38
  body.tool_choice = "auto";
39
39
  }
40
- const resp = await fetch(`${this.baseUrl}/chat/completions`, {
41
- method: "POST",
42
- headers: {
43
- "Content-Type": "application/json",
44
- ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
45
- },
46
- body: JSON.stringify(body)
47
- });
48
- if (!resp.ok) {
49
- const text = await resp.text();
50
- throw new Error(`LLM request failed: ${resp.status} ${text}`);
40
+ const RETRYABLE_CODES = [429, 500, 502, 503, 529];
41
+ const MAX_RETRIES = 3;
42
+ let lastError = null;
43
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
44
+ if (attempt > 0) {
45
+ const delayMs = Math.pow(2, attempt) * 1e3;
46
+ console.log(`[llm] Retry ${attempt}/${MAX_RETRIES} after ${delayMs}ms...`);
47
+ await new Promise((r) => setTimeout(r, delayMs));
48
+ }
49
+ const resp = await fetch(`${this.baseUrl}/chat/completions`, {
50
+ method: "POST",
51
+ headers: {
52
+ "Content-Type": "application/json",
53
+ ...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
54
+ },
55
+ body: JSON.stringify(body)
56
+ });
57
+ if (!resp.ok) {
58
+ const text = await resp.text();
59
+ lastError = new Error(`LLM request failed: ${resp.status} ${text}`);
60
+ if (RETRYABLE_CODES.includes(resp.status) && attempt < MAX_RETRIES) {
61
+ console.warn(`[llm] Retryable error ${resp.status}: ${text.slice(0, 200)}`);
62
+ continue;
63
+ }
64
+ throw lastError;
65
+ }
66
+ const data = await resp.json();
67
+ const choice = data.choices[0];
68
+ return {
69
+ content: choice.message.content ?? "",
70
+ model: data.model,
71
+ tool_calls: choice.message.tool_calls,
72
+ finish_reason: choice.finish_reason,
73
+ usage: data.usage
74
+ };
51
75
  }
52
- const data = await resp.json();
53
- const choice = data.choices[0];
54
- return {
55
- content: choice.message.content ?? "",
56
- model: data.model,
57
- tool_calls: choice.message.tool_calls,
58
- finish_reason: choice.finish_reason,
59
- usage: data.usage
60
- };
76
+ throw lastError ?? new Error("LLM request failed after retries");
61
77
  }
62
78
  };
63
79
 
@@ -472,6 +488,92 @@ var TaskEngine = class {
472
488
  // packages/runtime/src/prompt.ts
473
489
  import { readFileSync, existsSync, readdirSync } from "fs";
474
490
  import { resolve } from "path";
491
+ function loadMemoryFiles(workspace, contextName) {
492
+ const result = { global: null, context: null };
493
+ if (!workspace) return result;
494
+ const globalPath = resolve(workspace, "MEMORY.md");
495
+ if (existsSync(globalPath)) {
496
+ try {
497
+ const content = readFileSync(globalPath, "utf-8").trim();
498
+ if (content) result.global = content;
499
+ } catch {
500
+ }
501
+ }
502
+ if (contextName !== "global") {
503
+ const contextPath = resolve(workspace, "contexts", contextName, "MEMORY.md");
504
+ if (existsSync(contextPath)) {
505
+ try {
506
+ const content = readFileSync(contextPath, "utf-8").trim();
507
+ if (content) result.context = content;
508
+ } catch {
509
+ }
510
+ }
511
+ }
512
+ return result;
513
+ }
514
+ function discoverSkills(workspace) {
515
+ if (!workspace) return [];
516
+ const skillsDir = resolve(workspace, "skills");
517
+ if (!existsSync(skillsDir)) return [];
518
+ const skills = [];
519
+ try {
520
+ const entries = readdirSync(skillsDir);
521
+ for (const entry of entries) {
522
+ const skillMdPath = resolve(skillsDir, entry, "SKILL.md");
523
+ if (!existsSync(skillMdPath)) continue;
524
+ try {
525
+ const content = readFileSync(skillMdPath, "utf-8");
526
+ const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
527
+ let name = entry;
528
+ let description = "";
529
+ if (fmMatch) {
530
+ const fm = fmMatch[1];
531
+ const nameMatch = fm.match(/^name:\s*(.+)$/m);
532
+ const descMatch = fm.match(/^description:\s*(.+)$/m);
533
+ if (nameMatch) name = nameMatch[1].trim().replace(/^["']|["']$/g, "");
534
+ if (descMatch) description = descMatch[1].trim().replace(/^["']|["']$/g, "");
535
+ }
536
+ skills.push({ name, description, path: skillMdPath });
537
+ } catch {
538
+ }
539
+ }
540
+ } catch {
541
+ }
542
+ return skills;
543
+ }
544
+ function buildEventsDoc(dataDir) {
545
+ if (!dataDir) return "";
546
+ return `
547
+ ## Events (Scheduling)
548
+ You can schedule events by writing JSON files to the events directory.
549
+
550
+ ### Event Types
551
+ - **immediate**: Triggers as soon as the file is created
552
+ \`{"type": "immediate", "channelId": "<channel>", "text": "Your message"}\`
553
+ - **one-shot**: Triggers once at a specific time, then auto-deletes
554
+ \`{"type": "one-shot", "channelId": "<channel>", "text": "Reminder text", "at": "2026-03-01T09:00:00-08:00"}\`
555
+ - **periodic**: Triggers on a cron schedule
556
+ \`{"type": "periodic", "channelId": "<channel>", "text": "Check inbox", "schedule": "0 9 * * 1-5", "timezone": "America/Los_Angeles"}\`
557
+
558
+ ### Cron Format
559
+ \`minute hour day-of-month month day-of-week\`
560
+ - \`0 9 * * *\` = daily at 9:00
561
+ - \`0 9 * * 1-5\` = weekdays at 9:00
562
+ - \`*/30 * * * *\` = every 30 minutes
563
+
564
+ ### Creating Events
565
+ Use the write_file tool to create JSON files in the events directory.
566
+ Use unique filenames (include timestamp or description): \`reminder-dentist-1709312400.json\`
567
+
568
+ ### Managing Events
569
+ - List: use shell tool \`ls ~/hivemind/data/events/\`
570
+ - Delete/cancel: use shell tool \`rm ~/hivemind/data/events/filename.json\`
571
+
572
+ ### Silent Completion
573
+ For periodic events with nothing to report, respond with exactly \`[SILENT]\` (no other text).
574
+ This prevents spamming the channel.
575
+ `;
576
+ }
475
577
  var charterCache = null;
476
578
  function loadCharter(path) {
477
579
  if (charterCache && charterCache.path === path) return charterCache.content;
@@ -530,7 +632,19 @@ function loadWorkspaceFiles(dir) {
530
632
  }
531
633
  return files;
532
634
  }
533
- function buildSystemPrompt(config, episodes, contextName = "global", l3Knowledge = []) {
635
+ function buildSystemPrompt(configOrOpts, episodes, contextName = "global", l3Knowledge = []) {
636
+ let config;
637
+ let dataDir;
638
+ if ("config" in configOrOpts) {
639
+ config = configOrOpts.config;
640
+ episodes = configOrOpts.episodes;
641
+ contextName = configOrOpts.contextName ?? "global";
642
+ l3Knowledge = configOrOpts.l3Knowledge ?? [];
643
+ dataDir = configOrOpts.dataDir;
644
+ } else {
645
+ config = configOrOpts;
646
+ }
647
+ const resolvedEpisodes = episodes ?? [];
534
648
  let prompt = `You are ${config.name}. ${config.personality}
535
649
  `;
536
650
  let identityText = prompt;
@@ -575,6 +689,36 @@ You are currently working in the "${contextName}" project context.
575
689
  `;
576
690
  prompt += contextInfo;
577
691
  }
692
+ const memoryFiles = loadMemoryFiles(config.workspace, contextName);
693
+ if (memoryFiles.global || memoryFiles.context) {
694
+ prompt += "\n## Working Memory (agent-managed)\n";
695
+ if (memoryFiles.global) {
696
+ prompt += `
697
+ ### Global Memory
698
+ ${memoryFiles.global}
699
+ `;
700
+ }
701
+ if (memoryFiles.context) {
702
+ prompt += `
703
+ ### ${contextName} Memory
704
+ ${memoryFiles.context}
705
+ `;
706
+ }
707
+ prompt += "\nUpdate these files with write_file when you learn something important. They persist across sessions.\n";
708
+ }
709
+ const skills = discoverSkills(config.workspace);
710
+ if (skills.length > 0) {
711
+ prompt += "\n## Available Skills\n\n";
712
+ for (const skill of skills) {
713
+ prompt += `- **${skill.name}**: ${skill.description} \u2192 read \`${skill.path}\` for instructions
714
+ `;
715
+ }
716
+ prompt += "\nTo use a skill, read its SKILL.md with read_file. To create a new skill, write a SKILL.md to workspace/skills/<name>/SKILL.md\n";
717
+ }
718
+ const eventsDoc = buildEventsDoc(dataDir);
719
+ if (eventsDoc) {
720
+ prompt += eventsDoc;
721
+ }
578
722
  if (l3Knowledge.length > 0) {
579
723
  prompt += "\n## Established Knowledge (learned patterns)\n\n";
580
724
  for (const entry of l3Knowledge) {
@@ -582,9 +726,9 @@ You are currently working in the "${contextName}" project context.
582
726
  `;
583
727
  }
584
728
  }
585
- if (episodes.length > 0) {
729
+ if (resolvedEpisodes.length > 0) {
586
730
  prompt += "\n## Relevant memories from previous conversations\n\n";
587
- for (const ep of episodes) {
731
+ for (const ep of resolvedEpisodes) {
588
732
  const timeAgo = formatTimeAgo(ep.timestamp);
589
733
  const ctxLabel = ep.context_name !== contextName ? ` [from: ${ep.context_name}]` : "";
590
734
  prompt += `[${timeAgo}]${ctxLabel} ${ep.role}: ${ep.content}
@@ -597,7 +741,7 @@ You are currently working in the "${contextName}" project context.
597
741
  components: {
598
742
  identity: identityText,
599
743
  l3Knowledge: l3Knowledge.map((e) => e.content),
600
- l2Episodes: episodes.map((ep) => ({
744
+ l2Episodes: resolvedEpisodes.map((ep) => ({
601
745
  id: ep.id,
602
746
  content: ep.content,
603
747
  score: ep.score,
@@ -630,7 +774,260 @@ function formatTimeAgo(timestamp) {
630
774
  return date.toLocaleDateString();
631
775
  }
632
776
 
777
+ // packages/runtime/src/session.ts
778
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, appendFileSync } from "fs";
779
+ import { join as join2 } from "path";
780
+ import { randomUUID } from "crypto";
781
+ var SessionStore = class {
782
+ sessionDir;
783
+ constructor(dataDir) {
784
+ this.sessionDir = join2(dataDir, "sessions");
785
+ if (!existsSync2(this.sessionDir)) {
786
+ mkdirSync(this.sessionDir, { recursive: true });
787
+ }
788
+ }
789
+ filePath(contextName) {
790
+ const safe = contextName.replace(/[^a-zA-Z0-9_-]/g, "_");
791
+ return join2(this.sessionDir, `${safe}.jsonl`);
792
+ }
793
+ /**
794
+ * Append a message to the session JSONL file.
795
+ */
796
+ append(contextName, message, parentId) {
797
+ const fp = this.filePath(contextName);
798
+ const entry = {
799
+ id: randomUUID(),
800
+ parentId: parentId ?? null,
801
+ role: message.role,
802
+ content: message.content,
803
+ tool_calls: message.tool_calls,
804
+ tool_call_id: message.tool_call_id,
805
+ timestamp: Date.now()
806
+ };
807
+ appendFileSync(fp, JSON.stringify(entry) + "\n");
808
+ return entry.id;
809
+ }
810
+ /**
811
+ * Load all messages from a context's session file.
812
+ */
813
+ load(contextName) {
814
+ const fp = this.filePath(contextName);
815
+ if (!existsSync2(fp)) return [];
816
+ const lines = readFileSync2(fp, "utf-8").trim().split("\n").filter(Boolean);
817
+ const messages = [];
818
+ for (const line of lines) {
819
+ try {
820
+ const entry = JSON.parse(line);
821
+ const msg = {
822
+ role: entry.role,
823
+ content: entry.content
824
+ };
825
+ if (entry.tool_calls) msg.tool_calls = entry.tool_calls;
826
+ if (entry.tool_call_id) msg.tool_call_id = entry.tool_call_id;
827
+ messages.push(msg);
828
+ } catch {
829
+ }
830
+ }
831
+ return messages;
832
+ }
833
+ /**
834
+ * Get the N most recent messages from a context.
835
+ */
836
+ getRecentMessages(contextName, n) {
837
+ const all = this.load(contextName);
838
+ return all.slice(-n);
839
+ }
840
+ /**
841
+ * Simple string search across a context's history.
842
+ * Returns matching entries with timestamps.
843
+ */
844
+ grep(contextName, query, limit = 20) {
845
+ const fp = this.filePath(contextName);
846
+ if (!existsSync2(fp)) return [];
847
+ const lines = readFileSync2(fp, "utf-8").trim().split("\n").filter(Boolean);
848
+ const results = [];
849
+ const lowerQuery = query.toLowerCase();
850
+ for (const line of lines) {
851
+ try {
852
+ const entry = JSON.parse(line);
853
+ if (entry.content && entry.content.toLowerCase().includes(lowerQuery)) {
854
+ results.push({
855
+ role: entry.role,
856
+ content: entry.content,
857
+ timestamp: entry.timestamp
858
+ });
859
+ }
860
+ } catch {
861
+ }
862
+ }
863
+ return results.slice(-limit);
864
+ }
865
+ /**
866
+ * Get total message count for a context.
867
+ */
868
+ messageCount(contextName) {
869
+ const fp = this.filePath(contextName);
870
+ if (!existsSync2(fp)) return 0;
871
+ const content = readFileSync2(fp, "utf-8").trim();
872
+ if (!content) return 0;
873
+ return content.split("\n").length;
874
+ }
875
+ };
876
+ function estimateTokens(text) {
877
+ if (!text) return 0;
878
+ return Math.ceil(text.length / 4);
879
+ }
880
+ function estimateMessageTokens(messages) {
881
+ let total = 0;
882
+ for (const msg of messages) {
883
+ if (msg.content) total += estimateTokens(msg.content);
884
+ if (msg.tool_calls) {
885
+ for (const call of msg.tool_calls) {
886
+ total += estimateTokens(call.function.name);
887
+ total += estimateTokens(call.function.arguments);
888
+ }
889
+ }
890
+ }
891
+ total += messages.length * 4;
892
+ return total;
893
+ }
894
+ var MODEL_CONTEXT_WINDOWS = {
895
+ "claude-sonnet-4.5": 2e5,
896
+ "claude-sonnet-4-5": 2e5,
897
+ "anthropic/claude-sonnet-4.5": 2e5,
898
+ "anthropic/claude-sonnet-4-5-20250514": 2e5,
899
+ "claude-opus-4": 2e5,
900
+ "anthropic/claude-opus-4": 2e5,
901
+ "claude-3.5-sonnet": 2e5,
902
+ "gpt-4o": 128e3,
903
+ "gpt-4o-mini": 128e3,
904
+ "gpt-4-turbo": 128e3,
905
+ "gemini-2.5-pro": 1e6,
906
+ "gemini-2.0-flash": 1e6
907
+ };
908
+ function getModelContextWindow(model) {
909
+ if (MODEL_CONTEXT_WINDOWS[model]) return MODEL_CONTEXT_WINDOWS[model];
910
+ const parts = model.split("/");
911
+ const modelName = parts[parts.length - 1];
912
+ if (MODEL_CONTEXT_WINDOWS[modelName]) return MODEL_CONTEXT_WINDOWS[modelName];
913
+ for (const [key, value] of Object.entries(MODEL_CONTEXT_WINDOWS)) {
914
+ if (model.includes(key)) return value;
915
+ }
916
+ return 2e5;
917
+ }
918
+
919
+ // packages/runtime/src/compaction.ts
920
+ var CompactionManager = class {
921
+ /** Compact when total tokens exceed this fraction of context window */
922
+ COMPACT_THRESHOLD = 0.75;
923
+ /** Keep this many recent turns (turn = user + assistant) */
924
+ KEEP_RECENT_TURNS = 15;
925
+ /** Reserve tokens for response */
926
+ RESERVE_TOKENS = 16384;
927
+ /**
928
+ * Check if compaction is needed.
929
+ */
930
+ shouldCompact(messages, systemPromptTokens, model) {
931
+ const contextWindow = getModelContextWindow(model);
932
+ const messageTokens = estimateMessageTokens(messages);
933
+ const totalTokens = systemPromptTokens + messageTokens;
934
+ const threshold = (contextWindow - this.RESERVE_TOKENS) * this.COMPACT_THRESHOLD;
935
+ return totalTokens > threshold;
936
+ }
937
+ /**
938
+ * Perform lossless compaction:
939
+ * 1. Save all old messages to L2 (full preservation)
940
+ * 2. Ask LLM to summarize old messages
941
+ * 3. Return summary + recent messages
942
+ */
943
+ async compact(messages, contextName, memoryClient, llmClient, model) {
944
+ const tokensBefore = estimateMessageTokens(messages);
945
+ const keepCount = this.KEEP_RECENT_TURNS * 2;
946
+ const splitIndex = Math.max(0, messages.length - keepCount);
947
+ if (splitIndex <= 2) {
948
+ return {
949
+ messages,
950
+ compactedCount: 0,
951
+ tokensBefore,
952
+ tokensAfter: tokensBefore,
953
+ episodesSaved: 0
954
+ };
955
+ }
956
+ const oldMessages = messages.slice(0, splitIndex);
957
+ const recentMessages = messages.slice(splitIndex);
958
+ let episodesSaved = 0;
959
+ if (memoryClient) {
960
+ for (const msg of oldMessages) {
961
+ if (msg.content && (msg.role === "user" || msg.role === "assistant")) {
962
+ try {
963
+ await memoryClient.storeEpisode({
964
+ context_name: contextName,
965
+ role: msg.role,
966
+ content: `[compacted] ${msg.content}`
967
+ });
968
+ episodesSaved++;
969
+ } catch {
970
+ }
971
+ }
972
+ }
973
+ console.log(`[compaction] Saved ${episodesSaved} episodes to L2 before compacting`);
974
+ }
975
+ const summaryPrompt = [
976
+ {
977
+ role: "system",
978
+ content: `You are a context compactor. Summarize the following conversation into a concise but complete context summary. Preserve:
979
+ - Key decisions and their reasoning
980
+ - Important facts, names, and technical details mentioned
981
+ - The current state of any ongoing work
982
+ - Any commitments or action items
983
+ - The emotional/social tone if relevant
984
+
985
+ Be concise but don't lose critical information. Write in present tense as a context briefing.`
986
+ },
987
+ {
988
+ role: "user",
989
+ content: `Summarize this conversation:
990
+
991
+ ${oldMessages.filter((m) => m.content).map((m) => `[${m.role}]: ${m.content}`).join("\n\n")}`
992
+ }
993
+ ];
994
+ let summary;
995
+ try {
996
+ const response = await llmClient.chat(summaryPrompt);
997
+ summary = response.content;
998
+ } catch (err) {
999
+ console.error("[compaction] LLM summary failed, using fallback:", err.message);
1000
+ summary = oldMessages.filter((m) => m.content && m.role !== "system").map((m) => `[${m.role}]: ${m.content.slice(0, 200)}`).join("\n");
1001
+ }
1002
+ const compactedMessages = [
1003
+ {
1004
+ role: "user",
1005
+ content: `[Context from earlier conversation \u2014 ${oldMessages.length} messages compacted]
1006
+
1007
+ ${summary}`
1008
+ },
1009
+ {
1010
+ role: "assistant",
1011
+ content: "Understood. I have the context from our earlier conversation. Continuing from where we left off."
1012
+ },
1013
+ ...recentMessages
1014
+ ];
1015
+ const tokensAfter = estimateMessageTokens(compactedMessages);
1016
+ console.log(
1017
+ `[compaction] Compacted ${oldMessages.length} messages (${tokensBefore} \u2192 ${tokensAfter} tokens, saved ${episodesSaved} episodes to L2)`
1018
+ );
1019
+ return {
1020
+ messages: compactedMessages,
1021
+ compactedCount: oldMessages.length,
1022
+ tokensBefore,
1023
+ tokensAfter,
1024
+ episodesSaved
1025
+ };
1026
+ }
1027
+ };
1028
+
633
1029
  // packages/runtime/src/agent.ts
1030
+ import { resolve as resolve2 } from "path";
634
1031
  var Agent = class {
635
1032
  config;
636
1033
  llm;
@@ -644,14 +1041,34 @@ var Agent = class {
644
1041
  // Run promotion every N messages
645
1042
  MAX_TOOL_ITERATIONS = 25;
646
1043
  requestLogger = null;
1044
+ sessionStore;
1045
+ compactionManager;
1046
+ dataDir;
647
1047
  constructor(config, contextName = "global") {
648
1048
  this.config = config;
649
1049
  this.llm = new LLMClient(config.llm);
650
1050
  this.memory = new MemoryClient(config.memory);
651
1051
  this.contextManager = new ContextManager(this.memory);
1052
+ this.dataDir = resolve2(
1053
+ process.env.HIVEMIND_HOME || resolve2(process.env.HOME || "/root", "hivemind"),
1054
+ "data"
1055
+ );
1056
+ this.sessionStore = new SessionStore(this.dataDir);
1057
+ this.compactionManager = new CompactionManager();
652
1058
  if (contextName !== "global") {
653
1059
  this.contextManager.switchContext(contextName);
654
1060
  }
1061
+ this.restoreFromSession(contextName);
1062
+ }
1063
+ /**
1064
+ * Restore L1 conversation history from persisted session.
1065
+ */
1066
+ restoreFromSession(contextName) {
1067
+ const messages = this.sessionStore.getRecentMessages(contextName, 40);
1068
+ if (messages.length > 0) {
1069
+ this.conversationHistories.set(contextName, messages);
1070
+ console.log(`[session] Restored ${messages.length} messages for context "${contextName}"`);
1071
+ }
655
1072
  }
656
1073
  setToolRegistry(registry) {
657
1074
  this.toolRegistry = registry;
@@ -675,7 +1092,10 @@ var Agent = class {
675
1092
  }
676
1093
  }
677
1094
  if (!this.conversationHistories.has(contextName)) {
678
- this.conversationHistories.set(contextName, []);
1095
+ this.restoreFromSession(contextName);
1096
+ if (!this.conversationHistories.has(contextName)) {
1097
+ this.conversationHistories.set(contextName, []);
1098
+ }
679
1099
  }
680
1100
  const conversationHistory = this.conversationHistories.get(contextName);
681
1101
  const relevantEpisodes = await this.memory.search(userMessage, contextName, this.config.memory.top_k).catch((err) => {
@@ -692,7 +1112,28 @@ var Agent = class {
692
1112
  }
693
1113
  }
694
1114
  const l3Knowledge = await this.memory.getL3Knowledge(contextName).catch(() => []);
695
- const systemPromptResult = buildSystemPrompt(this.config.agent, relevantEpisodes, contextName, l3Knowledge);
1115
+ const systemPromptResult = buildSystemPrompt({
1116
+ config: this.config.agent,
1117
+ episodes: relevantEpisodes,
1118
+ contextName,
1119
+ l3Knowledge,
1120
+ dataDir: this.dataDir
1121
+ });
1122
+ const systemPromptTokens = estimateTokens(systemPromptResult.text);
1123
+ if (this.compactionManager.shouldCompact(conversationHistory, systemPromptTokens, this.config.llm.model)) {
1124
+ console.log("[compaction] Context approaching limit, compacting...");
1125
+ const result = await this.compactionManager.compact(
1126
+ conversationHistory,
1127
+ contextName,
1128
+ this.memory,
1129
+ this.llm,
1130
+ this.config.llm.model
1131
+ );
1132
+ this.conversationHistories.set(contextName, result.messages);
1133
+ const updatedHistory = this.conversationHistories.get(contextName);
1134
+ conversationHistory.length = 0;
1135
+ conversationHistory.push(...updatedHistory);
1136
+ }
696
1137
  const messages = buildMessages(systemPromptResult.text, conversationHistory, userMessage);
697
1138
  const llmStart = Date.now();
698
1139
  const toolDefs = this.toolRegistry?.getDefinitions() ?? [];
@@ -737,7 +1178,7 @@ var Agent = class {
737
1178
  },
738
1179
  conversationHistory: conversationHistory.map((m) => ({
739
1180
  role: m.role,
740
- content: m.content
1181
+ content: m.content ?? ""
741
1182
  })),
742
1183
  userMessage,
743
1184
  response: {
@@ -761,6 +1202,12 @@ var Agent = class {
761
1202
  { role: "user", content: userMessage },
762
1203
  { role: "assistant", content: response.content }
763
1204
  );
1205
+ try {
1206
+ this.sessionStore.append(contextName, { role: "user", content: userMessage });
1207
+ this.sessionStore.append(contextName, { role: "assistant", content: response.content });
1208
+ } catch (err) {
1209
+ console.error("[session] Failed to persist messages:", err.message);
1210
+ }
764
1211
  if (conversationHistory.length > 40) {
765
1212
  const trimmed = conversationHistory.slice(-40);
766
1213
  this.conversationHistories.set(contextName, trimmed);
@@ -802,192 +1249,6 @@ var Agent = class {
802
1249
  let stripped = message.replace(/^\[.+?\s+in\s+group\s+chat\]:\s*/, "");
803
1250
  stripped = stripped.replace(/^\[.+?\]:\s*/, "");
804
1251
  const cmdText = stripped;
805
- if (/^hm[:\s]status\b/i.test(cmdText)) {
806
- const memoryOk = await this.memory.healthCheck();
807
- const contexts = this.contextManager.listContexts();
808
- let contextStats = "";
809
- try {
810
- const daemonContexts = await this.memory.listContexts();
811
- contextStats = daemonContexts.map((c) => ` - ${c.name}: ${c.episode_count} episodes`).join("\n");
812
- } catch {
813
- contextStats = " (unable to query memory daemon)";
814
- }
815
- const response = [
816
- `## Agent Status`,
817
- ``,
818
- `**Name:** ${this.config.agent.name}`,
819
- `**Active Context:** ${activeCtx}`,
820
- `**Memory Daemon:** ${memoryOk ? "\u2705 connected" : "\u274C unreachable"} (${this.config.memory.daemon_url})`,
821
- `**Model:** ${this.config.llm.model}`,
822
- `**Temperature:** ${this.config.llm.temperature}`,
823
- `**Max Tokens:** ${this.config.llm.max_tokens}`,
824
- `**Memory Top-K:** ${this.config.memory.top_k}`,
825
- `**Messages Processed:** ${this.messageCount}`,
826
- `**L1 Contexts in Memory:** ${this.conversationHistories.size}`,
827
- ``,
828
- `### Contexts`,
829
- contextStats
830
- ].join("\n");
831
- return { content: response, model: "system", context: activeCtx };
832
- }
833
- if (/^hm[:\s]memory\s+stats\b/i.test(cmdText)) {
834
- try {
835
- const contexts = await this.memory.listContexts();
836
- let totalEpisodes = 0;
837
- let totalL3 = 0;
838
- let lines = [];
839
- for (const ctx of contexts) {
840
- totalEpisodes += ctx.episode_count;
841
- let l3Count = 0;
842
- try {
843
- const l3 = await this.memory.getL3Knowledge(ctx.name);
844
- l3Count = l3.length;
845
- totalL3 += l3Count;
846
- } catch {
847
- }
848
- lines.push(` - **${ctx.name}**: ${ctx.episode_count} L2 episodes, ${l3Count} L3 entries`);
849
- }
850
- const response = [
851
- `## Memory Stats`,
852
- ``,
853
- `**Total L2 Episodes:** ${totalEpisodes}`,
854
- `**Total L3 Knowledge:** ${totalL3}`,
855
- `**Contexts:** ${contexts.length}`,
856
- ``,
857
- `### Per Context`,
858
- ...lines
859
- ].join("\n");
860
- return { content: response, model: "system", context: activeCtx };
861
- } catch (err) {
862
- return { content: `Memory stats failed: ${err.message}`, model: "system", context: activeCtx };
863
- }
864
- }
865
- const memSearchMatch = cmdText.match(/^hm[:\s]memory\s+search\s+(.+)/i);
866
- if (memSearchMatch) {
867
- const query = memSearchMatch[1].trim();
868
- try {
869
- const results = await this.memory.search(query, activeCtx, this.config.memory.top_k);
870
- if (results.length === 0) {
871
- return { content: `No memories found for "${query}" in context "${activeCtx}".`, model: "system", context: activeCtx };
872
- }
873
- let response = `## Memory Search: "${query}"
874
-
875
- `;
876
- for (const ep of results) {
877
- const ctxLabel = ep.context_name !== activeCtx ? ` [from: ${ep.context_name}]` : "";
878
- response += `- **[${ep.role}]** (score: ${ep.score.toFixed(3)})${ctxLabel} ${ep.content.slice(0, 300)}${ep.content.length > 300 ? "..." : ""}
879
- `;
880
- }
881
- return { content: response, model: "system", context: activeCtx };
882
- } catch (err) {
883
- return { content: `Memory search failed: ${err.message}`, model: "system", context: activeCtx };
884
- }
885
- }
886
- const l3Match = cmdText.match(/^hm[:\s]memory\s+l3(?:\s+(\S+))?/i);
887
- if (l3Match) {
888
- const targetCtx = l3Match[1] || activeCtx;
889
- try {
890
- const entries = await this.memory.getL3Knowledge(targetCtx);
891
- if (entries.length === 0) {
892
- return { content: `No L3 knowledge in context "${targetCtx}".`, model: "system", context: activeCtx };
893
- }
894
- let response = `## L3 Knowledge: ${targetCtx}
895
-
896
- `;
897
- for (const entry of entries) {
898
- response += `- **[${entry.id.slice(0, 8)}]** (density: ${entry.connection_density}, accesses: ${entry.access_count})
899
- ${entry.content}
900
- `;
901
- }
902
- return { content: response, model: "system", context: activeCtx };
903
- } catch (err) {
904
- return { content: `L3 query failed: ${err.message}`, model: "system", context: activeCtx };
905
- }
906
- }
907
- if (/^hm[:\s]config\b/i.test(cmdText)) {
908
- const response = [
909
- `## Configuration`,
910
- ``,
911
- `**Agent:** ${this.config.agent.name}`,
912
- `**Personality:** ${this.config.agent.personality}`,
913
- `**Workspace:** ${this.config.agent.workspace || "(none)"}`,
914
- ``,
915
- `### LLM`,
916
- `- Model: ${this.config.llm.model}`,
917
- `- Base URL: ${this.config.llm.base_url}`,
918
- `- Max Tokens: ${this.config.llm.max_tokens}`,
919
- `- Temperature: ${this.config.llm.temperature}`,
920
- ``,
921
- `### Memory`,
922
- `- Daemon URL: ${this.config.memory.daemon_url}`,
923
- `- Top-K: ${this.config.memory.top_k}`,
924
- `- Embedding Model: ${this.config.memory.embedding_model}`,
925
- ``,
926
- `### Sesame`,
927
- `- API: ${this.config.sesame.api_url}`,
928
- `- WebSocket: ${this.config.sesame.ws_url}`,
929
- `- API Key: ${this.config.sesame.api_key ? "***" + this.config.sesame.api_key.slice(-4) : "(not set)"}`
930
- ].join("\n");
931
- return { content: response, model: "system", context: activeCtx };
932
- }
933
- if (/^hm[:\s]contexts\b/i.test(cmdText)) {
934
- const localContexts = this.contextManager.listContexts();
935
- let daemonInfo = "";
936
- try {
937
- const daemonContexts = await this.memory.listContexts();
938
- daemonInfo = "\n### Memory Daemon Contexts\n" + daemonContexts.map(
939
- (c) => ` - **${c.name}**: ${c.episode_count} episodes (created: ${c.created_at})`
940
- ).join("\n");
941
- } catch {
942
- daemonInfo = "\n(Memory daemon unreachable)";
943
- }
944
- const localInfo = localContexts.map((c) => {
945
- const active = c.name === activeCtx ? " \u2190 active" : "";
946
- const historyLen = this.conversationHistories.get(c.name)?.length || 0;
947
- return ` - **${c.name}**${active}: ${historyLen / 2} turns in L1`;
948
- }).join("\n");
949
- const response = [
950
- `## Contexts`,
951
- ``,
952
- `**Active:** ${activeCtx}`,
953
- ``,
954
- `### Local (L1 Working Memory)`,
955
- localInfo,
956
- daemonInfo
957
- ].join("\n");
958
- return { content: response, model: "system", context: activeCtx };
959
- }
960
- if (/^hm[:\s]help\b/i.test(cmdText)) {
961
- const response = [
962
- `## Available Commands`,
963
- ``,
964
- `### Introspection`,
965
- `- \`hm:status\` \u2014 Agent health, memory status, config summary`,
966
- `- \`hm:config\` \u2014 Full configuration details`,
967
- `- \`hm:contexts\` \u2014 List all contexts with L1/L2 info`,
968
- `- \`hm:memory stats\` \u2014 Episode counts, L3 entries per context`,
969
- `- \`hm:memory search <query>\` \u2014 Search L2 memories with scores`,
970
- `- \`hm:memory l3 [context]\` \u2014 Show L3 knowledge entries`,
971
- `- \`hm:help\` \u2014 This help message`,
972
- ``,
973
- `### Context Management`,
974
- `- \`switch to <name>\` \u2014 Switch active context`,
975
- `- \`create context <name>\` \u2014 Create a new context`,
976
- `- \`list contexts\` \u2014 List contexts (legacy)`,
977
- ``,
978
- `### Memory`,
979
- `- \`search all: <query>\` \u2014 Cross-context search`,
980
- `- \`share <episode_id> with <context>\` \u2014 Share episode across contexts`,
981
- ``,
982
- `### Tasks`,
983
- `- \`task add <title>\` \u2014 Create a task`,
984
- `- \`task list [status]\` \u2014 List tasks`,
985
- `- \`task start <id>\` \u2014 Start a task`,
986
- `- \`task complete <id>\` \u2014 Complete a task`,
987
- `- \`task next\` \u2014 Pick up next available task`
988
- ].join("\n");
989
- return { content: response, model: "system", context: activeCtx };
990
- }
991
1252
  const searchAllMatch = cmdText.match(/^(?:search\s+all|cross-context\s+search)[:\s]+(.+)/i);
992
1253
  if (searchAllMatch) {
993
1254
  const query = searchAllMatch[1].trim();
@@ -1088,9 +1349,219 @@ var Agent = class {
1088
1349
  }
1089
1350
  };
1090
1351
 
1352
+ // packages/runtime/src/events.ts
1353
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, unlinkSync, watch } from "fs";
1354
+ import { join as join3 } from "path";
1355
+ function matchCronField(field, value, _max) {
1356
+ if (field === "*") return true;
1357
+ for (const part of field.split(",")) {
1358
+ if (part.includes("-")) {
1359
+ const [lo, hi] = part.split("-").map(Number);
1360
+ if (value >= lo && value <= hi) return true;
1361
+ } else if (part.includes("/")) {
1362
+ const [base, step] = part.split("/");
1363
+ const stepN = Number(step);
1364
+ const baseN = base === "*" ? 0 : Number(base);
1365
+ if ((value - baseN) % stepN === 0 && value >= baseN) return true;
1366
+ } else {
1367
+ if (Number(part) === value) return true;
1368
+ }
1369
+ }
1370
+ return false;
1371
+ }
1372
+ function cronMatches(expr, date) {
1373
+ const parts = expr.trim().split(/\s+/);
1374
+ if (parts.length !== 5) return false;
1375
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
1376
+ return matchCronField(minute, date.getMinutes(), 59) && matchCronField(hour, date.getHours(), 23) && matchCronField(dayOfMonth, date.getDate(), 31) && matchCronField(month, date.getMonth() + 1, 12) && matchCronField(dayOfWeek, date.getDay(), 6);
1377
+ }
1378
+ var EventsWatcher = class {
1379
+ eventsDir;
1380
+ handler;
1381
+ watcher = null;
1382
+ cronInterval = null;
1383
+ oneShotTimers = /* @__PURE__ */ new Map();
1384
+ knownFiles = /* @__PURE__ */ new Set();
1385
+ startTime;
1386
+ debounceTimers = /* @__PURE__ */ new Map();
1387
+ constructor(dataDir, handler) {
1388
+ this.eventsDir = join3(dataDir, "events");
1389
+ this.handler = handler;
1390
+ this.startTime = Date.now();
1391
+ }
1392
+ start() {
1393
+ if (!existsSync3(this.eventsDir)) {
1394
+ mkdirSync2(this.eventsDir, { recursive: true });
1395
+ }
1396
+ console.log(`[events] Watching ${this.eventsDir}`);
1397
+ this.scanExisting();
1398
+ this.watcher = watch(this.eventsDir, (_eventType, filename) => {
1399
+ if (!filename || !filename.endsWith(".json")) return;
1400
+ this.debounce(filename, () => this.handleFileChange(filename));
1401
+ });
1402
+ this.cronInterval = setInterval(() => this.checkPeriodicEvents(), 6e4);
1403
+ console.log(`[events] Started, tracking ${this.knownFiles.size} files`);
1404
+ }
1405
+ stop() {
1406
+ if (this.watcher) {
1407
+ this.watcher.close();
1408
+ this.watcher = null;
1409
+ }
1410
+ if (this.cronInterval) {
1411
+ clearInterval(this.cronInterval);
1412
+ this.cronInterval = null;
1413
+ }
1414
+ for (const timer of this.oneShotTimers.values()) {
1415
+ clearTimeout(timer);
1416
+ }
1417
+ this.oneShotTimers.clear();
1418
+ for (const timer of this.debounceTimers.values()) {
1419
+ clearTimeout(timer);
1420
+ }
1421
+ this.debounceTimers.clear();
1422
+ }
1423
+ debounce(filename, fn) {
1424
+ const existing = this.debounceTimers.get(filename);
1425
+ if (existing) clearTimeout(existing);
1426
+ this.debounceTimers.set(
1427
+ filename,
1428
+ setTimeout(() => {
1429
+ this.debounceTimers.delete(filename);
1430
+ fn();
1431
+ }, 200)
1432
+ );
1433
+ }
1434
+ scanExisting() {
1435
+ try {
1436
+ const files = readdirSync2(this.eventsDir).filter((f) => f.endsWith(".json"));
1437
+ for (const file of files) {
1438
+ this.knownFiles.add(file);
1439
+ this.processEventFile(file);
1440
+ }
1441
+ } catch {
1442
+ }
1443
+ }
1444
+ handleFileChange(filename) {
1445
+ const filePath = join3(this.eventsDir, filename);
1446
+ if (!existsSync3(filePath)) {
1447
+ this.knownFiles.delete(filename);
1448
+ const timer = this.oneShotTimers.get(filename);
1449
+ if (timer) {
1450
+ clearTimeout(timer);
1451
+ this.oneShotTimers.delete(filename);
1452
+ }
1453
+ return;
1454
+ }
1455
+ if (!this.knownFiles.has(filename)) {
1456
+ this.knownFiles.add(filename);
1457
+ }
1458
+ this.processEventFile(filename);
1459
+ }
1460
+ processEventFile(filename) {
1461
+ const filePath = join3(this.eventsDir, filename);
1462
+ let event;
1463
+ try {
1464
+ const content = readFileSync3(filePath, "utf-8");
1465
+ event = JSON.parse(content);
1466
+ } catch (err) {
1467
+ console.error(`[events] Failed to parse ${filename}:`, err.message);
1468
+ return;
1469
+ }
1470
+ if (!event.type || !event.channelId || !event.text) {
1471
+ console.warn(`[events] Invalid event in ${filename}: missing required fields`);
1472
+ return;
1473
+ }
1474
+ switch (event.type) {
1475
+ case "immediate":
1476
+ this.handleImmediate(filename, event);
1477
+ break;
1478
+ case "one-shot":
1479
+ this.scheduleOneShot(filename, event);
1480
+ break;
1481
+ case "periodic":
1482
+ console.log(`[events] Registered periodic event: ${filename} (${event.schedule})`);
1483
+ break;
1484
+ default:
1485
+ console.warn(`[events] Unknown event type in ${filename}: ${event.type}`);
1486
+ }
1487
+ }
1488
+ async handleImmediate(filename, event) {
1489
+ console.log(`[events] Triggering immediate event: ${filename}`);
1490
+ try {
1491
+ await this.handler(event.channelId, event.text, filename, "immediate");
1492
+ } catch (err) {
1493
+ console.error(`[events] Handler error for ${filename}:`, err.message);
1494
+ }
1495
+ this.deleteEvent(filename);
1496
+ }
1497
+ scheduleOneShot(filename, event) {
1498
+ const targetTime = new Date(event.at).getTime();
1499
+ const now = Date.now();
1500
+ const delay = targetTime - now;
1501
+ if (delay <= 0) {
1502
+ console.log(`[events] One-shot event ${filename} is past due, triggering now`);
1503
+ this.handler(event.channelId, event.text, filename, "one-shot").catch((err) => {
1504
+ console.error(`[events] Handler error for ${filename}:`, err.message);
1505
+ });
1506
+ this.deleteEvent(filename);
1507
+ return;
1508
+ }
1509
+ const existing = this.oneShotTimers.get(filename);
1510
+ if (existing) clearTimeout(existing);
1511
+ console.log(`[events] Scheduled one-shot: ${filename} in ${Math.round(delay / 1e3)}s`);
1512
+ const timer = setTimeout(async () => {
1513
+ this.oneShotTimers.delete(filename);
1514
+ try {
1515
+ await this.handler(event.channelId, event.text, filename, "one-shot");
1516
+ } catch (err) {
1517
+ console.error(`[events] Handler error for ${filename}:`, err.message);
1518
+ }
1519
+ this.deleteEvent(filename);
1520
+ }, delay);
1521
+ this.oneShotTimers.set(filename, timer);
1522
+ }
1523
+ checkPeriodicEvents() {
1524
+ const now = /* @__PURE__ */ new Date();
1525
+ for (const filename of this.knownFiles) {
1526
+ const filePath = join3(this.eventsDir, filename);
1527
+ if (!existsSync3(filePath)) continue;
1528
+ try {
1529
+ const event = JSON.parse(readFileSync3(filePath, "utf-8"));
1530
+ if (event.type !== "periodic") continue;
1531
+ let checkDate = now;
1532
+ if (event.timezone) {
1533
+ try {
1534
+ const tzStr = now.toLocaleString("en-US", { timeZone: event.timezone });
1535
+ checkDate = new Date(tzStr);
1536
+ } catch {
1537
+ }
1538
+ }
1539
+ if (cronMatches(event.schedule, checkDate)) {
1540
+ console.log(`[events] Triggering periodic event: ${filename}`);
1541
+ this.handler(event.channelId, event.text, filename, "periodic").catch((err) => {
1542
+ console.error(`[events] Handler error for ${filename}:`, err.message);
1543
+ });
1544
+ }
1545
+ } catch {
1546
+ }
1547
+ }
1548
+ }
1549
+ deleteEvent(filename) {
1550
+ try {
1551
+ const filePath = join3(this.eventsDir, filename);
1552
+ if (existsSync3(filePath)) {
1553
+ unlinkSync(filePath);
1554
+ }
1555
+ this.knownFiles.delete(filename);
1556
+ } catch (err) {
1557
+ console.error(`[events] Failed to delete ${filename}:`, err.message);
1558
+ }
1559
+ }
1560
+ };
1561
+
1091
1562
  // packages/runtime/src/config.ts
1092
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1093
- import { resolve as resolve2, dirname } from "path";
1563
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
1564
+ import { resolve as resolve3, dirname as dirname2 } from "path";
1094
1565
 
1095
1566
  // node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
1096
1567
  function getLineColFromPtr(string, ptr) {
@@ -1814,12 +2285,12 @@ function deepMerge(target, source) {
1814
2285
  return result;
1815
2286
  }
1816
2287
  function loadConfig(path) {
1817
- const raw = readFileSync2(path, "utf-8");
2288
+ const raw = readFileSync4(path, "utf-8");
1818
2289
  let parsed = parse(raw);
1819
- const configDir = dirname(path);
1820
- const localPath = resolve2(configDir, "local.toml");
1821
- if (existsSync2(localPath)) {
1822
- const localRaw = readFileSync2(localPath, "utf-8");
2290
+ const configDir = dirname2(path);
2291
+ const localPath = resolve3(configDir, "local.toml");
2292
+ if (existsSync4(localPath)) {
2293
+ const localRaw = readFileSync4(localPath, "utf-8");
1823
2294
  const localParsed = parse(localRaw);
1824
2295
  parsed = deepMerge(parsed, localParsed);
1825
2296
  console.log(`[config] Merged overrides from ${localPath}`);
@@ -1868,20 +2339,20 @@ function loadConfig(path) {
1868
2339
  parsed.sentinel.stop_flag_file = process.env.SENTINEL_STOP_FLAG_FILE;
1869
2340
  }
1870
2341
  if (parsed.agent.workspace && !parsed.agent.workspace.startsWith("/")) {
1871
- const configDir2 = dirname(path);
1872
- parsed.agent.workspace = resolve2(configDir2, "..", parsed.agent.workspace);
2342
+ const configDir2 = dirname2(path);
2343
+ parsed.agent.workspace = resolve3(configDir2, "..", parsed.agent.workspace);
1873
2344
  }
1874
2345
  return parsed;
1875
2346
  }
1876
2347
 
1877
2348
  // packages/runtime/src/sesame.ts
1878
- import { readFileSync as readFileSync3 } from "fs";
1879
- import { resolve as resolve3, dirname as dirname2 } from "path";
2349
+ import { readFileSync as readFileSync5 } from "fs";
2350
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1880
2351
  import { fileURLToPath } from "url";
1881
2352
  var RUNTIME_VERSION = "unknown";
1882
2353
  try {
1883
- const __dirname2 = dirname2(fileURLToPath(import.meta.url));
1884
- const pkg = JSON.parse(readFileSync3(resolve3(__dirname2, "../package.json"), "utf-8"));
2354
+ const __dirname2 = dirname3(fileURLToPath(import.meta.url));
2355
+ const pkg = JSON.parse(readFileSync5(resolve4(__dirname2, "../package.json"), "utf-8"));
1885
2356
  RUNTIME_VERSION = pkg.version ?? "unknown";
1886
2357
  } catch {
1887
2358
  }
@@ -1947,12 +2418,7 @@ var SesameClient2 = class {
1947
2418
  this.sdk.on("message", (event) => {
1948
2419
  const msg = event.data || event.message || event;
1949
2420
  const senderId = msg.senderId || msg.sender?.id;
1950
- if (senderId === this.agentId) {
1951
- const content = msg.content?.trim() || "";
1952
- const isHmCommand = /^hm[:\s]/i.test(content);
1953
- const isTextCommand = /^(?:list contexts|switch to|create context|search all:|task )/i.test(content);
1954
- if (!isHmCommand && !isTextCommand) return;
1955
- }
2421
+ if (senderId === this.agentId) return;
1956
2422
  if (!this.messageHandler || !msg.content) return;
1957
2423
  const channelInfo = this.channels.get(msg.channelId);
1958
2424
  this.messageHandler({
@@ -2055,24 +2521,24 @@ var HEALTH_PORT = 9484;
2055
2521
  var HEALTH_PATH = "/health";
2056
2522
 
2057
2523
  // packages/runtime/src/request-logger.ts
2058
- import { randomUUID } from "crypto";
2059
- import { mkdirSync, existsSync as existsSync3, appendFileSync, readFileSync as readFileSync4, writeFileSync } from "fs";
2060
- import { dirname as dirname3 } from "path";
2524
+ import { randomUUID as randomUUID2 } from "crypto";
2525
+ import { mkdirSync as mkdirSync3, existsSync as existsSync5, appendFileSync as appendFileSync2, readFileSync as readFileSync6, writeFileSync } from "fs";
2526
+ import { dirname as dirname4 } from "path";
2061
2527
  var RequestLogger = class {
2062
2528
  logPath;
2063
2529
  maxAgeDays = 7;
2064
2530
  constructor(dbPath) {
2065
2531
  this.logPath = dbPath.replace(/\.db$/, ".jsonl");
2066
2532
  if (this.logPath === dbPath) this.logPath = dbPath + ".jsonl";
2067
- const dir = dirname3(this.logPath);
2068
- if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
2533
+ const dir = dirname4(this.logPath);
2534
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
2069
2535
  this.prune();
2070
2536
  }
2071
2537
  prune() {
2072
- if (!existsSync3(this.logPath)) return;
2538
+ if (!existsSync5(this.logPath)) return;
2073
2539
  const cutoff = new Date(Date.now() - this.maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
2074
2540
  try {
2075
- const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2541
+ const lines = readFileSync6(this.logPath, "utf-8").split("\n").filter(Boolean);
2076
2542
  const kept = [];
2077
2543
  let pruned = 0;
2078
2544
  for (const line of lines) {
@@ -2094,7 +2560,7 @@ var RequestLogger = class {
2094
2560
  }
2095
2561
  }
2096
2562
  log(entry) {
2097
- const id = randomUUID();
2563
+ const id = randomUUID2();
2098
2564
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2099
2565
  const sysTokens = Math.ceil(entry.systemPromptComponents.fullText.length / 4);
2100
2566
  const histTokens = Math.ceil(
@@ -2124,7 +2590,7 @@ var RequestLogger = class {
2124
2590
  token_est_user: userTokens,
2125
2591
  token_est_total: sysTokens + histTokens + userTokens
2126
2592
  };
2127
- appendFileSync(this.logPath, JSON.stringify(record) + "\n");
2593
+ appendFileSync2(this.logPath, JSON.stringify(record) + "\n");
2128
2594
  return id;
2129
2595
  }
2130
2596
  getRequests(opts = {}) {
@@ -2148,9 +2614,9 @@ var RequestLogger = class {
2148
2614
  close() {
2149
2615
  }
2150
2616
  readAll() {
2151
- if (!existsSync3(this.logPath)) return [];
2617
+ if (!existsSync5(this.logPath)) return [];
2152
2618
  try {
2153
- const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2619
+ const lines = readFileSync6(this.logPath, "utf-8").split("\n").filter(Boolean);
2154
2620
  const entries = [];
2155
2621
  for (const line of lines) {
2156
2622
  try {
@@ -2167,17 +2633,17 @@ var RequestLogger = class {
2167
2633
 
2168
2634
  // packages/runtime/src/dashboard.ts
2169
2635
  import { createServer } from "http";
2170
- import { readFileSync as readFileSync5 } from "fs";
2171
- import { resolve as resolve4, dirname as dirname4 } from "path";
2636
+ import { readFileSync as readFileSync7 } from "fs";
2637
+ import { resolve as resolve5, dirname as dirname5 } from "path";
2172
2638
  import { fileURLToPath as fileURLToPath2 } from "url";
2173
- var __dirname = dirname4(fileURLToPath2(import.meta.url));
2639
+ var __dirname = dirname5(fileURLToPath2(import.meta.url));
2174
2640
  var DASHBOARD_PORT = 9485;
2175
2641
  var spaHtml = null;
2176
2642
  function getSpaHtml() {
2177
2643
  if (!spaHtml) {
2178
- for (const dir of [__dirname, resolve4(__dirname, "../src")]) {
2644
+ for (const dir of [__dirname, resolve5(__dirname, "../src")]) {
2179
2645
  try {
2180
- spaHtml = readFileSync5(resolve4(dir, "dashboard.html"), "utf-8");
2646
+ spaHtml = readFileSync7(resolve5(dir, "dashboard.html"), "utf-8");
2181
2647
  break;
2182
2648
  } catch {
2183
2649
  }
@@ -2349,7 +2815,7 @@ var ToolRegistry = class {
2349
2815
 
2350
2816
  // packages/runtime/src/tools/shell.ts
2351
2817
  import { execSync } from "child_process";
2352
- import { resolve as resolve5 } from "path";
2818
+ import { resolve as resolve6 } from "path";
2353
2819
  var MAX_OUTPUT = 5e4;
2354
2820
  function registerShellTool(registry, workspaceDir) {
2355
2821
  registry.register(
@@ -2376,7 +2842,7 @@ function registerShellTool(registry, workspaceDir) {
2376
2842
  async (params) => {
2377
2843
  const command = params.command;
2378
2844
  const timeout = (params.timeout || 30) * 1e3;
2379
- const cwd = params.workdir ? resolve5(workspaceDir, params.workdir) : workspaceDir;
2845
+ const cwd = params.workdir ? resolve6(workspaceDir, params.workdir) : workspaceDir;
2380
2846
  try {
2381
2847
  const output = execSync(command, {
2382
2848
  cwd,
@@ -2403,8 +2869,8 @@ ${output || err.message}`;
2403
2869
  }
2404
2870
 
2405
2871
  // packages/runtime/src/tools/files.ts
2406
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
2407
- import { resolve as resolve6, dirname as dirname5 } from "path";
2872
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
2873
+ import { resolve as resolve7, dirname as dirname6 } from "path";
2408
2874
  var MAX_READ = 1e5;
2409
2875
  function registerFileTools(registry, workspaceDir) {
2410
2876
  registry.register(
@@ -2430,11 +2896,11 @@ function registerFileTools(registry, workspaceDir) {
2430
2896
  },
2431
2897
  async (params) => {
2432
2898
  const filePath = resolvePath(workspaceDir, params.path);
2433
- if (!existsSync4(filePath)) {
2899
+ if (!existsSync6(filePath)) {
2434
2900
  return `Error: File not found: ${filePath}`;
2435
2901
  }
2436
2902
  try {
2437
- let content = readFileSync6(filePath, "utf-8");
2903
+ let content = readFileSync8(filePath, "utf-8");
2438
2904
  if (params.offset || params.limit) {
2439
2905
  const lines = content.split("\n");
2440
2906
  const start = (params.offset || 1) - 1;
@@ -2471,8 +2937,8 @@ function registerFileTools(registry, workspaceDir) {
2471
2937
  async (params) => {
2472
2938
  const filePath = resolvePath(workspaceDir, params.path);
2473
2939
  try {
2474
- const dir = dirname5(filePath);
2475
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2940
+ const dir = dirname6(filePath);
2941
+ if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
2476
2942
  writeFileSync2(filePath, params.content);
2477
2943
  return `Written ${params.content.length} bytes to ${filePath}`;
2478
2944
  } catch (err) {
@@ -2503,11 +2969,11 @@ function registerFileTools(registry, workspaceDir) {
2503
2969
  },
2504
2970
  async (params) => {
2505
2971
  const filePath = resolvePath(workspaceDir, params.path);
2506
- if (!existsSync4(filePath)) {
2972
+ if (!existsSync6(filePath)) {
2507
2973
  return `Error: File not found: ${filePath}`;
2508
2974
  }
2509
2975
  try {
2510
- const content = readFileSync6(filePath, "utf-8");
2976
+ const content = readFileSync8(filePath, "utf-8");
2511
2977
  const oldText = params.old_text;
2512
2978
  const newText = params.new_text;
2513
2979
  if (!content.includes(oldText)) {
@@ -2535,18 +3001,18 @@ function registerFileTools(registry, workspaceDir) {
2535
3001
  required: []
2536
3002
  },
2537
3003
  async (params) => {
2538
- const { readdirSync: readdirSync2, statSync } = await import("fs");
3004
+ const { readdirSync: readdirSync3, statSync: statSync2 } = await import("fs");
2539
3005
  const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
2540
- if (!existsSync4(dirPath)) {
3006
+ if (!existsSync6(dirPath)) {
2541
3007
  return `Error: Directory not found: ${dirPath}`;
2542
3008
  }
2543
3009
  try {
2544
- const entries = readdirSync2(dirPath);
3010
+ const entries = readdirSync3(dirPath);
2545
3011
  const results = [];
2546
3012
  for (const entry of entries) {
2547
3013
  if (entry.startsWith(".")) continue;
2548
3014
  try {
2549
- const stat = statSync(resolve6(dirPath, entry));
3015
+ const stat = statSync2(resolve7(dirPath, entry));
2550
3016
  results.push(stat.isDirectory() ? `${entry}/` : entry);
2551
3017
  } catch {
2552
3018
  results.push(entry);
@@ -2563,7 +3029,7 @@ function resolvePath(workspace, path) {
2563
3029
  if (path.startsWith("/") || path.startsWith("~")) {
2564
3030
  return path.replace(/^~/, process.env.HOME || "/root");
2565
3031
  }
2566
- return resolve6(workspace, path);
3032
+ return resolve7(workspace, path);
2567
3033
  }
2568
3034
 
2569
3035
  // packages/runtime/src/tools/web.ts
@@ -2664,32 +3130,184 @@ function registerWebTools(registry, config) {
2664
3130
  );
2665
3131
  }
2666
3132
 
3133
+ // packages/runtime/src/tools/memory.ts
3134
+ function registerMemoryTools(registry, daemonUrl) {
3135
+ registry.register(
3136
+ "memory_search",
3137
+ "Search your episodic memory for relevant past conversations and experiences. Returns scored results from your memory system. Use this when you want to recall something from a previous conversation or check what you know about a topic.",
3138
+ {
3139
+ type: "object",
3140
+ properties: {
3141
+ query: {
3142
+ type: "string",
3143
+ description: "What to search for in memory"
3144
+ },
3145
+ context: {
3146
+ type: "string",
3147
+ description: "Context to search in (default: current active context)"
3148
+ },
3149
+ top_k: {
3150
+ type: "number",
3151
+ description: "Number of results to return (default: 10)"
3152
+ }
3153
+ },
3154
+ required: ["query"]
3155
+ },
3156
+ async (params) => {
3157
+ const query = params.query;
3158
+ const context = params.context || "global";
3159
+ const topK = params.top_k || 10;
3160
+ try {
3161
+ const resp = await fetch(`${daemonUrl}/api/search`, {
3162
+ method: "POST",
3163
+ headers: { "Content-Type": "application/json" },
3164
+ body: JSON.stringify({ query, context_name: context, top_k: topK })
3165
+ });
3166
+ if (!resp.ok) return `Memory search failed: ${resp.status}`;
3167
+ const data = await resp.json();
3168
+ if (!data.results?.length) return `No memories found for "${query}" in context "${context}".`;
3169
+ return data.results.map((r, i) => `${i + 1}. [score: ${r.score.toFixed(3)}] [${r.context_name}] [${r.role}] ${r.content.slice(0, 200)}`).join("\n");
3170
+ } catch (err) {
3171
+ return `Memory daemon unreachable: ${err.message}`;
3172
+ }
3173
+ }
3174
+ );
3175
+ registry.register(
3176
+ "memory_stats",
3177
+ "Get statistics about your memory system \u2014 episode counts, contexts, L3 knowledge entries. Use for self-diagnosis or understanding your memory state.",
3178
+ {
3179
+ type: "object",
3180
+ properties: {},
3181
+ required: []
3182
+ },
3183
+ async () => {
3184
+ try {
3185
+ const healthResp = await fetch(`${daemonUrl}/health`);
3186
+ const health = healthResp.ok ? await healthResp.json() : { status: "unreachable" };
3187
+ const ctxResp = await fetch(`${daemonUrl}/api/contexts`);
3188
+ const contexts = ctxResp.ok ? (await ctxResp.json()).contexts : [];
3189
+ const l3Counts = {};
3190
+ for (const ctx of contexts) {
3191
+ try {
3192
+ const l3Resp = await fetch(`${daemonUrl}/api/l3/${encodeURIComponent(ctx.name)}`);
3193
+ if (l3Resp.ok) {
3194
+ const l3Data = await l3Resp.json();
3195
+ l3Counts[ctx.name] = l3Data.entries?.length ?? 0;
3196
+ }
3197
+ } catch {
3198
+ }
3199
+ }
3200
+ const totalEpisodes = contexts.reduce((sum, c) => sum + c.episode_count, 0);
3201
+ const totalL3 = Object.values(l3Counts).reduce((sum, c) => sum + c, 0);
3202
+ let output = `Memory Daemon: ${health.status || "unknown"}
3203
+ `;
3204
+ output += `Total Episodes (L2): ${totalEpisodes}
3205
+ `;
3206
+ output += `Total Knowledge (L3): ${totalL3}
3207
+ `;
3208
+ output += `
3209
+ Contexts:
3210
+ `;
3211
+ for (const ctx of contexts) {
3212
+ output += ` - ${ctx.name}: ${ctx.episode_count} episodes, ${l3Counts[ctx.name] ?? 0} L3 entries
3213
+ `;
3214
+ }
3215
+ return output;
3216
+ } catch (err) {
3217
+ return `Memory daemon unreachable: ${err.message}`;
3218
+ }
3219
+ }
3220
+ );
3221
+ registry.register(
3222
+ "memory_l3",
3223
+ "View your promoted L3 knowledge \u2014 high-level patterns, decisions, and insights that have been distilled from your episodic memories. This is your 'long-term wisdom'.",
3224
+ {
3225
+ type: "object",
3226
+ properties: {
3227
+ context: {
3228
+ type: "string",
3229
+ description: "Context to get L3 knowledge for (default: global)"
3230
+ }
3231
+ },
3232
+ required: []
3233
+ },
3234
+ async (params) => {
3235
+ const context = params.context || "global";
3236
+ try {
3237
+ const resp = await fetch(`${daemonUrl}/api/l3/${encodeURIComponent(context)}`);
3238
+ if (!resp.ok) return `L3 query failed: ${resp.status}`;
3239
+ const data = await resp.json();
3240
+ if (!data.entries?.length) return `No L3 knowledge in context "${context}".`;
3241
+ return data.entries.map((e, i) => `${i + 1}. ${e.content} (from ${e.source_episodes} episodes, ${e.created_at})`).join("\n");
3242
+ } catch (err) {
3243
+ return `Memory daemon unreachable: ${err.message}`;
3244
+ }
3245
+ }
3246
+ );
3247
+ registry.register(
3248
+ "memory_cross_search",
3249
+ "Search across ALL memory contexts at once. Useful when you're not sure which context a memory belongs to.",
3250
+ {
3251
+ type: "object",
3252
+ properties: {
3253
+ query: {
3254
+ type: "string",
3255
+ description: "What to search for across all contexts"
3256
+ },
3257
+ top_k: {
3258
+ type: "number",
3259
+ description: "Number of results (default: 10)"
3260
+ }
3261
+ },
3262
+ required: ["query"]
3263
+ },
3264
+ async (params) => {
3265
+ const query = params.query;
3266
+ const topK = params.top_k || 10;
3267
+ try {
3268
+ const resp = await fetch(`${daemonUrl}/api/search/cross-context`, {
3269
+ method: "POST",
3270
+ headers: { "Content-Type": "application/json" },
3271
+ body: JSON.stringify({ query, top_k: topK })
3272
+ });
3273
+ if (!resp.ok) return `Cross-context search failed: ${resp.status}`;
3274
+ const data = await resp.json();
3275
+ if (!data.results?.length) return `No memories found across any context for "${query}".`;
3276
+ return data.results.map((r, i) => `${i + 1}. [${r.score.toFixed(3)}] [${r.context_name}] ${r.content.slice(0, 200)}`).join("\n");
3277
+ } catch (err) {
3278
+ return `Memory daemon unreachable: ${err.message}`;
3279
+ }
3280
+ }
3281
+ );
3282
+ }
3283
+
2667
3284
  // packages/runtime/src/tools/register.ts
2668
- import { resolve as resolve7 } from "path";
2669
- import { mkdirSync as mkdirSync3, existsSync as existsSync5 } from "fs";
3285
+ import { resolve as resolve8 } from "path";
3286
+ import { mkdirSync as mkdirSync5, existsSync as existsSync7 } from "fs";
2670
3287
  function registerAllTools(hivemindHome, config) {
2671
3288
  const registry = new ToolRegistry();
2672
3289
  if (config?.enabled === false) {
2673
3290
  return registry;
2674
3291
  }
2675
- const workspaceDir = resolve7(hivemindHome, config?.workspace || "workspace");
2676
- if (!existsSync5(workspaceDir)) {
2677
- mkdirSync3(workspaceDir, { recursive: true });
3292
+ const workspaceDir = resolve8(hivemindHome, config?.workspace || "workspace");
3293
+ if (!existsSync7(workspaceDir)) {
3294
+ mkdirSync5(workspaceDir, { recursive: true });
2678
3295
  }
2679
3296
  registerShellTool(registry, workspaceDir);
2680
3297
  registerFileTools(registry, workspaceDir);
2681
3298
  registerWebTools(registry, { braveApiKey: config?.braveApiKey });
3299
+ registerMemoryTools(registry, config?.memoryDaemonUrl || "http://localhost:3434");
2682
3300
  return registry;
2683
3301
  }
2684
3302
 
2685
3303
  // packages/runtime/src/pipeline.ts
2686
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync } from "fs";
2687
- import { resolve as resolve8, dirname as dirname6 } from "path";
3304
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
3305
+ import { resolve as resolve9, dirname as dirname7 } from "path";
2688
3306
  import { fileURLToPath as fileURLToPath3 } from "url";
2689
3307
  var PACKAGE_VERSION = "unknown";
2690
3308
  try {
2691
- const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
2692
- const pkg = JSON.parse(readFileSync7(resolve8(__dirname2, "../package.json"), "utf-8"));
3309
+ const __dirname2 = dirname7(fileURLToPath3(import.meta.url));
3310
+ const pkg = JSON.parse(readFileSync9(resolve9(__dirname2, "../package.json"), "utf-8"));
2693
3311
  PACKAGE_VERSION = pkg.version ?? "unknown";
2694
3312
  } catch {
2695
3313
  }
@@ -2725,7 +3343,7 @@ function writePidFile(path) {
2725
3343
  }
2726
3344
  function cleanupPidFile(path) {
2727
3345
  try {
2728
- unlinkSync(path);
3346
+ unlinkSync2(path);
2729
3347
  } catch {
2730
3348
  }
2731
3349
  }
@@ -2751,27 +3369,50 @@ async function startPipeline(configPath) {
2751
3369
  memoryConnected = true;
2752
3370
  console.log("[hivemind] Memory daemon connected");
2753
3371
  }
2754
- const requestLogger = new RequestLogger(resolve8(dirname6(configPath), "data", "dashboard.db"));
3372
+ const requestLogger = new RequestLogger(resolve9(dirname7(configPath), "data", "dashboard.db"));
2755
3373
  startDashboardServer(requestLogger, config.memory);
2756
3374
  const agent = new Agent(config);
2757
3375
  agent.setRequestLogger(requestLogger);
2758
- const hivemindHome = process.env.HIVEMIND_HOME || resolve8(process.env.HOME || "/root", "hivemind");
3376
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve9(process.env.HOME || "/root", "hivemind");
2759
3377
  const toolRegistry = registerAllTools(hivemindHome, {
2760
3378
  enabled: true,
2761
3379
  workspace: config.agent.workspace || "workspace",
2762
- braveApiKey: process.env.BRAVE_API_KEY
3380
+ braveApiKey: process.env.BRAVE_API_KEY,
3381
+ memoryDaemonUrl: config.memory.daemon_url
2763
3382
  });
2764
3383
  agent.setToolRegistry(toolRegistry);
2765
3384
  console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
3385
+ const dataDir = resolve9(dirname7(configPath), "data");
2766
3386
  if (config.sesame.api_key) {
2767
- await startSesameLoop(config, agent);
3387
+ await startSesameLoop(config, agent, dataDir);
2768
3388
  } else {
2769
3389
  console.log("[hivemind] No Sesame API key configured \u2014 running in stdin mode");
2770
3390
  await startStdinLoop(agent);
2771
3391
  }
2772
3392
  }
2773
- async function startSesameLoop(config, agent) {
3393
+ async function startSesameLoop(config, agent, dataDir) {
2774
3394
  const sesame = new SesameClient2(config.sesame);
3395
+ let eventsWatcher = null;
3396
+ if (dataDir) {
3397
+ eventsWatcher = new EventsWatcher(dataDir, async (channelId, text, filename, eventType) => {
3398
+ console.log(`[events] Firing ${eventType} event from ${filename}`);
3399
+ const eventMessage = `[EVENT:${filename}:${eventType}] ${text}`;
3400
+ try {
3401
+ const response = await agent.processMessage(eventMessage);
3402
+ if (response.content.trim() === "[SILENT]" || response.content.trim().startsWith("[SILENT]")) {
3403
+ console.log(`[events] Silent response for ${filename}`);
3404
+ return;
3405
+ }
3406
+ if (response.content.trim() === "__SKIP__") return;
3407
+ if (channelId) {
3408
+ await sesame.sendMessage(channelId, response.content);
3409
+ }
3410
+ } catch (err) {
3411
+ console.error(`[events] Error processing event ${filename}:`, err.message);
3412
+ }
3413
+ });
3414
+ eventsWatcher.start();
3415
+ }
2775
3416
  let shuttingDown = false;
2776
3417
  const shutdown = (signal) => {
2777
3418
  if (shuttingDown) return;
@@ -2836,6 +3477,11 @@ async function startSesameLoop(config, agent) {
2836
3477
  sesame.updatePresence("online", { emoji: "\u{1F7E2}" });
2837
3478
  return;
2838
3479
  }
3480
+ if (response.content.trim() === "[SILENT]" || response.content.trim().startsWith("[SILENT]")) {
3481
+ console.log(`[sesame] ${config.agent.name}: silent response`);
3482
+ sesame.updatePresence("online", { emoji: "\u{1F7E2}" });
3483
+ return;
3484
+ }
2839
3485
  const ctxPrefix = response.contextSwitched ? `[switched to ${response.context}] ` : "";
2840
3486
  let content = response.content;
2841
3487
  const prefixPatterns = [
@@ -2909,8 +3555,8 @@ ${response.content}
2909
3555
  console.error("Error:", err.message);
2910
3556
  }
2911
3557
  });
2912
- return new Promise((resolve9) => {
2913
- rl.on("close", resolve9);
3558
+ return new Promise((resolve10) => {
3559
+ rl.on("close", resolve10);
2914
3560
  });
2915
3561
  }
2916
3562
 
@@ -2939,20 +3585,20 @@ var WorkerServer = class {
2939
3585
  }
2940
3586
  /** Start listening. */
2941
3587
  async start() {
2942
- return new Promise((resolve9, reject) => {
3588
+ return new Promise((resolve10, reject) => {
2943
3589
  this.server = createServer3((req, res) => this.handleRequest(req, res));
2944
3590
  this.server.on("error", reject);
2945
- this.server.listen(this.port, () => resolve9());
3591
+ this.server.listen(this.port, () => resolve10());
2946
3592
  });
2947
3593
  }
2948
3594
  /** Stop the server. */
2949
3595
  async stop() {
2950
- return new Promise((resolve9) => {
3596
+ return new Promise((resolve10) => {
2951
3597
  if (!this.server) {
2952
- resolve9();
3598
+ resolve10();
2953
3599
  return;
2954
3600
  }
2955
- this.server.close(() => resolve9());
3601
+ this.server.close(() => resolve10());
2956
3602
  });
2957
3603
  }
2958
3604
  getPort() {
@@ -3075,10 +3721,10 @@ var WorkerServer = class {
3075
3721
  }
3076
3722
  };
3077
3723
  function readBody(req) {
3078
- return new Promise((resolve9, reject) => {
3724
+ return new Promise((resolve10, reject) => {
3079
3725
  const chunks = [];
3080
3726
  req.on("data", (chunk) => chunks.push(chunk));
3081
- req.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
3727
+ req.on("end", () => resolve10(Buffer.concat(chunks).toString("utf-8")));
3082
3728
  req.on("error", reject);
3083
3729
  });
3084
3730
  }
@@ -3351,7 +3997,13 @@ export {
3351
3997
  TaskEngine,
3352
3998
  buildSystemPrompt,
3353
3999
  buildMessages,
4000
+ SessionStore,
4001
+ estimateTokens,
4002
+ estimateMessageTokens,
4003
+ getModelContextWindow,
4004
+ CompactionManager,
3354
4005
  Agent,
4006
+ EventsWatcher,
3355
4007
  defaultSentinelConfig,
3356
4008
  loadConfig,
3357
4009
  SesameClient2 as SesameClient,
@@ -3405,4 +4057,4 @@ smol-toml/dist/index.js:
3405
4057
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3406
4058
  *)
3407
4059
  */
3408
- //# sourceMappingURL=chunk-LQ7CYHSQ.js.map
4060
+ //# sourceMappingURL=chunk-DXQ3GROV.js.map