@sesamespace/hivemind 0.12.4 → 0.12.6

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.
@@ -3049,7 +3049,7 @@ var SesameClient = class {
3049
3049
  * Connect to the real-time WebSocket gateway.
3050
3050
  */
3051
3051
  connect() {
3052
- return new Promise((resolve21, reject) => {
3052
+ return new Promise((resolve22, reject) => {
3053
3053
  try {
3054
3054
  this.ws = new WebSocket(`${this.config.wsUrl}/v1/connect`);
3055
3055
  this.ws.on("open", () => {
@@ -3063,7 +3063,7 @@ var SesameClient = class {
3063
3063
  this.authenticated = true;
3064
3064
  this.startHeartbeat(event.heartbeatIntervalMs ?? 3e4);
3065
3065
  this.sendReplay();
3066
- resolve21();
3066
+ resolve22();
3067
3067
  return;
3068
3068
  }
3069
3069
  if (event.type === "pong")
@@ -3249,6 +3249,10 @@ var SesameClient2 = class {
3249
3249
  agentId = null;
3250
3250
  channels = /* @__PURE__ */ new Map();
3251
3251
  typingIntervals = /* @__PURE__ */ new Map();
3252
+ /** Messages held briefly to allow streaming edits to replace initial fragments */
3253
+ pendingMessages = /* @__PURE__ */ new Map();
3254
+ /** How long to wait for streaming edits before delivering (ms) */
3255
+ MESSAGE_HOLD_MS = 3e3;
3252
3256
  constructor(config) {
3253
3257
  this.config = config;
3254
3258
  this.sdk = new SesameClient({
@@ -3305,8 +3309,9 @@ var SesameClient2 = class {
3305
3309
  if (senderId === this.agentId) return;
3306
3310
  if (!this.messageHandler || !msg.content) return;
3307
3311
  const channelInfo = this.channels.get(msg.channelId);
3308
- this.messageHandler({
3309
- id: msg.id || "unknown",
3312
+ const messageId = msg.id || "unknown";
3313
+ const sesameMsg = {
3314
+ id: messageId,
3310
3315
  channelId: msg.channelId || "unknown",
3311
3316
  channelKind: channelInfo?.kind || "dm",
3312
3317
  content: msg.content,
@@ -3315,7 +3320,52 @@ var SesameClient2 = class {
3315
3320
  handle: msg.senderHandle || msg.metadata?.senderHandle || "unknown"
3316
3321
  },
3317
3322
  timestamp: msg.createdAt || (/* @__PURE__ */ new Date()).toISOString()
3318
- });
3323
+ };
3324
+ const existing = this.pendingMessages.get(messageId);
3325
+ if (existing && !existing.delivered) {
3326
+ existing.message = sesameMsg;
3327
+ return;
3328
+ }
3329
+ const pending = {
3330
+ message: sesameMsg,
3331
+ delivered: false,
3332
+ timer: setTimeout(() => {
3333
+ const p = this.pendingMessages.get(messageId);
3334
+ if (p && !p.delivered && this.messageHandler) {
3335
+ p.delivered = true;
3336
+ this.messageHandler(p.message);
3337
+ }
3338
+ this.pendingMessages.delete(messageId);
3339
+ }, this.MESSAGE_HOLD_MS)
3340
+ };
3341
+ this.pendingMessages.set(messageId, pending);
3342
+ });
3343
+ this.sdk.on("message.edited", (event) => {
3344
+ const msg = event.data || event.message || event;
3345
+ const senderId = msg.senderId || msg.sender?.id;
3346
+ if (senderId === this.agentId) return;
3347
+ if (event.streaming) return;
3348
+ const messageId = msg.id || "unknown";
3349
+ const channelInfo = this.channels.get(msg.channelId);
3350
+ const updatedMsg = {
3351
+ id: messageId,
3352
+ channelId: msg.channelId || "unknown",
3353
+ channelKind: channelInfo?.kind || "dm",
3354
+ content: msg.content,
3355
+ author: {
3356
+ id: senderId || "unknown",
3357
+ handle: msg.senderHandle || msg.metadata?.senderHandle || "unknown"
3358
+ },
3359
+ timestamp: msg.updatedAt || msg.createdAt || (/* @__PURE__ */ new Date()).toISOString()
3360
+ };
3361
+ const pending = this.pendingMessages.get(messageId);
3362
+ if (pending && !pending.delivered) {
3363
+ pending.message = updatedMsg;
3364
+ return;
3365
+ }
3366
+ if (this.messageHandler && msg.content) {
3367
+ this.messageHandler(updatedMsg);
3368
+ }
3319
3369
  });
3320
3370
  await this.sdk.connect();
3321
3371
  console.log("[sesame] WebSocket connected");
@@ -4225,12 +4275,12 @@ var LogWatcher = class extends BackgroundProcess {
4225
4275
  }
4226
4276
  savePositions() {
4227
4277
  try {
4228
- const { mkdirSync: mkdirSync16, writeFileSync: writeFileSync10 } = __require("fs");
4229
- const { dirname: dirname9 } = __require("path");
4230
- const dir = dirname9(this.positionsFile);
4231
- if (!existsSync7(dir)) mkdirSync16(dir, { recursive: true });
4278
+ const { mkdirSync: mkdirSync17, writeFileSync: writeFileSync11 } = __require("fs");
4279
+ const { dirname: dirname10 } = __require("path");
4280
+ const dir = dirname10(this.positionsFile);
4281
+ if (!existsSync7(dir)) mkdirSync17(dir, { recursive: true });
4232
4282
  const data = Object.fromEntries(this.tailPositions);
4233
- writeFileSync10(this.positionsFile, JSON.stringify(data));
4283
+ writeFileSync11(this.positionsFile, JSON.stringify(data));
4234
4284
  } catch (err) {
4235
4285
  log2.warn("failed to save tail positions", {
4236
4286
  error: err.message
@@ -4750,10 +4800,10 @@ function parseQuery(url) {
4750
4800
  return params;
4751
4801
  }
4752
4802
  function readBody(req) {
4753
- return new Promise((resolve21, reject) => {
4803
+ return new Promise((resolve22, reject) => {
4754
4804
  const chunks = [];
4755
4805
  req.on("data", (chunk) => chunks.push(chunk));
4756
- req.on("end", () => resolve21(Buffer.concat(chunks).toString()));
4806
+ req.on("end", () => resolve22(Buffer.concat(chunks).toString()));
4757
4807
  req.on("error", reject);
4758
4808
  });
4759
4809
  }
@@ -5178,13 +5228,13 @@ function registerFileTools(registry, workspaceDir) {
5178
5228
  required: []
5179
5229
  },
5180
5230
  async (params) => {
5181
- const { readdirSync: readdirSync5, statSync: statSync5 } = await import("fs");
5231
+ const { readdirSync: readdirSync6, statSync: statSync5 } = await import("fs");
5182
5232
  const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
5183
5233
  if (!existsSync10(dirPath)) {
5184
5234
  return `Error: Directory not found: ${dirPath}`;
5185
5235
  }
5186
5236
  try {
5187
- const entries = readdirSync5(dirPath);
5237
+ const entries = readdirSync6(dirPath);
5188
5238
  const results = [];
5189
5239
  for (const entry of entries) {
5190
5240
  if (entry.startsWith(".")) continue;
@@ -7898,7 +7948,7 @@ function registerCodingAgentTools(registry, workspaceDir, contextBridge) {
7898
7948
  contextBridge.on("error-encountered", errorHandler);
7899
7949
  }
7900
7950
  try {
7901
- const output = await new Promise((resolve21, reject) => {
7951
+ const output = await new Promise((resolve22, reject) => {
7902
7952
  const child = exec2(command, {
7903
7953
  cwd,
7904
7954
  timeout: timeoutSeconds * 1e3,
@@ -7915,7 +7965,7 @@ function registerCodingAgentTools(registry, workspaceDir, contextBridge) {
7915
7965
  error.stderr = stderr;
7916
7966
  reject(error);
7917
7967
  } else {
7918
- resolve21(stdout);
7968
+ resolve22(stdout);
7919
7969
  }
7920
7970
  });
7921
7971
  child.stdin?.end();
@@ -7966,6 +8016,9 @@ ${output || err.message}`;
7966
8016
  if (contextBridge && errorHandler) {
7967
8017
  contextBridge.off("error-encountered", errorHandler);
7968
8018
  }
8019
+ if (contextBridge) {
8020
+ contextBridge.cleanupContextFile();
8021
+ }
7969
8022
  }
7970
8023
  }
7971
8024
  );
@@ -8633,12 +8686,12 @@ Path: ${skillDir}`;
8633
8686
  }
8634
8687
 
8635
8688
  // packages/runtime/src/pipeline.ts
8636
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3 } from "fs";
8637
- import { resolve as resolve20, dirname as dirname8 } from "path";
8689
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync10, unlinkSync as unlinkSync4 } from "fs";
8690
+ import { resolve as resolve21, dirname as dirname9 } from "path";
8638
8691
  import { fileURLToPath as fileURLToPath3 } from "url";
8639
8692
 
8640
8693
  // packages/runtime/src/tools/context-bridge.ts
8641
- import { writeFileSync as writeFileSync8, readFileSync as readFileSync15, statSync as statSync4, existsSync as existsSync20, mkdirSync as mkdirSync15, openSync, readSync, closeSync } from "fs";
8694
+ import { writeFileSync as writeFileSync8, readFileSync as readFileSync15, statSync as statSync4, existsSync as existsSync20, mkdirSync as mkdirSync15, openSync, readSync, closeSync, readdirSync as readdirSync5, unlinkSync as unlinkSync3 } from "fs";
8642
8695
  import { createHash as createHash2 } from "crypto";
8643
8696
  import { join as join8 } from "path";
8644
8697
  import { homedir } from "os";
@@ -8649,16 +8702,20 @@ var ClaudeContextBridge = class extends EventEmitter2 {
8649
8702
  logger;
8650
8703
  sessionLogPath;
8651
8704
  contextFilePath;
8705
+ sessionArchiveDir;
8652
8706
  watchInterval = null;
8653
8707
  lastLogSize = 0;
8654
8708
  seenPatterns = /* @__PURE__ */ new Set();
8709
+ currentSessionId;
8655
8710
  constructor(config) {
8656
8711
  super();
8657
8712
  this.workspaceDir = config.workspaceDir;
8658
8713
  this.memoryClient = config.memoryClient;
8659
8714
  this.logger = config.logger;
8660
8715
  this.sessionLogPath = join8(homedir(), ".claude", "session.log");
8661
- this.contextFilePath = join8(this.workspaceDir, "CLAUDE_CONTEXT.md");
8716
+ this.sessionArchiveDir = join8(homedir(), ".claude", "sessions");
8717
+ this.contextFilePath = join8(this.workspaceDir, ".claude-context-current.md");
8718
+ this.currentSessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
8662
8719
  }
8663
8720
  /**
8664
8721
  * Initialize the context bridge
@@ -8668,10 +8725,49 @@ var ClaudeContextBridge = class extends EventEmitter2 {
8668
8725
  if (!existsSync20(claudeDir)) {
8669
8726
  mkdirSync15(claudeDir, { recursive: true });
8670
8727
  }
8728
+ if (!existsSync20(this.sessionArchiveDir)) {
8729
+ mkdirSync15(this.sessionArchiveDir, { recursive: true });
8730
+ }
8731
+ await this.archiveSessionLog();
8671
8732
  await this.setupClaudeSettings();
8672
8733
  this.startLogMonitoring();
8673
8734
  this.logger.info("Claude context bridge initialized");
8674
8735
  }
8736
+ /**
8737
+ * Archive previous session log if it exists
8738
+ */
8739
+ async archiveSessionLog() {
8740
+ if (existsSync20(this.sessionLogPath)) {
8741
+ const stats = statSync4(this.sessionLogPath);
8742
+ if (stats.size > 0) {
8743
+ const archivePath = join8(this.sessionArchiveDir, `${this.currentSessionId}.log`);
8744
+ const content = readFileSync15(this.sessionLogPath, "utf-8");
8745
+ writeFileSync8(archivePath, content);
8746
+ writeFileSync8(this.sessionLogPath, "");
8747
+ this.logger.info("Archived previous session log", { path: archivePath });
8748
+ this.cleanupOldArchives();
8749
+ }
8750
+ }
8751
+ }
8752
+ /**
8753
+ * Clean up old session archives, keeping only the most recent ones
8754
+ */
8755
+ cleanupOldArchives() {
8756
+ try {
8757
+ const files = readdirSync5(this.sessionArchiveDir).filter((f) => f.endsWith(".log")).map((f) => ({
8758
+ name: f,
8759
+ path: join8(this.sessionArchiveDir, f),
8760
+ mtime: statSync4(join8(this.sessionArchiveDir, f)).mtime
8761
+ })).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
8762
+ const toDelete = files.slice(10);
8763
+ for (const file of toDelete) {
8764
+ unlinkSync3(file.path);
8765
+ this.logger.debug("Deleted old session archive", { file: file.name });
8766
+ }
8767
+ } catch (err) {
8768
+ this.logger.warn("Failed to cleanup old archives", { error: String(err) });
8769
+ }
8770
+ }
8675
8771
  /**
8676
8772
  * Configure Claude Code to report insights via workspace CLAUDE.md
8677
8773
  */
@@ -8686,7 +8782,7 @@ When working on tasks, please report significant findings by writing to the sess
8686
8782
  - When you find reusable solutions: echo "PATTERN: [approach]" >> ~/.claude/session.log
8687
8783
  - When you encounter errors: echo "ERROR: [error details]" >> ~/.claude/session.log
8688
8784
 
8689
- Always check CLAUDE_CONTEXT.md in the workspace for task context and requirements.`;
8785
+ Always check .claude-context-current.md in the workspace for task context and requirements.`;
8690
8786
  const claudeMdPath = join8(this.workspaceDir, "CLAUDE.md");
8691
8787
  writeFileSync8(claudeMdPath, instructions);
8692
8788
  this.logger.info("Claude workspace CLAUDE.md configured for context bridge");
@@ -8940,6 +9036,15 @@ ${memories.join("\n\n")}
8940
9036
  };
8941
9037
  }
8942
9038
  }
9039
+ /**
9040
+ * Clean up just the context file (for use after a session)
9041
+ */
9042
+ cleanupContextFile() {
9043
+ if (existsSync20(this.contextFilePath)) {
9044
+ unlinkSync3(this.contextFilePath);
9045
+ this.logger.info("Cleaned up context file");
9046
+ }
9047
+ }
8943
9048
  /**
8944
9049
  * Clean up resources
8945
9050
  */
@@ -8950,6 +9055,7 @@ ${memories.join("\n\n")}
8950
9055
  }
8951
9056
  this.seenPatterns.clear();
8952
9057
  this.removeAllListeners();
9058
+ this.cleanupContextFile();
8953
9059
  }
8954
9060
  };
8955
9061
  async function createContextBridge(config) {
@@ -8958,11 +9064,516 @@ async function createContextBridge(config) {
8958
9064
  return bridge;
8959
9065
  }
8960
9066
 
9067
+ // packages/runtime/src/sesame-sync.ts
9068
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync21, mkdirSync as mkdirSync16 } from "fs";
9069
+ import { resolve as resolve20, dirname as dirname8 } from "path";
9070
+
9071
+ // packages/runtime/src/sesame-api.ts
9072
+ var SesameAPI = class {
9073
+ baseUrl;
9074
+ apiKey;
9075
+ rateLimitRemaining = 120;
9076
+ rateLimitResetAt = 0;
9077
+ constructor(config) {
9078
+ this.baseUrl = config.api_url.endsWith("/api/v1") ? config.api_url : `${config.api_url}/api/v1`;
9079
+ this.apiKey = config.api_key;
9080
+ }
9081
+ // ── Internal helpers ──
9082
+ async request(method, path, body) {
9083
+ if (this.rateLimitRemaining <= 5 && Date.now() < this.rateLimitResetAt) {
9084
+ const waitMs = this.rateLimitResetAt - Date.now();
9085
+ console.warn(`[sesame-api] Rate limit low (${this.rateLimitRemaining}), waiting ${waitMs}ms`);
9086
+ await new Promise((r) => setTimeout(r, Math.min(waitMs, 5e3)));
9087
+ }
9088
+ const url = `${this.baseUrl}${path}`;
9089
+ const headers = {
9090
+ Authorization: `Bearer ${this.apiKey}`,
9091
+ "Content-Type": "application/json"
9092
+ };
9093
+ const options = { method, headers };
9094
+ if (body && method !== "GET") {
9095
+ options.body = JSON.stringify(body);
9096
+ }
9097
+ try {
9098
+ const response = await fetch(url, options);
9099
+ const remaining = response.headers.get("x-ratelimit-remaining");
9100
+ const reset = response.headers.get("x-ratelimit-reset");
9101
+ if (remaining) this.rateLimitRemaining = parseInt(remaining, 10);
9102
+ if (reset) this.rateLimitResetAt = parseInt(reset, 10) * 1e3;
9103
+ if (!response.ok) {
9104
+ const errorBody = await response.text();
9105
+ let parsed;
9106
+ try {
9107
+ parsed = JSON.parse(errorBody);
9108
+ } catch {
9109
+ parsed = { error: errorBody };
9110
+ }
9111
+ const err = new Error(`Sesame API ${method} ${path}: ${response.status} ${parsed.error || errorBody}`);
9112
+ err.status = response.status;
9113
+ err.details = parsed;
9114
+ throw err;
9115
+ }
9116
+ const data = await response.json();
9117
+ return data.data ?? data;
9118
+ } catch (err) {
9119
+ if (err.status) throw err;
9120
+ console.error(`[sesame-api] Network error on ${method} ${path}:`, err.message);
9121
+ throw err;
9122
+ }
9123
+ }
9124
+ // ── Wake ──
9125
+ async wake(agentId) {
9126
+ return this.request("GET", `/agents/${agentId}/wake`);
9127
+ }
9128
+ // ── Agent State ──
9129
+ async getState(agentId, namespace = "default") {
9130
+ try {
9131
+ return await this.request("GET", `/agents/${agentId}/state?namespace=${namespace}`);
9132
+ } catch (err) {
9133
+ if (err.status === 404) return null;
9134
+ throw err;
9135
+ }
9136
+ }
9137
+ async setState(agentId, state, namespace = "default", ttlSeconds = 86400) {
9138
+ return this.request("PUT", `/agents/${agentId}/state`, {
9139
+ namespace,
9140
+ state,
9141
+ ttlSeconds
9142
+ });
9143
+ }
9144
+ // ── Agent Memory ──
9145
+ async getMemory(agentId, category, limit = 50) {
9146
+ const params = new URLSearchParams({ limit: String(limit) });
9147
+ if (category) params.set("category", category);
9148
+ return this.request("GET", `/agents/${agentId}/memory?${params}`);
9149
+ }
9150
+ async setMemory(agentId, category, key, content, metadata) {
9151
+ return this.request("PUT", `/agents/${agentId}/memory`, {
9152
+ category,
9153
+ key,
9154
+ content,
9155
+ metadata
9156
+ });
9157
+ }
9158
+ async deleteMemory(agentId, memoryId) {
9159
+ await this.request("DELETE", `/agents/${agentId}/memory/${memoryId}`);
9160
+ }
9161
+ // ── Agent Focus ──
9162
+ async setFocus(agentId, taskId) {
9163
+ await this.request("PUT", `/agents/${agentId}/focus`, { taskId });
9164
+ }
9165
+ async clearFocus(agentId) {
9166
+ await this.request("DELETE", `/agents/${agentId}/focus`);
9167
+ }
9168
+ // ── Tasks ──
9169
+ async createTask(task) {
9170
+ return this.request("POST", "/tasks", task);
9171
+ }
9172
+ async getTask(taskId) {
9173
+ return this.request("GET", `/tasks/${taskId}`);
9174
+ }
9175
+ async updateTask(taskId, updates) {
9176
+ return this.request("PATCH", `/tasks/${taskId}`, updates);
9177
+ }
9178
+ async getMyTasks(status) {
9179
+ const params = status ? `?status=${status}` : "";
9180
+ return this.request("GET", `/tasks/mine${params}`);
9181
+ }
9182
+ async getNextTask() {
9183
+ try {
9184
+ return await this.request("GET", "/tasks/next");
9185
+ } catch (err) {
9186
+ if (err.status === 404) return null;
9187
+ throw err;
9188
+ }
9189
+ }
9190
+ async logActivity(taskId, type, message, metadata) {
9191
+ return this.request("POST", `/tasks/${taskId}/activity`, {
9192
+ type,
9193
+ message,
9194
+ metadata
9195
+ });
9196
+ }
9197
+ async getTaskContext(taskId) {
9198
+ try {
9199
+ return await this.request("GET", `/tasks/${taskId}/context`);
9200
+ } catch (err) {
9201
+ if (err.status === 404) return null;
9202
+ throw err;
9203
+ }
9204
+ }
9205
+ async updateTaskContext(taskId, context) {
9206
+ return this.request("PATCH", `/tasks/${taskId}/context`, context);
9207
+ }
9208
+ async appendTaskContext(taskId, field, values) {
9209
+ const current = await this.getTaskContext(taskId);
9210
+ const existing = current?.[field] ?? [];
9211
+ const merged = [...existing, ...values.filter((v) => !existing.includes(v))];
9212
+ return this.request("PATCH", `/tasks/${taskId}/context`, {
9213
+ [field]: merged
9214
+ });
9215
+ }
9216
+ async searchTasks(query, scope = "all") {
9217
+ return this.request("GET", `/tasks/search?q=${encodeURIComponent(query)}&scope=${scope}`);
9218
+ }
9219
+ async handoffTask(taskId, toHandle, summary, instructions, reason, state) {
9220
+ return this.request("POST", `/tasks/${taskId}/handoff`, {
9221
+ toHandle,
9222
+ reason: reason || "Handoff",
9223
+ summary,
9224
+ instructions,
9225
+ state
9226
+ });
9227
+ }
9228
+ // ── Projects ──
9229
+ async createProject(project) {
9230
+ return this.request("POST", "/projects", project);
9231
+ }
9232
+ async getProject(projectId) {
9233
+ return this.request("GET", `/projects/${projectId}`);
9234
+ }
9235
+ async getProjectContext(projectId) {
9236
+ try {
9237
+ return await this.request("GET", `/projects/${projectId}/context`);
9238
+ } catch (err) {
9239
+ if (err.status === 404) return null;
9240
+ throw err;
9241
+ }
9242
+ }
9243
+ async updateProjectContext(projectId, context) {
9244
+ return this.request("PUT", `/projects/${projectId}/context`, context);
9245
+ }
9246
+ // ── Schedule ──
9247
+ async syncSchedule(events) {
9248
+ return this.request("PUT", "/schedule/sync", { events });
9249
+ }
9250
+ async recordOccurrence(eventId, scheduledAt, status, result) {
9251
+ return this.request("POST", `/schedule/${eventId}/occurrences`, {
9252
+ scheduledAt,
9253
+ status,
9254
+ result
9255
+ });
9256
+ }
9257
+ // ── Utility ──
9258
+ getRateLimitRemaining() {
9259
+ return this.rateLimitRemaining;
9260
+ }
9261
+ };
9262
+
9263
+ // packages/runtime/src/sesame-sync.ts
9264
+ function defaultSyncState() {
9265
+ return {
9266
+ lastStatePush: null,
9267
+ lastL3Sync: null,
9268
+ syncedL3Keys: [],
9269
+ lastMemoryMdHash: null,
9270
+ lastWake: null
9271
+ };
9272
+ }
9273
+ function simpleHash(str) {
9274
+ let hash = 0;
9275
+ for (let i = 0; i < str.length; i++) {
9276
+ const char = str.charCodeAt(i);
9277
+ hash = (hash << 5) - hash + char;
9278
+ hash = hash & hash;
9279
+ }
9280
+ return hash.toString(36);
9281
+ }
9282
+ var SesameSync = class {
9283
+ api;
9284
+ agentId;
9285
+ workspaceDir;
9286
+ dataDir;
9287
+ syncState;
9288
+ syncStatePath;
9289
+ syncInterval = null;
9290
+ constructor(config, agentId, workspaceDir, dataDir) {
9291
+ this.api = new SesameAPI(config);
9292
+ this.agentId = agentId;
9293
+ this.workspaceDir = workspaceDir;
9294
+ this.dataDir = dataDir;
9295
+ this.syncStatePath = resolve20(dataDir, "sesame-sync-state.json");
9296
+ this.syncState = this.loadSyncState();
9297
+ }
9298
+ // ── State Persistence ──
9299
+ loadSyncState() {
9300
+ try {
9301
+ if (existsSync21(this.syncStatePath)) {
9302
+ return JSON.parse(readFileSync16(this.syncStatePath, "utf-8"));
9303
+ }
9304
+ } catch (err) {
9305
+ console.warn("[sesame-sync] Failed to load sync state:", err.message);
9306
+ }
9307
+ return defaultSyncState();
9308
+ }
9309
+ saveSyncState() {
9310
+ try {
9311
+ const dir = dirname8(this.syncStatePath);
9312
+ if (!existsSync21(dir)) mkdirSync16(dir, { recursive: true });
9313
+ writeFileSync9(this.syncStatePath, JSON.stringify(this.syncState, null, 2));
9314
+ } catch (err) {
9315
+ console.warn("[sesame-sync] Failed to save sync state:", err.message);
9316
+ }
9317
+ }
9318
+ // ── Push: MEMORY.md -> Agent State ──
9319
+ /**
9320
+ * Read MEMORY.md from workspace and push to Sesame agent state.
9321
+ * Only pushes if content has changed since last push.
9322
+ */
9323
+ async pushState() {
9324
+ const memoryMdPath = resolve20(this.workspaceDir, "MEMORY.md");
9325
+ if (!existsSync21(memoryMdPath)) {
9326
+ console.log("[sesame-sync] No MEMORY.md found, skipping state push");
9327
+ return false;
9328
+ }
9329
+ const content = readFileSync16(memoryMdPath, "utf-8");
9330
+ const hash = simpleHash(content);
9331
+ if (hash === this.syncState.lastMemoryMdHash) {
9332
+ return false;
9333
+ }
9334
+ try {
9335
+ await this.api.setState(this.agentId, {
9336
+ memoryMd: content,
9337
+ memoryMdHash: hash,
9338
+ lastSync: (/* @__PURE__ */ new Date()).toISOString(),
9339
+ source: "hivemind-sync"
9340
+ }, "memory", 172800);
9341
+ this.syncState.lastMemoryMdHash = hash;
9342
+ this.syncState.lastStatePush = (/* @__PURE__ */ new Date()).toISOString();
9343
+ this.saveSyncState();
9344
+ console.log("[sesame-sync] Pushed MEMORY.md to agent state");
9345
+ return true;
9346
+ } catch (err) {
9347
+ console.error("[sesame-sync] Failed to push state:", err.message);
9348
+ return false;
9349
+ }
9350
+ }
9351
+ // ── Push: Working Context -> Agent State ──
9352
+ /**
9353
+ * Push current working context (focus, active work, etc.) to agent state.
9354
+ * This is the "default" namespace for quick session recovery.
9355
+ */
9356
+ async pushWorkingContext(context) {
9357
+ try {
9358
+ await this.api.setState(this.agentId, {
9359
+ ...context,
9360
+ lastSync: (/* @__PURE__ */ new Date()).toISOString(),
9361
+ source: "hivemind-sync"
9362
+ }, "default", 86400);
9363
+ console.log("[sesame-sync] Pushed working context to agent state");
9364
+ return true;
9365
+ } catch (err) {
9366
+ console.error("[sesame-sync] Failed to push working context:", err.message);
9367
+ return false;
9368
+ }
9369
+ }
9370
+ // ── Push: L3 Knowledge -> Agent Memory ──
9371
+ /**
9372
+ * Sync L3 promoted knowledge to Sesame agent memory.
9373
+ * Only pushes entries that haven't been synced yet.
9374
+ *
9375
+ * @param memoryDaemonUrl - URL of the local memory daemon
9376
+ */
9377
+ async syncL3ToMemory(memoryDaemonUrl) {
9378
+ let l3Entries;
9379
+ try {
9380
+ const response = await fetch(`${memoryDaemonUrl}/promotion/l3?context=global`);
9381
+ if (!response.ok) {
9382
+ console.warn("[sesame-sync] Failed to fetch L3 knowledge:", response.status);
9383
+ return 0;
9384
+ }
9385
+ const data = await response.json();
9386
+ l3Entries = data.entries || data || [];
9387
+ } catch (err) {
9388
+ console.error("[sesame-sync] Failed to fetch L3 knowledge:", err.message);
9389
+ return 0;
9390
+ }
9391
+ if (!Array.isArray(l3Entries) || l3Entries.length === 0) return 0;
9392
+ let synced = 0;
9393
+ const syncedKeys = new Set(this.syncState.syncedL3Keys);
9394
+ for (const entry of l3Entries) {
9395
+ const key = entry.key || `l3-${simpleHash(entry.content)}`;
9396
+ if (syncedKeys.has(key)) continue;
9397
+ try {
9398
+ await this.api.setMemory(
9399
+ this.agentId,
9400
+ "lessons",
9401
+ key,
9402
+ entry.content,
9403
+ {
9404
+ source: "hivemind-l3-promotion",
9405
+ l3Score: entry.score,
9406
+ l3Context: entry.context || "global",
9407
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString()
9408
+ }
9409
+ );
9410
+ syncedKeys.add(key);
9411
+ synced++;
9412
+ } catch (err) {
9413
+ console.warn(`[sesame-sync] Failed to sync L3 entry "${key}":`, err.message);
9414
+ }
9415
+ }
9416
+ if (synced > 0) {
9417
+ this.syncState.syncedL3Keys = Array.from(syncedKeys);
9418
+ this.syncState.lastL3Sync = (/* @__PURE__ */ new Date()).toISOString();
9419
+ this.saveSyncState();
9420
+ console.log(`[sesame-sync] Synced ${synced} L3 entries to Sesame agent memory`);
9421
+ }
9422
+ return synced;
9423
+ }
9424
+ // ── Pull: Wake -> Rebuild Context ──
9425
+ /**
9426
+ * Call the wake endpoint and return structured data for context rebuilding.
9427
+ * This is the primary recovery mechanism after restart/compaction.
9428
+ */
9429
+ async pullOnWake() {
9430
+ try {
9431
+ const wake = await this.api.wake(this.agentId);
9432
+ this.syncState.lastWake = (/* @__PURE__ */ new Date()).toISOString();
9433
+ this.saveSyncState();
9434
+ console.log("[sesame-sync] Wake data retrieved successfully");
9435
+ return wake;
9436
+ } catch (err) {
9437
+ console.error("[sesame-sync] Wake failed:", err.message);
9438
+ return null;
9439
+ }
9440
+ }
9441
+ // ── Pull: Agent State -> MEMORY.md Recovery ──
9442
+ /**
9443
+ * Pull agent state and restore MEMORY.md if local copy is missing or empty.
9444
+ */
9445
+ async pullStateToMemoryMd() {
9446
+ const memoryMdPath = resolve20(this.workspaceDir, "MEMORY.md");
9447
+ if (existsSync21(memoryMdPath)) {
9448
+ const content = readFileSync16(memoryMdPath, "utf-8").trim();
9449
+ if (content.length > 0) {
9450
+ return false;
9451
+ }
9452
+ }
9453
+ try {
9454
+ const state = await this.api.getState(this.agentId, "memory");
9455
+ if (!state || !state.state.memoryMd) {
9456
+ console.log("[sesame-sync] No MEMORY.md found in agent state");
9457
+ return false;
9458
+ }
9459
+ writeFileSync9(memoryMdPath, state.state.memoryMd);
9460
+ console.log("[sesame-sync] Restored MEMORY.md from agent state");
9461
+ return true;
9462
+ } catch (err) {
9463
+ console.error("[sesame-sync] Failed to pull state:", err.message);
9464
+ return false;
9465
+ }
9466
+ }
9467
+ // ── Pre-Compaction Flush ──
9468
+ /**
9469
+ * Safety flush before compaction risk.
9470
+ * Pushes MEMORY.md to agent state and updates focused task context.
9471
+ */
9472
+ async flushBeforeCompaction(focusedTaskId, contextSummary) {
9473
+ console.log("[sesame-sync] Pre-compaction flush starting...");
9474
+ await this.pushState();
9475
+ if (contextSummary) {
9476
+ await this.pushWorkingContext({
9477
+ preCompactionFlush: true,
9478
+ contextSummary,
9479
+ focusedTaskId,
9480
+ flushedAt: (/* @__PURE__ */ new Date()).toISOString()
9481
+ });
9482
+ }
9483
+ if (focusedTaskId && contextSummary) {
9484
+ try {
9485
+ await this.api.updateTaskContext(focusedTaskId, {
9486
+ notes: `[Pre-compaction flush ${(/* @__PURE__ */ new Date()).toISOString()}] ${contextSummary}`
9487
+ });
9488
+ console.log("[sesame-sync] Updated focused task context");
9489
+ } catch (err) {
9490
+ console.warn("[sesame-sync] Failed to update task context:", err.message);
9491
+ }
9492
+ }
9493
+ console.log("[sesame-sync] Pre-compaction flush complete");
9494
+ }
9495
+ // ── Task Helpers ──
9496
+ /**
9497
+ * Log progress on a Sesame task.
9498
+ */
9499
+ async logTaskProgress(taskId, message) {
9500
+ try {
9501
+ await this.api.logActivity(taskId, "progress", message);
9502
+ } catch (err) {
9503
+ console.warn("[sesame-sync] Failed to log task progress:", err.message);
9504
+ }
9505
+ }
9506
+ /**
9507
+ * Log a decision on a Sesame task.
9508
+ */
9509
+ async logTaskDecision(taskId, message) {
9510
+ try {
9511
+ await this.api.logActivity(taskId, "decision", message);
9512
+ } catch (err) {
9513
+ console.warn("[sesame-sync] Failed to log task decision:", err.message);
9514
+ }
9515
+ }
9516
+ /**
9517
+ * Update a task's context block.
9518
+ */
9519
+ async updateTaskContext(taskId, context) {
9520
+ try {
9521
+ await this.api.updateTaskContext(taskId, context);
9522
+ } catch (err) {
9523
+ console.warn("[sesame-sync] Failed to update task context:", err.message);
9524
+ }
9525
+ }
9526
+ // ── Periodic Sync ──
9527
+ /**
9528
+ * Start periodic background sync.
9529
+ * Pushes state every intervalMs (default: 5 minutes).
9530
+ * Syncs L3 every l3IntervalMs (default: 30 minutes).
9531
+ */
9532
+ startPeriodicSync(memoryDaemonUrl, intervalMs = 5 * 60 * 1e3, l3IntervalMs = 30 * 60 * 1e3) {
9533
+ if (this.syncInterval) return;
9534
+ let l3Counter = 0;
9535
+ const l3Every = Math.max(1, Math.floor(l3IntervalMs / intervalMs));
9536
+ this.syncInterval = setInterval(async () => {
9537
+ try {
9538
+ await this.pushState();
9539
+ l3Counter++;
9540
+ if (l3Counter >= l3Every) {
9541
+ l3Counter = 0;
9542
+ await this.syncL3ToMemory(memoryDaemonUrl);
9543
+ }
9544
+ } catch (err) {
9545
+ console.error("[sesame-sync] Periodic sync error:", err.message);
9546
+ }
9547
+ }, intervalMs);
9548
+ console.log(`[sesame-sync] Periodic sync started (state: ${intervalMs / 1e3}s, L3: ${l3IntervalMs / 1e3}s)`);
9549
+ }
9550
+ /**
9551
+ * Stop periodic sync.
9552
+ */
9553
+ stopPeriodicSync() {
9554
+ if (this.syncInterval) {
9555
+ clearInterval(this.syncInterval);
9556
+ this.syncInterval = null;
9557
+ console.log("[sesame-sync] Periodic sync stopped");
9558
+ }
9559
+ }
9560
+ // ── Accessors ──
9561
+ getApi() {
9562
+ return this.api;
9563
+ }
9564
+ getSyncState() {
9565
+ return { ...this.syncState };
9566
+ }
9567
+ getAgentId() {
9568
+ return this.agentId;
9569
+ }
9570
+ };
9571
+
8961
9572
  // packages/runtime/src/pipeline.ts
8962
9573
  var PACKAGE_VERSION = "unknown";
8963
9574
  try {
8964
- const __dirname2 = dirname8(fileURLToPath3(import.meta.url));
8965
- const pkg = JSON.parse(readFileSync16(resolve20(__dirname2, "../package.json"), "utf-8"));
9575
+ const __dirname2 = dirname9(fileURLToPath3(import.meta.url));
9576
+ const pkg = JSON.parse(readFileSync17(resolve21(__dirname2, "../package.json"), "utf-8"));
8966
9577
  PACKAGE_VERSION = pkg.version ?? "unknown";
8967
9578
  } catch {
8968
9579
  }
@@ -9004,12 +9615,12 @@ function startHealthServer(port) {
9004
9615
  return server;
9005
9616
  }
9006
9617
  function writePidFile(path) {
9007
- writeFileSync9(path, String(process.pid));
9618
+ writeFileSync10(path, String(process.pid));
9008
9619
  console.log(`[hivemind] PID file written: ${path}`);
9009
9620
  }
9010
9621
  function cleanupPidFile(path) {
9011
9622
  try {
9012
- unlinkSync3(path);
9623
+ unlinkSync4(path);
9013
9624
  } catch {
9014
9625
  }
9015
9626
  }
@@ -9045,12 +9656,12 @@ async function startPipeline(configPath) {
9045
9656
  console.log("[hivemind] Global context already exists in memory daemon");
9046
9657
  }
9047
9658
  }
9048
- const requestLogger = new RequestLogger(resolve20(dirname8(configPath), "data", "dashboard.db"));
9659
+ const requestLogger = new RequestLogger(resolve21(dirname9(configPath), "data", "dashboard.db"));
9049
9660
  const agent = new Agent(config);
9050
9661
  startDashboardServer(requestLogger, config.memory, () => agent.getConversationHistories());
9051
9662
  agent.setRequestLogger(requestLogger);
9052
- const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
9053
- const workspaceDir = resolve20(hivemindHome, config.agent.workspace || "workspace");
9663
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve21(process.env.HOME || "/root", "hivemind");
9664
+ const workspaceDir = resolve21(hivemindHome, config.agent.workspace || "workspace");
9054
9665
  let contextBridge;
9055
9666
  if (memoryConnected) {
9056
9667
  try {
@@ -9084,7 +9695,7 @@ async function startPipeline(configPath) {
9084
9695
  agent.setToolRegistry(toolRegistry);
9085
9696
  console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
9086
9697
  if (config.auto_debug?.enabled) {
9087
- const dataDir2 = resolve20(hivemindHome, "data");
9698
+ const dataDir2 = resolve21(hivemindHome, "data");
9088
9699
  const autoDebugConfig = config.auto_debug;
9089
9700
  const logWatcher = new LogWatcher(
9090
9701
  {
@@ -9126,7 +9737,7 @@ async function startPipeline(configPath) {
9126
9737
  process.on("exit", () => processManager.stop());
9127
9738
  console.log("[hivemind] Auto-debug processors started");
9128
9739
  }
9129
- const dataDir = resolve20(hivemindHome, "data");
9740
+ const dataDir = resolve21(hivemindHome, "data");
9130
9741
  if (config.sesame.api_key) {
9131
9742
  await startSesameLoop(config, agent, toolRegistry, dataDir);
9132
9743
  } else {
@@ -9137,6 +9748,7 @@ async function startPipeline(configPath) {
9137
9748
  async function startSesameLoop(config, agent, toolRegistry, dataDir) {
9138
9749
  const sesame = new SesameClient2(config.sesame);
9139
9750
  registerMessagingTools(toolRegistry, sesame);
9751
+ let sesameSync = null;
9140
9752
  let eventsWatcher = null;
9141
9753
  if (dataDir) {
9142
9754
  eventsWatcher = new EventsWatcher(dataDir, async (channelId, text, filename, eventType) => {
@@ -9159,11 +9771,20 @@ async function startSesameLoop(config, agent, toolRegistry, dataDir) {
9159
9771
  eventsWatcher.start();
9160
9772
  }
9161
9773
  let shuttingDown = false;
9162
- const shutdown = (signal) => {
9774
+ const shutdown = async (signal) => {
9163
9775
  if (shuttingDown) return;
9164
9776
  shuttingDown = true;
9165
9777
  console.log(`
9166
9778
  [hivemind] Received ${signal}, shutting down...`);
9779
+ if (sesameSync) {
9780
+ try {
9781
+ await sesameSync.flushBeforeCompaction(void 0, `Shutdown via ${signal}`);
9782
+ sesameSync.stopPeriodicSync();
9783
+ console.log("[hivemind] Sesame state flushed before shutdown");
9784
+ } catch (err) {
9785
+ console.warn("[hivemind] Sesame flush failed:", err.message);
9786
+ }
9787
+ }
9167
9788
  try {
9168
9789
  sesame.updatePresence("offline", { emoji: "\u2B58" });
9169
9790
  sesame.disconnect();
@@ -9253,6 +9874,21 @@ async function startSesameLoop(config, agent, toolRegistry, dataDir) {
9253
9874
  await sesame.connect();
9254
9875
  sesameConnected = true;
9255
9876
  console.log("[hivemind] Listening for Sesame messages");
9877
+ const agentId = sesame.getAgentId();
9878
+ if (agentId && dataDir) {
9879
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve21(process.env.HOME || "/root", "hivemind");
9880
+ const workspaceDir = resolve21(hivemindHome, config.agent.workspace || "workspace");
9881
+ sesameSync = new SesameSync(config.sesame, agentId, workspaceDir, dataDir);
9882
+ try {
9883
+ await sesameSync.pushState();
9884
+ console.log("[hivemind] Initial Sesame state sync complete");
9885
+ } catch (err) {
9886
+ console.warn("[hivemind] Initial Sesame state sync failed:", err.message);
9887
+ }
9888
+ sesameSync.startPeriodicSync(config.memory.daemon_url, 5 * 60 * 1e3, 30 * 60 * 1e3);
9889
+ process.on("exit", () => sesameSync?.stopPeriodicSync());
9890
+ console.log("[hivemind] Sesame sync module initialized");
9891
+ }
9256
9892
  await new Promise(() => {
9257
9893
  });
9258
9894
  }
@@ -9300,8 +9936,8 @@ ${response.content}
9300
9936
  console.error("Error:", err.message);
9301
9937
  }
9302
9938
  });
9303
- return new Promise((resolve21) => {
9304
- rl.on("close", resolve21);
9939
+ return new Promise((resolve22) => {
9940
+ rl.on("close", resolve22);
9305
9941
  });
9306
9942
  }
9307
9943
  async function runSpawnTask(config, configPath) {
@@ -9312,7 +9948,7 @@ async function runSpawnTask(config, configPath) {
9312
9948
  const spawnDir = process.env.SPAWN_DIR;
9313
9949
  console.log(`[spawn] Sub-agent starting (id: ${spawnId}, context: ${context})`);
9314
9950
  const agent = new Agent(config, context);
9315
- const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
9951
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve21(process.env.HOME || "/root", "hivemind");
9316
9952
  const toolRegistry = registerAllTools(hivemindHome, {
9317
9953
  enabled: true,
9318
9954
  workspace: config.agent.workspace || "workspace",
@@ -9325,7 +9961,7 @@ async function runSpawnTask(config, configPath) {
9325
9961
  const result = response.content;
9326
9962
  console.log(`[spawn] Task completed (context: ${response.context})`);
9327
9963
  if (spawnDir) {
9328
- writeFileSync9(resolve20(spawnDir, "result.txt"), result);
9964
+ writeFileSync10(resolve21(spawnDir, "result.txt"), result);
9329
9965
  }
9330
9966
  if (channelId && config.sesame.api_key) {
9331
9967
  try {
@@ -9342,7 +9978,7 @@ async function runSpawnTask(config, configPath) {
9342
9978
  const errorMsg = `[SPAWN ERROR] ${err.message}`;
9343
9979
  console.error(`[spawn] ${errorMsg}`);
9344
9980
  if (spawnDir) {
9345
- writeFileSync9(resolve20(spawnDir, "result.txt"), errorMsg);
9981
+ writeFileSync10(resolve21(spawnDir, "result.txt"), errorMsg);
9346
9982
  }
9347
9983
  process.exitCode = 1;
9348
9984
  }
@@ -9373,20 +10009,20 @@ var WorkerServer = class {
9373
10009
  }
9374
10010
  /** Start listening. */
9375
10011
  async start() {
9376
- return new Promise((resolve21, reject) => {
10012
+ return new Promise((resolve22, reject) => {
9377
10013
  this.server = createServer4((req, res) => this.handleRequest(req, res));
9378
10014
  this.server.on("error", reject);
9379
- this.server.listen(this.port, () => resolve21());
10015
+ this.server.listen(this.port, () => resolve22());
9380
10016
  });
9381
10017
  }
9382
10018
  /** Stop the server. */
9383
10019
  async stop() {
9384
- return new Promise((resolve21) => {
10020
+ return new Promise((resolve22) => {
9385
10021
  if (!this.server) {
9386
- resolve21();
10022
+ resolve22();
9387
10023
  return;
9388
10024
  }
9389
- this.server.close(() => resolve21());
10025
+ this.server.close(() => resolve22());
9390
10026
  });
9391
10027
  }
9392
10028
  getPort() {
@@ -9509,10 +10145,10 @@ var WorkerServer = class {
9509
10145
  }
9510
10146
  };
9511
10147
  function readBody2(req) {
9512
- return new Promise((resolve21, reject) => {
10148
+ return new Promise((resolve22, reject) => {
9513
10149
  const chunks = [];
9514
10150
  req.on("data", (chunk) => chunks.push(chunk));
9515
- req.on("end", () => resolve21(Buffer.concat(chunks).toString("utf-8")));
10151
+ req.on("end", () => resolve22(Buffer.concat(chunks).toString("utf-8")));
9516
10152
  req.on("error", reject);
9517
10153
  });
9518
10154
  }
@@ -9854,4 +10490,4 @@ smol-toml/dist/index.js:
9854
10490
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
9855
10491
  *)
9856
10492
  */
9857
- //# sourceMappingURL=chunk-PGLO6WA5.js.map
10493
+ //# sourceMappingURL=chunk-TETVPYWD.js.map