@sesamespace/hivemind 0.6.1 → 0.7.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.
@@ -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);
@@ -902,9 +1349,219 @@ var Agent = class {
902
1349
  }
903
1350
  };
904
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
+
905
1562
  // packages/runtime/src/config.ts
906
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
907
- 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";
908
1565
 
909
1566
  // node_modules/.pnpm/smol-toml@1.6.0/node_modules/smol-toml/dist/error.js
910
1567
  function getLineColFromPtr(string, ptr) {
@@ -1628,12 +2285,12 @@ function deepMerge(target, source) {
1628
2285
  return result;
1629
2286
  }
1630
2287
  function loadConfig(path) {
1631
- const raw = readFileSync2(path, "utf-8");
2288
+ const raw = readFileSync4(path, "utf-8");
1632
2289
  let parsed = parse(raw);
1633
- const configDir = dirname(path);
1634
- const localPath = resolve2(configDir, "local.toml");
1635
- if (existsSync2(localPath)) {
1636
- 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");
1637
2294
  const localParsed = parse(localRaw);
1638
2295
  parsed = deepMerge(parsed, localParsed);
1639
2296
  console.log(`[config] Merged overrides from ${localPath}`);
@@ -1682,20 +2339,20 @@ function loadConfig(path) {
1682
2339
  parsed.sentinel.stop_flag_file = process.env.SENTINEL_STOP_FLAG_FILE;
1683
2340
  }
1684
2341
  if (parsed.agent.workspace && !parsed.agent.workspace.startsWith("/")) {
1685
- const configDir2 = dirname(path);
1686
- parsed.agent.workspace = resolve2(configDir2, "..", parsed.agent.workspace);
2342
+ const configDir2 = dirname2(path);
2343
+ parsed.agent.workspace = resolve3(configDir2, "..", parsed.agent.workspace);
1687
2344
  }
1688
2345
  return parsed;
1689
2346
  }
1690
2347
 
1691
2348
  // packages/runtime/src/sesame.ts
1692
- import { readFileSync as readFileSync3 } from "fs";
1693
- 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";
1694
2351
  import { fileURLToPath } from "url";
1695
2352
  var RUNTIME_VERSION = "unknown";
1696
2353
  try {
1697
- const __dirname2 = dirname2(fileURLToPath(import.meta.url));
1698
- 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"));
1699
2356
  RUNTIME_VERSION = pkg.version ?? "unknown";
1700
2357
  } catch {
1701
2358
  }
@@ -1864,24 +2521,24 @@ var HEALTH_PORT = 9484;
1864
2521
  var HEALTH_PATH = "/health";
1865
2522
 
1866
2523
  // packages/runtime/src/request-logger.ts
1867
- import { randomUUID } from "crypto";
1868
- import { mkdirSync, existsSync as existsSync3, appendFileSync, readFileSync as readFileSync4, writeFileSync } from "fs";
1869
- 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";
1870
2527
  var RequestLogger = class {
1871
2528
  logPath;
1872
2529
  maxAgeDays = 7;
1873
2530
  constructor(dbPath) {
1874
2531
  this.logPath = dbPath.replace(/\.db$/, ".jsonl");
1875
2532
  if (this.logPath === dbPath) this.logPath = dbPath + ".jsonl";
1876
- const dir = dirname3(this.logPath);
1877
- if (!existsSync3(dir)) mkdirSync(dir, { recursive: true });
2533
+ const dir = dirname4(this.logPath);
2534
+ if (!existsSync5(dir)) mkdirSync3(dir, { recursive: true });
1878
2535
  this.prune();
1879
2536
  }
1880
2537
  prune() {
1881
- if (!existsSync3(this.logPath)) return;
2538
+ if (!existsSync5(this.logPath)) return;
1882
2539
  const cutoff = new Date(Date.now() - this.maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
1883
2540
  try {
1884
- const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2541
+ const lines = readFileSync6(this.logPath, "utf-8").split("\n").filter(Boolean);
1885
2542
  const kept = [];
1886
2543
  let pruned = 0;
1887
2544
  for (const line of lines) {
@@ -1903,7 +2560,7 @@ var RequestLogger = class {
1903
2560
  }
1904
2561
  }
1905
2562
  log(entry) {
1906
- const id = randomUUID();
2563
+ const id = randomUUID2();
1907
2564
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1908
2565
  const sysTokens = Math.ceil(entry.systemPromptComponents.fullText.length / 4);
1909
2566
  const histTokens = Math.ceil(
@@ -1933,7 +2590,7 @@ var RequestLogger = class {
1933
2590
  token_est_user: userTokens,
1934
2591
  token_est_total: sysTokens + histTokens + userTokens
1935
2592
  };
1936
- appendFileSync(this.logPath, JSON.stringify(record) + "\n");
2593
+ appendFileSync2(this.logPath, JSON.stringify(record) + "\n");
1937
2594
  return id;
1938
2595
  }
1939
2596
  getRequests(opts = {}) {
@@ -1957,9 +2614,9 @@ var RequestLogger = class {
1957
2614
  close() {
1958
2615
  }
1959
2616
  readAll() {
1960
- if (!existsSync3(this.logPath)) return [];
2617
+ if (!existsSync5(this.logPath)) return [];
1961
2618
  try {
1962
- const lines = readFileSync4(this.logPath, "utf-8").split("\n").filter(Boolean);
2619
+ const lines = readFileSync6(this.logPath, "utf-8").split("\n").filter(Boolean);
1963
2620
  const entries = [];
1964
2621
  for (const line of lines) {
1965
2622
  try {
@@ -1976,17 +2633,17 @@ var RequestLogger = class {
1976
2633
 
1977
2634
  // packages/runtime/src/dashboard.ts
1978
2635
  import { createServer } from "http";
1979
- import { readFileSync as readFileSync5 } from "fs";
1980
- 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";
1981
2638
  import { fileURLToPath as fileURLToPath2 } from "url";
1982
- var __dirname = dirname4(fileURLToPath2(import.meta.url));
2639
+ var __dirname = dirname5(fileURLToPath2(import.meta.url));
1983
2640
  var DASHBOARD_PORT = 9485;
1984
2641
  var spaHtml = null;
1985
2642
  function getSpaHtml() {
1986
2643
  if (!spaHtml) {
1987
- for (const dir of [__dirname, resolve4(__dirname, "../src")]) {
2644
+ for (const dir of [__dirname, resolve5(__dirname, "../src")]) {
1988
2645
  try {
1989
- spaHtml = readFileSync5(resolve4(dir, "dashboard.html"), "utf-8");
2646
+ spaHtml = readFileSync7(resolve5(dir, "dashboard.html"), "utf-8");
1990
2647
  break;
1991
2648
  } catch {
1992
2649
  }
@@ -2158,7 +2815,7 @@ var ToolRegistry = class {
2158
2815
 
2159
2816
  // packages/runtime/src/tools/shell.ts
2160
2817
  import { execSync } from "child_process";
2161
- import { resolve as resolve5 } from "path";
2818
+ import { resolve as resolve6 } from "path";
2162
2819
  var MAX_OUTPUT = 5e4;
2163
2820
  function registerShellTool(registry, workspaceDir) {
2164
2821
  registry.register(
@@ -2185,7 +2842,7 @@ function registerShellTool(registry, workspaceDir) {
2185
2842
  async (params) => {
2186
2843
  const command = params.command;
2187
2844
  const timeout = (params.timeout || 30) * 1e3;
2188
- const cwd = params.workdir ? resolve5(workspaceDir, params.workdir) : workspaceDir;
2845
+ const cwd = params.workdir ? resolve6(workspaceDir, params.workdir) : workspaceDir;
2189
2846
  try {
2190
2847
  const output = execSync(command, {
2191
2848
  cwd,
@@ -2212,8 +2869,8 @@ ${output || err.message}`;
2212
2869
  }
2213
2870
 
2214
2871
  // packages/runtime/src/tools/files.ts
2215
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
2216
- 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";
2217
2874
  var MAX_READ = 1e5;
2218
2875
  function registerFileTools(registry, workspaceDir) {
2219
2876
  registry.register(
@@ -2239,11 +2896,11 @@ function registerFileTools(registry, workspaceDir) {
2239
2896
  },
2240
2897
  async (params) => {
2241
2898
  const filePath = resolvePath(workspaceDir, params.path);
2242
- if (!existsSync4(filePath)) {
2899
+ if (!existsSync6(filePath)) {
2243
2900
  return `Error: File not found: ${filePath}`;
2244
2901
  }
2245
2902
  try {
2246
- let content = readFileSync6(filePath, "utf-8");
2903
+ let content = readFileSync8(filePath, "utf-8");
2247
2904
  if (params.offset || params.limit) {
2248
2905
  const lines = content.split("\n");
2249
2906
  const start = (params.offset || 1) - 1;
@@ -2280,8 +2937,8 @@ function registerFileTools(registry, workspaceDir) {
2280
2937
  async (params) => {
2281
2938
  const filePath = resolvePath(workspaceDir, params.path);
2282
2939
  try {
2283
- const dir = dirname5(filePath);
2284
- if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
2940
+ const dir = dirname6(filePath);
2941
+ if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
2285
2942
  writeFileSync2(filePath, params.content);
2286
2943
  return `Written ${params.content.length} bytes to ${filePath}`;
2287
2944
  } catch (err) {
@@ -2312,11 +2969,11 @@ function registerFileTools(registry, workspaceDir) {
2312
2969
  },
2313
2970
  async (params) => {
2314
2971
  const filePath = resolvePath(workspaceDir, params.path);
2315
- if (!existsSync4(filePath)) {
2972
+ if (!existsSync6(filePath)) {
2316
2973
  return `Error: File not found: ${filePath}`;
2317
2974
  }
2318
2975
  try {
2319
- const content = readFileSync6(filePath, "utf-8");
2976
+ const content = readFileSync8(filePath, "utf-8");
2320
2977
  const oldText = params.old_text;
2321
2978
  const newText = params.new_text;
2322
2979
  if (!content.includes(oldText)) {
@@ -2344,18 +3001,18 @@ function registerFileTools(registry, workspaceDir) {
2344
3001
  required: []
2345
3002
  },
2346
3003
  async (params) => {
2347
- const { readdirSync: readdirSync2, statSync } = await import("fs");
3004
+ const { readdirSync: readdirSync3, statSync: statSync2 } = await import("fs");
2348
3005
  const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
2349
- if (!existsSync4(dirPath)) {
3006
+ if (!existsSync6(dirPath)) {
2350
3007
  return `Error: Directory not found: ${dirPath}`;
2351
3008
  }
2352
3009
  try {
2353
- const entries = readdirSync2(dirPath);
3010
+ const entries = readdirSync3(dirPath);
2354
3011
  const results = [];
2355
3012
  for (const entry of entries) {
2356
3013
  if (entry.startsWith(".")) continue;
2357
3014
  try {
2358
- const stat = statSync(resolve6(dirPath, entry));
3015
+ const stat = statSync2(resolve7(dirPath, entry));
2359
3016
  results.push(stat.isDirectory() ? `${entry}/` : entry);
2360
3017
  } catch {
2361
3018
  results.push(entry);
@@ -2372,7 +3029,7 @@ function resolvePath(workspace, path) {
2372
3029
  if (path.startsWith("/") || path.startsWith("~")) {
2373
3030
  return path.replace(/^~/, process.env.HOME || "/root");
2374
3031
  }
2375
- return resolve6(workspace, path);
3032
+ return resolve7(workspace, path);
2376
3033
  }
2377
3034
 
2378
3035
  // packages/runtime/src/tools/web.ts
@@ -2625,16 +3282,16 @@ Contexts:
2625
3282
  }
2626
3283
 
2627
3284
  // packages/runtime/src/tools/register.ts
2628
- import { resolve as resolve7 } from "path";
2629
- 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";
2630
3287
  function registerAllTools(hivemindHome, config) {
2631
3288
  const registry = new ToolRegistry();
2632
3289
  if (config?.enabled === false) {
2633
3290
  return registry;
2634
3291
  }
2635
- const workspaceDir = resolve7(hivemindHome, config?.workspace || "workspace");
2636
- if (!existsSync5(workspaceDir)) {
2637
- mkdirSync3(workspaceDir, { recursive: true });
3292
+ const workspaceDir = resolve8(hivemindHome, config?.workspace || "workspace");
3293
+ if (!existsSync7(workspaceDir)) {
3294
+ mkdirSync5(workspaceDir, { recursive: true });
2638
3295
  }
2639
3296
  registerShellTool(registry, workspaceDir);
2640
3297
  registerFileTools(registry, workspaceDir);
@@ -2644,13 +3301,13 @@ function registerAllTools(hivemindHome, config) {
2644
3301
  }
2645
3302
 
2646
3303
  // packages/runtime/src/pipeline.ts
2647
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, unlinkSync } from "fs";
2648
- 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";
2649
3306
  import { fileURLToPath as fileURLToPath3 } from "url";
2650
3307
  var PACKAGE_VERSION = "unknown";
2651
3308
  try {
2652
- const __dirname2 = dirname6(fileURLToPath3(import.meta.url));
2653
- 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"));
2654
3311
  PACKAGE_VERSION = pkg.version ?? "unknown";
2655
3312
  } catch {
2656
3313
  }
@@ -2686,7 +3343,7 @@ function writePidFile(path) {
2686
3343
  }
2687
3344
  function cleanupPidFile(path) {
2688
3345
  try {
2689
- unlinkSync(path);
3346
+ unlinkSync2(path);
2690
3347
  } catch {
2691
3348
  }
2692
3349
  }
@@ -2712,11 +3369,11 @@ async function startPipeline(configPath) {
2712
3369
  memoryConnected = true;
2713
3370
  console.log("[hivemind] Memory daemon connected");
2714
3371
  }
2715
- const requestLogger = new RequestLogger(resolve8(dirname6(configPath), "data", "dashboard.db"));
3372
+ const requestLogger = new RequestLogger(resolve9(dirname7(configPath), "data", "dashboard.db"));
2716
3373
  startDashboardServer(requestLogger, config.memory);
2717
3374
  const agent = new Agent(config);
2718
3375
  agent.setRequestLogger(requestLogger);
2719
- 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");
2720
3377
  const toolRegistry = registerAllTools(hivemindHome, {
2721
3378
  enabled: true,
2722
3379
  workspace: config.agent.workspace || "workspace",
@@ -2725,15 +3382,37 @@ async function startPipeline(configPath) {
2725
3382
  });
2726
3383
  agent.setToolRegistry(toolRegistry);
2727
3384
  console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
3385
+ const dataDir = resolve9(hivemindHome, "data");
2728
3386
  if (config.sesame.api_key) {
2729
- await startSesameLoop(config, agent);
3387
+ await startSesameLoop(config, agent, dataDir);
2730
3388
  } else {
2731
3389
  console.log("[hivemind] No Sesame API key configured \u2014 running in stdin mode");
2732
3390
  await startStdinLoop(agent);
2733
3391
  }
2734
3392
  }
2735
- async function startSesameLoop(config, agent) {
3393
+ async function startSesameLoop(config, agent, dataDir) {
2736
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
+ }
2737
3416
  let shuttingDown = false;
2738
3417
  const shutdown = (signal) => {
2739
3418
  if (shuttingDown) return;
@@ -2798,6 +3477,11 @@ async function startSesameLoop(config, agent) {
2798
3477
  sesame.updatePresence("online", { emoji: "\u{1F7E2}" });
2799
3478
  return;
2800
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
+ }
2801
3485
  const ctxPrefix = response.contextSwitched ? `[switched to ${response.context}] ` : "";
2802
3486
  let content = response.content;
2803
3487
  const prefixPatterns = [
@@ -2871,8 +3555,8 @@ ${response.content}
2871
3555
  console.error("Error:", err.message);
2872
3556
  }
2873
3557
  });
2874
- return new Promise((resolve9) => {
2875
- rl.on("close", resolve9);
3558
+ return new Promise((resolve10) => {
3559
+ rl.on("close", resolve10);
2876
3560
  });
2877
3561
  }
2878
3562
 
@@ -2901,20 +3585,20 @@ var WorkerServer = class {
2901
3585
  }
2902
3586
  /** Start listening. */
2903
3587
  async start() {
2904
- return new Promise((resolve9, reject) => {
3588
+ return new Promise((resolve10, reject) => {
2905
3589
  this.server = createServer3((req, res) => this.handleRequest(req, res));
2906
3590
  this.server.on("error", reject);
2907
- this.server.listen(this.port, () => resolve9());
3591
+ this.server.listen(this.port, () => resolve10());
2908
3592
  });
2909
3593
  }
2910
3594
  /** Stop the server. */
2911
3595
  async stop() {
2912
- return new Promise((resolve9) => {
3596
+ return new Promise((resolve10) => {
2913
3597
  if (!this.server) {
2914
- resolve9();
3598
+ resolve10();
2915
3599
  return;
2916
3600
  }
2917
- this.server.close(() => resolve9());
3601
+ this.server.close(() => resolve10());
2918
3602
  });
2919
3603
  }
2920
3604
  getPort() {
@@ -3037,10 +3721,10 @@ var WorkerServer = class {
3037
3721
  }
3038
3722
  };
3039
3723
  function readBody(req) {
3040
- return new Promise((resolve9, reject) => {
3724
+ return new Promise((resolve10, reject) => {
3041
3725
  const chunks = [];
3042
3726
  req.on("data", (chunk) => chunks.push(chunk));
3043
- req.on("end", () => resolve9(Buffer.concat(chunks).toString("utf-8")));
3727
+ req.on("end", () => resolve10(Buffer.concat(chunks).toString("utf-8")));
3044
3728
  req.on("error", reject);
3045
3729
  });
3046
3730
  }
@@ -3313,7 +3997,13 @@ export {
3313
3997
  TaskEngine,
3314
3998
  buildSystemPrompt,
3315
3999
  buildMessages,
4000
+ SessionStore,
4001
+ estimateTokens,
4002
+ estimateMessageTokens,
4003
+ getModelContextWindow,
4004
+ CompactionManager,
3316
4005
  Agent,
4006
+ EventsWatcher,
3317
4007
  defaultSentinelConfig,
3318
4008
  loadConfig,
3319
4009
  SesameClient2 as SesameClient,
@@ -3367,4 +4057,4 @@ smol-toml/dist/index.js:
3367
4057
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3368
4058
  *)
3369
4059
  */
3370
- //# sourceMappingURL=chunk-7YHRRM5B.js.map
4060
+ //# sourceMappingURL=chunk-G6PPTVAS.js.map