@sesamespace/hivemind 0.8.12 → 0.10.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.
Files changed (59) hide show
  1. package/README.md +2 -1
  2. package/config/default.toml +18 -0
  3. package/dist/{chunk-NCJW2AOK.js → chunk-BHCDOHSK.js} +3 -3
  4. package/dist/chunk-DGUM43GV.js +11 -0
  5. package/dist/chunk-DGUM43GV.js.map +1 -0
  6. package/dist/{chunk-OVRYYS5I.js → chunk-DPLCEMEC.js} +2 -2
  7. package/dist/{chunk-XQZU3ULO.js → chunk-FBQBBAPZ.js} +2 -2
  8. package/dist/{chunk-XBJGLZ7V.js → chunk-FK6WYXRM.js} +79 -2
  9. package/dist/chunk-FK6WYXRM.js.map +1 -0
  10. package/dist/{chunk-LJHJGDKY.js → chunk-ICSJNKI6.js} +62 -2
  11. package/dist/chunk-ICSJNKI6.js.map +1 -0
  12. package/dist/{chunk-HDYEAKN5.js → chunk-IXBIAX76.js} +4 -34
  13. package/dist/chunk-IXBIAX76.js.map +1 -0
  14. package/dist/{chunk-OG6GPQDK.js → chunk-M3A2WRXM.js} +1765 -189
  15. package/dist/chunk-M3A2WRXM.js.map +1 -0
  16. package/dist/commands/fleet.js +4 -3
  17. package/dist/commands/init.js +4 -3
  18. package/dist/commands/service.js +1 -0
  19. package/dist/commands/start.js +4 -3
  20. package/dist/commands/upgrade.js +2 -1
  21. package/dist/commands/watchdog.js +4 -3
  22. package/dist/dashboard.html +873 -131
  23. package/dist/index.js +14 -5
  24. package/dist/main.js +376 -7
  25. package/dist/main.js.map +1 -1
  26. package/dist/start.js +2 -1
  27. package/dist/start.js.map +1 -1
  28. package/install.sh +162 -0
  29. package/package.json +24 -23
  30. package/packages/memory/Cargo.lock +6480 -0
  31. package/packages/memory/Cargo.toml +21 -0
  32. package/packages/memory/src/src/context.rs +179 -0
  33. package/packages/memory/src/src/embeddings.rs +51 -0
  34. package/packages/memory/src/src/main.rs +887 -0
  35. package/packages/memory/src/src/promotion.rs +808 -0
  36. package/packages/memory/src/src/scoring.rs +142 -0
  37. package/packages/memory/src/src/store.rs +460 -0
  38. package/packages/memory/src/src/tasks.rs +321 -0
  39. package/.pnpmrc.json +0 -1
  40. package/DASHBOARD-PLAN.md +0 -206
  41. package/MEMORY-ENHANCEMENT-PLAN.md +0 -211
  42. package/TOOL-USE-DESIGN.md +0 -173
  43. package/dist/chunk-HDYEAKN5.js.map +0 -1
  44. package/dist/chunk-LJHJGDKY.js.map +0 -1
  45. package/dist/chunk-OG6GPQDK.js.map +0 -1
  46. package/dist/chunk-XBJGLZ7V.js.map +0 -1
  47. package/docs/TOOL-PARITY-PLAN.md +0 -191
  48. package/src/memory/dashboard-integration.ts +0 -295
  49. package/src/memory/index.ts +0 -187
  50. package/src/memory/performance-test.ts +0 -208
  51. package/src/memory/processors/agent-sync.ts +0 -312
  52. package/src/memory/processors/command-learner.ts +0 -298
  53. package/src/memory/processors/memory-api-client.ts +0 -105
  54. package/src/memory/processors/message-flow-integration.ts +0 -168
  55. package/src/memory/processors/research-digester.ts +0 -204
  56. package/test-caitlin-access.md +0 -11
  57. /package/dist/{chunk-NCJW2AOK.js.map → chunk-BHCDOHSK.js.map} +0 -0
  58. /package/dist/{chunk-OVRYYS5I.js.map → chunk-DPLCEMEC.js.map} +0 -0
  59. /package/dist/{chunk-XQZU3ULO.js.map → chunk-FBQBBAPZ.js.map} +0 -0
@@ -1,3 +1,36 @@
1
+ import {
2
+ __require
3
+ } from "./chunk-DGUM43GV.js";
4
+
5
+ // packages/runtime/src/logger.ts
6
+ var LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
7
+ var currentLevel = process.env.HIVEMIND_LOG_LEVEL || "info";
8
+ function setLogLevel(level) {
9
+ currentLevel = level;
10
+ }
11
+ function createLogger(component) {
12
+ const log4 = (level, msg, data) => {
13
+ if (LEVELS[level] < LEVELS[currentLevel]) return;
14
+ const entry = {
15
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
16
+ level,
17
+ component,
18
+ msg,
19
+ ...data
20
+ };
21
+ const line = JSON.stringify(entry);
22
+ if (level === "error") console.error(line);
23
+ else if (level === "warn") console.warn(line);
24
+ else console.log(line);
25
+ };
26
+ return {
27
+ debug: (msg, data) => log4("debug", msg, data),
28
+ info: (msg, data) => log4("info", msg, data),
29
+ warn: (msg, data) => log4("warn", msg, data),
30
+ error: (msg, data) => log4("error", msg, data)
31
+ };
32
+ }
33
+
1
34
  // packages/runtime/src/llm-client.ts
2
35
  import { execSync } from "child_process";
3
36
  function convertMessagesForAnthropic(messages) {
@@ -1310,14 +1343,15 @@ var Agent = class {
1310
1343
  });
1311
1344
  if (relevantEpisodes.length > 0) {
1312
1345
  const episodeIds = relevantEpisodes.map((e) => e.id);
1313
- this.memory.recordCoAccess(episodeIds).catch(() => {
1314
- });
1346
+ this.memory.recordCoAccess(episodeIds).catch((err) => console.warn("[memory] recordCoAccess failed:", err.message));
1315
1347
  for (const ep of relevantEpisodes) {
1316
- this.memory.recordAccess(ep.id).catch(() => {
1317
- });
1348
+ this.memory.recordAccess(ep.id).catch((err) => console.warn("[memory] recordAccess failed:", err.message));
1318
1349
  }
1319
1350
  }
1320
- const l3Knowledge = await this.memory.getL3Knowledge(contextName).catch(() => []);
1351
+ const l3Knowledge = await this.memory.getL3Knowledge(contextName).catch((err) => {
1352
+ console.warn("[memory] getL3Knowledge failed:", err.message);
1353
+ return [];
1354
+ });
1321
1355
  const systemPromptResult = buildSystemPrompt({
1322
1356
  config: this.config.agent,
1323
1357
  episodes: relevantEpisodes,
@@ -1554,6 +1588,9 @@ var Agent = class {
1554
1588
  getActiveContext() {
1555
1589
  return this.contextManager.getActiveContext();
1556
1590
  }
1591
+ getConversationHistories() {
1592
+ return this.conversationHistories;
1593
+ }
1557
1594
  };
1558
1595
 
1559
1596
  // packages/runtime/src/events.ts
@@ -3012,7 +3049,7 @@ var SesameClient = class {
3012
3049
  * Connect to the real-time WebSocket gateway.
3013
3050
  */
3014
3051
  connect() {
3015
- return new Promise((resolve20, reject) => {
3052
+ return new Promise((resolve21, reject) => {
3016
3053
  try {
3017
3054
  this.ws = new WebSocket(`${this.config.wsUrl}/v1/connect`);
3018
3055
  this.ws.on("open", () => {
@@ -3026,7 +3063,7 @@ var SesameClient = class {
3026
3063
  this.authenticated = true;
3027
3064
  this.startHeartbeat(event.heartbeatIntervalMs ?? 3e4);
3028
3065
  this.sendReplay();
3029
- resolve20();
3066
+ resolve21();
3030
3067
  return;
3031
3068
  }
3032
3069
  if (event.type === "pong")
@@ -3585,6 +3622,977 @@ var SkillsEngine = class {
3585
3622
  }
3586
3623
  };
3587
3624
 
3625
+ // packages/runtime/src/processors/error-registry.ts
3626
+ import { readFileSync as readFileSync7, writeFileSync, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
3627
+ import { createHash } from "crypto";
3628
+ import { dirname as dirname4 } from "path";
3629
+ var log = createLogger("error-registry");
3630
+ function normalizeForHash(msg, stack) {
3631
+ let text = msg;
3632
+ if (stack) {
3633
+ text += "\n" + stack;
3634
+ }
3635
+ text = text.replace(/:\d+:\d+/g, ":X:X");
3636
+ text = text.replace(/\d{4}-\d{2}-\d{2}T[\d:.Z]+/g, "T");
3637
+ text = text.replace(/\b\d{4,}\b/g, "N");
3638
+ return text;
3639
+ }
3640
+ function hashError(msg, stack) {
3641
+ const normalized = normalizeForHash(msg, stack);
3642
+ return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
3643
+ }
3644
+ function scoreSeverity(entry) {
3645
+ let score = 0;
3646
+ if (entry.level === "crash") score += 5;
3647
+ const firstMs = new Date(entry.firstSeen).getTime();
3648
+ const lastMs = new Date(entry.lastSeen).getTime();
3649
+ const spanMinutes = (lastMs - firstMs) / 6e4;
3650
+ if (entry.totalOccurrences >= 3 && spanMinutes < 5) score += 3;
3651
+ const spanHours = Math.max(spanMinutes / 60, 1 / 60);
3652
+ if (entry.totalOccurrences / spanHours > 10) score += 2;
3653
+ if (/sesame/i.test(entry.message) || /sesame/i.test(entry.stackTrace ?? "")) {
3654
+ score += 2;
3655
+ }
3656
+ if (/memory/i.test(entry.message) || /memory/i.test(entry.stackTrace ?? "")) {
3657
+ score += 1;
3658
+ }
3659
+ if (entry.status === "fix-submitted" || entry.status === "wont-fix") {
3660
+ score -= 10;
3661
+ }
3662
+ return score;
3663
+ }
3664
+ var ErrorRegistry = class {
3665
+ filePath;
3666
+ entries = /* @__PURE__ */ new Map();
3667
+ constructor(dataDir) {
3668
+ this.filePath = `${dataDir}/error-registry.json`;
3669
+ this.load();
3670
+ }
3671
+ // ---- persistence -------------------------------------------------------
3672
+ load() {
3673
+ try {
3674
+ if (!existsSync6(this.filePath)) return;
3675
+ const raw = readFileSync7(this.filePath, "utf-8");
3676
+ const data = JSON.parse(raw);
3677
+ if (data.version === 1 && data.errors) {
3678
+ for (const [id, entry] of Object.entries(data.errors)) {
3679
+ this.entries.set(id, entry);
3680
+ }
3681
+ }
3682
+ log.debug("loaded error registry", { count: this.entries.size });
3683
+ } catch (err) {
3684
+ log.warn("failed to load error registry, starting fresh", {
3685
+ error: err.message
3686
+ });
3687
+ }
3688
+ }
3689
+ save() {
3690
+ try {
3691
+ const dir = dirname4(this.filePath);
3692
+ if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
3693
+ const data = {
3694
+ version: 1,
3695
+ errors: Object.fromEntries(this.entries)
3696
+ };
3697
+ writeFileSync(this.filePath, JSON.stringify(data, null, 2));
3698
+ } catch (err) {
3699
+ log.error("failed to save error registry", {
3700
+ error: err.message
3701
+ });
3702
+ }
3703
+ }
3704
+ // ---- public API --------------------------------------------------------
3705
+ /** Record an error event, creating or updating the registry entry. */
3706
+ recordError(event) {
3707
+ const existing = this.entries.get(event.id);
3708
+ if (existing) {
3709
+ existing.lastSeen = event.timestamp.toISOString();
3710
+ existing.totalOccurrences += event.occurrences;
3711
+ if (event.level === "crash" && existing.level !== "crash") {
3712
+ existing.level = "crash";
3713
+ }
3714
+ existing.severity = scoreSeverity(existing);
3715
+ this.save();
3716
+ log.debug("updated error entry", {
3717
+ id: event.id,
3718
+ total: existing.totalOccurrences,
3719
+ severity: existing.severity
3720
+ });
3721
+ return existing;
3722
+ }
3723
+ const entry = {
3724
+ id: event.id,
3725
+ message: event.message,
3726
+ source: event.source,
3727
+ level: event.level,
3728
+ stackTrace: event.stackTrace,
3729
+ firstSeen: event.timestamp.toISOString(),
3730
+ lastSeen: event.timestamp.toISOString(),
3731
+ totalOccurrences: event.occurrences,
3732
+ status: "new",
3733
+ severity: 0
3734
+ };
3735
+ entry.severity = scoreSeverity(entry);
3736
+ this.entries.set(event.id, entry);
3737
+ this.save();
3738
+ log.info("new error registered", {
3739
+ id: event.id,
3740
+ message: event.message.slice(0, 120),
3741
+ severity: entry.severity
3742
+ });
3743
+ return entry;
3744
+ }
3745
+ /** Return errors above the severity threshold that are actionable (new or investigating). */
3746
+ getActionable(threshold) {
3747
+ const results = [];
3748
+ for (const entry of this.entries.values()) {
3749
+ entry.severity = scoreSeverity(entry);
3750
+ if (entry.severity >= threshold && (entry.status === "new" || entry.status === "investigating")) {
3751
+ results.push(entry);
3752
+ }
3753
+ }
3754
+ results.sort((a, b) => b.severity - a.severity);
3755
+ return results;
3756
+ }
3757
+ /** Update the status (and optional PR url) for an error. */
3758
+ updateStatus(id, status, prUrl) {
3759
+ const entry = this.entries.get(id);
3760
+ if (!entry) return;
3761
+ entry.status = status;
3762
+ if (prUrl) entry.prUrl = prUrl;
3763
+ entry.severity = scoreSeverity(entry);
3764
+ this.save();
3765
+ log.info("error status updated", { id, status, prUrl });
3766
+ }
3767
+ /** Remove entries older than the given date. */
3768
+ cleanup(olderThan) {
3769
+ let removed = 0;
3770
+ for (const [id, entry] of this.entries) {
3771
+ if (new Date(entry.lastSeen) < olderThan) {
3772
+ this.entries.delete(id);
3773
+ removed++;
3774
+ }
3775
+ }
3776
+ if (removed > 0) {
3777
+ this.save();
3778
+ log.info("cleaned up old errors", { removed });
3779
+ }
3780
+ return removed;
3781
+ }
3782
+ /** Get a single entry by id. */
3783
+ get(id) {
3784
+ return this.entries.get(id);
3785
+ }
3786
+ /** Total number of tracked errors. */
3787
+ get size() {
3788
+ return this.entries.size;
3789
+ }
3790
+ };
3791
+
3792
+ // packages/runtime/src/processors/log-watcher.ts
3793
+ import { readFileSync as readFileSync8, statSync as statSync2, existsSync as existsSync7 } from "fs";
3794
+
3795
+ // packages/runtime/src/memory/background-processor.ts
3796
+ import { EventEmitter } from "events";
3797
+ var BackgroundProcess = class extends EventEmitter {
3798
+ name;
3799
+ interval;
3800
+ enabled = true;
3801
+ timer;
3802
+ running = false;
3803
+ lastRun;
3804
+ lastResult;
3805
+ constructor(name, interval) {
3806
+ super();
3807
+ this.name = name;
3808
+ this.interval = interval;
3809
+ }
3810
+ /**
3811
+ * Determine if this process should run now
3812
+ */
3813
+ async shouldRun(context) {
3814
+ if (!this.enabled || this.running) return false;
3815
+ return true;
3816
+ }
3817
+ /**
3818
+ * Start the background process
3819
+ */
3820
+ start(context) {
3821
+ if (this.timer) return;
3822
+ this.runOnce(context);
3823
+ this.timer = setInterval(() => {
3824
+ this.runOnce(context);
3825
+ }, this.interval);
3826
+ }
3827
+ /**
3828
+ * Stop the background process
3829
+ */
3830
+ stop() {
3831
+ if (this.timer) {
3832
+ clearInterval(this.timer);
3833
+ this.timer = void 0;
3834
+ }
3835
+ }
3836
+ /**
3837
+ * Run the process once
3838
+ */
3839
+ async runOnce(context) {
3840
+ if (!await this.shouldRun(context)) return;
3841
+ this.running = true;
3842
+ const startTime2 = Date.now();
3843
+ try {
3844
+ this.emit("start", { name: this.name, time: /* @__PURE__ */ new Date() });
3845
+ const result = await this.process(context);
3846
+ this.lastResult = result;
3847
+ this.lastRun = /* @__PURE__ */ new Date();
3848
+ if (result.nextRunIn && result.nextRunIn !== this.interval) {
3849
+ this.stop();
3850
+ this.interval = result.nextRunIn;
3851
+ this.start(context);
3852
+ }
3853
+ this.emit("complete", {
3854
+ name: this.name,
3855
+ duration: Date.now() - startTime2,
3856
+ result
3857
+ });
3858
+ if (result.errors.length > 0) {
3859
+ this.emit("error", {
3860
+ name: this.name,
3861
+ errors: result.errors
3862
+ });
3863
+ }
3864
+ } catch (error) {
3865
+ this.emit("error", {
3866
+ name: this.name,
3867
+ error: error instanceof Error ? error.message : String(error)
3868
+ });
3869
+ } finally {
3870
+ this.running = false;
3871
+ }
3872
+ }
3873
+ /**
3874
+ * Get process status
3875
+ */
3876
+ getStatus() {
3877
+ return {
3878
+ name: this.name,
3879
+ enabled: this.enabled,
3880
+ running: this.running,
3881
+ interval: this.interval,
3882
+ lastRun: this.lastRun,
3883
+ lastResult: this.lastResult
3884
+ };
3885
+ }
3886
+ };
3887
+ var ProcessManager = class extends EventEmitter {
3888
+ processes = /* @__PURE__ */ new Map();
3889
+ context;
3890
+ started = false;
3891
+ constructor(context) {
3892
+ super();
3893
+ this.context = context;
3894
+ }
3895
+ /**
3896
+ * Register a background process
3897
+ */
3898
+ register(process2) {
3899
+ if (this.processes.has(process2.name)) {
3900
+ throw new Error(`Process ${process2.name} already registered`);
3901
+ }
3902
+ process2.on("start", (data) => this.emit("process:start", data));
3903
+ process2.on("complete", (data) => this.emit("process:complete", data));
3904
+ process2.on("error", (data) => this.emit("process:error", data));
3905
+ this.processes.set(process2.name, process2);
3906
+ if (this.started) {
3907
+ process2.start(this.context);
3908
+ }
3909
+ }
3910
+ /**
3911
+ * Start all registered processes
3912
+ */
3913
+ start() {
3914
+ if (this.started) return;
3915
+ for (const process2 of this.processes.values()) {
3916
+ process2.start(this.context);
3917
+ }
3918
+ this.started = true;
3919
+ this.emit("started");
3920
+ }
3921
+ /**
3922
+ * Stop all processes
3923
+ */
3924
+ stop() {
3925
+ if (!this.started) return;
3926
+ for (const process2 of this.processes.values()) {
3927
+ process2.stop();
3928
+ }
3929
+ this.started = false;
3930
+ this.emit("stopped");
3931
+ }
3932
+ /**
3933
+ * Get status of all processes
3934
+ */
3935
+ getStatus() {
3936
+ const statuses = {};
3937
+ for (const [name, process2] of this.processes) {
3938
+ statuses[name] = process2.getStatus();
3939
+ }
3940
+ return {
3941
+ started: this.started,
3942
+ processCount: this.processes.size,
3943
+ processes: statuses
3944
+ };
3945
+ }
3946
+ /**
3947
+ * Enable/disable a specific process
3948
+ */
3949
+ setProcessEnabled(name, enabled) {
3950
+ const process2 = this.processes.get(name);
3951
+ if (!process2) {
3952
+ throw new Error(`Process ${name} not found`);
3953
+ }
3954
+ process2.enabled = enabled;
3955
+ if (!enabled) {
3956
+ process2.stop();
3957
+ } else if (this.started) {
3958
+ process2.start(this.context);
3959
+ }
3960
+ }
3961
+ /**
3962
+ * Manually trigger a process run
3963
+ */
3964
+ async runProcess(name) {
3965
+ const process2 = this.processes.get(name);
3966
+ if (!process2) {
3967
+ throw new Error(`Process ${name} not found`);
3968
+ }
3969
+ return process2.process(this.context);
3970
+ }
3971
+ };
3972
+
3973
+ // packages/runtime/src/processors/log-watcher.ts
3974
+ var log2 = createLogger("log-watcher");
3975
+ var STACK_TRACE_START = /^(?:Error|TypeError|RangeError|ReferenceError|SyntaxError|URIError|EvalError|AggregateError|UnhandledPromiseRejectionWarning|Uncaught):/;
3976
+ var STACK_FRAME = /^\s+at\s+/;
3977
+ var CRASH_INDICATOR = /(?:exit\s+code\s+[1-9]\d*|SIGKILL|SIGTERM|SIGSEGV|SIGABRT|uncaughtException|unhandledRejection)/i;
3978
+ var WARNING_DEDUP_WINDOW_MS = 5 * 60 * 1e3;
3979
+ var WARNING_THRESHOLD = 3;
3980
+ var LogWatcher = class extends BackgroundProcess {
3981
+ config;
3982
+ registry;
3983
+ tailPositions = /* @__PURE__ */ new Map();
3984
+ warningAccumulators = /* @__PURE__ */ new Map();
3985
+ positionsFile;
3986
+ constructor(config, dataDir) {
3987
+ super("log-watcher", 1e4);
3988
+ this.config = config;
3989
+ this.registry = new ErrorRegistry(dataDir);
3990
+ this.positionsFile = `${dataDir}/log-watcher-positions.json`;
3991
+ this.loadPositions();
3992
+ }
3993
+ /** Expose the registry so the auto-debugger can consume it. */
3994
+ getRegistry() {
3995
+ return this.registry;
3996
+ }
3997
+ // ---- BackgroundProcess implementation ------------------------------------
3998
+ async process(context) {
3999
+ const errors = [];
4000
+ let itemsProcessed = 0;
4001
+ for (const logFile of this.config.log_files) {
4002
+ try {
4003
+ const result = this.processLogFile(logFile, context);
4004
+ itemsProcessed += result.linesRead;
4005
+ } catch (err) {
4006
+ errors.push(`${logFile}: ${err.message}`);
4007
+ }
4008
+ }
4009
+ this.pruneWarnings();
4010
+ this.savePositions();
4011
+ this.registry.cleanup(new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3));
4012
+ return { itemsProcessed, errors };
4013
+ }
4014
+ // ---- log tailing ---------------------------------------------------------
4015
+ processLogFile(logFile, context) {
4016
+ if (!existsSync7(logFile)) return { linesRead: 0 };
4017
+ let fileSize;
4018
+ try {
4019
+ fileSize = statSync2(logFile).size;
4020
+ } catch {
4021
+ return { linesRead: 0 };
4022
+ }
4023
+ const lastPos = this.tailPositions.get(logFile) ?? 0;
4024
+ if (fileSize < lastPos) {
4025
+ this.tailPositions.set(logFile, 0);
4026
+ return this.processLogFile(logFile, context);
4027
+ }
4028
+ if (fileSize === lastPos) return { linesRead: 0 };
4029
+ let chunk;
4030
+ try {
4031
+ const buf = Buffer.alloc(fileSize - lastPos);
4032
+ const fd = __require("fs").openSync(logFile, "r");
4033
+ try {
4034
+ __require("fs").readSync(fd, buf, 0, buf.length, lastPos);
4035
+ } finally {
4036
+ __require("fs").closeSync(fd);
4037
+ }
4038
+ chunk = buf.toString("utf-8");
4039
+ } catch {
4040
+ return { linesRead: 0 };
4041
+ }
4042
+ this.tailPositions.set(logFile, fileSize);
4043
+ const lines = chunk.split("\n").filter((l) => l.length > 0);
4044
+ if (lines.length === 0) return { linesRead: 0 };
4045
+ let baseLineNumber;
4046
+ try {
4047
+ const fullContent = readFileSync8(logFile, "utf-8");
4048
+ const preContent = fullContent.substring(0, lastPos);
4049
+ baseLineNumber = preContent.split("\n").length;
4050
+ } catch {
4051
+ baseLineNumber = 1;
4052
+ }
4053
+ this.detectErrors(lines, logFile, baseLineNumber, context);
4054
+ return { linesRead: lines.length };
4055
+ }
4056
+ // ---- error detection -----------------------------------------------------
4057
+ detectErrors(lines, logFile, baseLineNumber, context) {
4058
+ const source = this.inferSource(logFile);
4059
+ for (let i = 0; i < lines.length; i++) {
4060
+ const line = lines[i];
4061
+ const lineNumber = baseLineNumber + i;
4062
+ const jsonError = this.tryParseJsonError(line);
4063
+ if (jsonError) {
4064
+ this.emitError({
4065
+ message: jsonError.msg,
4066
+ stackTrace: jsonError.stack,
4067
+ level: jsonError.level === "crash" ? "crash" : "error",
4068
+ source,
4069
+ logFile,
4070
+ lineNumber,
4071
+ context: this.getSurroundingLines(lines, i)
4072
+ });
4073
+ continue;
4074
+ }
4075
+ if (STACK_TRACE_START.test(line)) {
4076
+ const { message, stack } = this.collectStackTrace(lines, i);
4077
+ const level = /uncaught|unhandled/i.test(line) ? "crash" : "error";
4078
+ this.emitError({
4079
+ message,
4080
+ stackTrace: stack,
4081
+ level,
4082
+ source,
4083
+ logFile,
4084
+ lineNumber,
4085
+ context: this.getSurroundingLines(lines, i)
4086
+ });
4087
+ continue;
4088
+ }
4089
+ if (CRASH_INDICATOR.test(line)) {
4090
+ this.emitError({
4091
+ message: line.trim(),
4092
+ level: "crash",
4093
+ source,
4094
+ logFile,
4095
+ lineNumber,
4096
+ context: this.getSurroundingLines(lines, i)
4097
+ });
4098
+ continue;
4099
+ }
4100
+ this.accumulateWarning(line, logFile, lineNumber);
4101
+ }
4102
+ }
4103
+ tryParseJsonError(line) {
4104
+ if (!line.startsWith("{")) return null;
4105
+ try {
4106
+ const entry = JSON.parse(line);
4107
+ if (entry.level === "error" || entry.level === "crash") {
4108
+ return {
4109
+ msg: entry.msg || entry.message || "unknown error",
4110
+ stack: entry.stack || entry.stackTrace,
4111
+ level: entry.level
4112
+ };
4113
+ }
4114
+ } catch {
4115
+ }
4116
+ return null;
4117
+ }
4118
+ collectStackTrace(lines, startIdx) {
4119
+ const message = lines[startIdx].trim();
4120
+ const stackLines = [message];
4121
+ for (let j = startIdx + 1; j < lines.length; j++) {
4122
+ if (STACK_FRAME.test(lines[j])) {
4123
+ stackLines.push(lines[j]);
4124
+ } else {
4125
+ break;
4126
+ }
4127
+ }
4128
+ return { message, stack: stackLines.join("\n") };
4129
+ }
4130
+ getSurroundingLines(lines, idx) {
4131
+ const start = Math.max(0, idx - 10);
4132
+ const end = Math.min(lines.length, idx + 11);
4133
+ return lines.slice(start, end);
4134
+ }
4135
+ emitError(params) {
4136
+ const id = hashError(params.message, params.stackTrace);
4137
+ const event = {
4138
+ id,
4139
+ timestamp: /* @__PURE__ */ new Date(),
4140
+ source: params.source,
4141
+ level: params.level,
4142
+ message: params.message,
4143
+ stackTrace: params.stackTrace,
4144
+ logFile: params.logFile,
4145
+ lineNumber: params.lineNumber,
4146
+ occurrences: 1,
4147
+ context: params.context
4148
+ };
4149
+ const entry = this.registry.recordError(event);
4150
+ log2.info("error detected", {
4151
+ id,
4152
+ severity: entry.severity,
4153
+ message: params.message.slice(0, 120),
4154
+ logFile: params.logFile
4155
+ });
4156
+ }
4157
+ // ---- repeated warning detection ------------------------------------------
4158
+ accumulateWarning(line, logFile, lineNumber) {
4159
+ const isWarning = /\bwarn\b/i.test(line) || line.startsWith("{") && line.includes('"warn"');
4160
+ if (!isWarning) return;
4161
+ const normalized = line.replace(/\d{4}-\d{2}-\d{2}T[\d:.Z]+/g, "T").replace(/\b\d{4,}\b/g, "N").trim();
4162
+ const key = `${logFile}::${normalized}`;
4163
+ const existing = this.warningAccumulators.get(key);
4164
+ const now = Date.now();
4165
+ if (existing) {
4166
+ existing.count++;
4167
+ existing.lastLine = lineNumber;
4168
+ if (existing.count >= WARNING_THRESHOLD && now - existing.firstSeen <= WARNING_DEDUP_WINDOW_MS) {
4169
+ const id = hashError(existing.message);
4170
+ const event = {
4171
+ id,
4172
+ timestamp: /* @__PURE__ */ new Date(),
4173
+ source: this.inferSource(logFile),
4174
+ level: "repeated-warning",
4175
+ message: `Repeated warning (${existing.count}x in 5 min): ${existing.message}`,
4176
+ logFile,
4177
+ lineNumber: existing.lastLine,
4178
+ occurrences: existing.count,
4179
+ context: []
4180
+ };
4181
+ this.registry.recordError(event);
4182
+ log2.info("repeated warning detected", {
4183
+ id,
4184
+ count: existing.count,
4185
+ message: existing.message.slice(0, 100)
4186
+ });
4187
+ this.warningAccumulators.delete(key);
4188
+ }
4189
+ } else {
4190
+ this.warningAccumulators.set(key, {
4191
+ message: normalized,
4192
+ count: 1,
4193
+ firstSeen: now,
4194
+ logFile,
4195
+ lastLine: lineNumber
4196
+ });
4197
+ }
4198
+ }
4199
+ pruneWarnings() {
4200
+ const now = Date.now();
4201
+ for (const [key, acc] of this.warningAccumulators) {
4202
+ if (now - acc.firstSeen > WARNING_DEDUP_WINDOW_MS) {
4203
+ this.warningAccumulators.delete(key);
4204
+ }
4205
+ }
4206
+ }
4207
+ // ---- helpers -------------------------------------------------------------
4208
+ inferSource(logFile) {
4209
+ if (logFile.includes("watchdog")) return "watchdog";
4210
+ if (logFile.includes("memory")) return "memory";
4211
+ return "agent";
4212
+ }
4213
+ // ---- position persistence ------------------------------------------------
4214
+ loadPositions() {
4215
+ try {
4216
+ if (!existsSync7(this.positionsFile)) return;
4217
+ const raw = readFileSync8(this.positionsFile, "utf-8");
4218
+ const data = JSON.parse(raw);
4219
+ for (const [file, pos] of Object.entries(data)) {
4220
+ this.tailPositions.set(file, pos);
4221
+ }
4222
+ log2.debug("loaded tail positions", { count: this.tailPositions.size });
4223
+ } catch {
4224
+ }
4225
+ }
4226
+ savePositions() {
4227
+ try {
4228
+ const { mkdirSync: mkdirSync15, writeFileSync: writeFileSync9 } = __require("fs");
4229
+ const { dirname: dirname9 } = __require("path");
4230
+ const dir = dirname9(this.positionsFile);
4231
+ if (!existsSync7(dir)) mkdirSync15(dir, { recursive: true });
4232
+ const data = Object.fromEntries(this.tailPositions);
4233
+ writeFileSync9(this.positionsFile, JSON.stringify(data));
4234
+ } catch (err) {
4235
+ log2.warn("failed to save tail positions", {
4236
+ error: err.message
4237
+ });
4238
+ }
4239
+ }
4240
+ };
4241
+
4242
+ // packages/runtime/src/processors/auto-debugger.ts
4243
+ import { execSync as execSync3 } from "child_process";
4244
+ import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
4245
+ import { resolve as resolve6 } from "path";
4246
+ var log3 = createLogger("auto-debugger");
4247
+ var SECRET_PATTERNS = [
4248
+ /(?:api[_-]?key|token|secret|password|credential|auth)[\s=:]+\S+/gi,
4249
+ /sk-[a-zA-Z0-9]{20,}/g,
4250
+ /ghp_[a-zA-Z0-9]{36}/g,
4251
+ /Bearer\s+\S+/g
4252
+ ];
4253
+ function sanitize(text) {
4254
+ let clean = text;
4255
+ for (const pat of SECRET_PATTERNS) {
4256
+ clean = clean.replace(pat, "[REDACTED]");
4257
+ }
4258
+ return clean;
4259
+ }
4260
+ function extractSourceFiles(stackTrace, repoRoot) {
4261
+ const files = /* @__PURE__ */ new Set();
4262
+ const pathPattern = /(?:[(]|\s)((?:\/[^\s:()]+|[a-zA-Z][a-zA-Z0-9/_.-]+)\.[a-zA-Z]+)(?::\d+){0,2}/g;
4263
+ let m;
4264
+ while ((m = pathPattern.exec(stackTrace)) !== null) {
4265
+ let filePath = m[1];
4266
+ if (filePath.includes("node_modules")) continue;
4267
+ if (!filePath.startsWith("/")) {
4268
+ filePath = resolve6(repoRoot, filePath);
4269
+ }
4270
+ if (filePath.startsWith(repoRoot) && existsSync8(filePath)) {
4271
+ files.add(filePath);
4272
+ }
4273
+ }
4274
+ return [...files].slice(0, 5);
4275
+ }
4276
+ function ghAvailable() {
4277
+ try {
4278
+ execSync3("gh auth status", { stdio: "ignore", timeout: 5e3 });
4279
+ return true;
4280
+ } catch {
4281
+ return false;
4282
+ }
4283
+ }
4284
+ function run(cmd, cwd) {
4285
+ try {
4286
+ return execSync3(cmd, { cwd, encoding: "utf-8", timeout: 6e4 }).trim();
4287
+ } catch {
4288
+ return null;
4289
+ }
4290
+ }
4291
+ var AutoDebugger = class extends BackgroundProcess {
4292
+ config;
4293
+ registry;
4294
+ llm;
4295
+ repoRoot;
4296
+ activeFixes = /* @__PURE__ */ new Set();
4297
+ // error ids currently being fixed
4298
+ cooldowns = /* @__PURE__ */ new Map();
4299
+ // error id → epoch ms of last fix attempt
4300
+ constructor(config, registry, llmConfig, repoRoot) {
4301
+ super("auto-debugger", 6e4);
4302
+ this.config = config;
4303
+ this.registry = registry;
4304
+ this.repoRoot = repoRoot;
4305
+ if (llmConfig && llmConfig.api_key) {
4306
+ try {
4307
+ this.llm = new LLMClient(llmConfig);
4308
+ } catch {
4309
+ this.llm = null;
4310
+ }
4311
+ } else {
4312
+ this.llm = null;
4313
+ }
4314
+ }
4315
+ // ---- BackgroundProcess implementation ------------------------------------
4316
+ async process(_context) {
4317
+ const errors = [];
4318
+ let itemsProcessed = 0;
4319
+ const actionable = this.registry.getActionable(this.config.severity_threshold);
4320
+ if (actionable.length === 0) return { itemsProcessed: 0, errors: [] };
4321
+ log3.info("actionable errors found", { count: actionable.length });
4322
+ for (const entry of actionable) {
4323
+ if (this.activeFixes.size >= this.config.max_concurrent_fixes) {
4324
+ log3.debug("max concurrent fixes reached, skipping remaining");
4325
+ break;
4326
+ }
4327
+ const lastAttempt = this.cooldowns.get(entry.id);
4328
+ if (lastAttempt && Date.now() - lastAttempt < this.config.cooldown_minutes * 6e4) {
4329
+ continue;
4330
+ }
4331
+ try {
4332
+ await this.investigate(entry);
4333
+ itemsProcessed++;
4334
+ } catch (err) {
4335
+ errors.push(`${entry.id}: ${err.message}`);
4336
+ log3.error("investigation failed", {
4337
+ id: entry.id,
4338
+ error: err.message
4339
+ });
4340
+ }
4341
+ }
4342
+ return { itemsProcessed, errors };
4343
+ }
4344
+ // ---- investigation pipeline ----------------------------------------------
4345
+ async investigate(entry) {
4346
+ this.registry.updateStatus(entry.id, "investigating");
4347
+ this.activeFixes.add(entry.id);
4348
+ this.cooldowns.set(entry.id, Date.now());
4349
+ try {
4350
+ const sourceFiles = entry.stackTrace ? extractSourceFiles(entry.stackTrace, this.repoRoot) : [];
4351
+ const fileContents = {};
4352
+ for (const f of sourceFiles) {
4353
+ try {
4354
+ const content = readFileSync9(f, "utf-8");
4355
+ fileContents[f] = content.split("\n").slice(0, 200).join("\n");
4356
+ } catch {
4357
+ }
4358
+ }
4359
+ if (this.llm) {
4360
+ const diagnosis = await this.diagnose(entry, fileContents);
4361
+ log3.info("diagnosis complete", {
4362
+ id: entry.id,
4363
+ diagnosis: diagnosis.slice(0, 200)
4364
+ });
4365
+ if (this.config.auto_pr && ghAvailable()) {
4366
+ await this.attemptFix(entry, diagnosis, fileContents);
4367
+ }
4368
+ } else {
4369
+ log3.info("error detected (no LLM configured for diagnosis)", {
4370
+ id: entry.id,
4371
+ message: entry.message.slice(0, 120),
4372
+ severity: entry.severity,
4373
+ sourceFiles
4374
+ });
4375
+ }
4376
+ } finally {
4377
+ this.activeFixes.delete(entry.id);
4378
+ }
4379
+ }
4380
+ // ---- LLM diagnosis -------------------------------------------------------
4381
+ async diagnose(entry, fileContents) {
4382
+ const fileSection = Object.entries(fileContents).map(([path, content]) => `--- ${path} ---
4383
+ ${content}`).join("\n\n");
4384
+ const messages = [
4385
+ {
4386
+ role: "system",
4387
+ content: [
4388
+ "You are a senior software engineer diagnosing a bug in the Hivemind agent framework (TypeScript/Node.js).",
4389
+ "Analyze the error, identify the root cause, and suggest a concise fix.",
4390
+ "Be specific: reference exact lines, variables, and logic flows.",
4391
+ "If you cannot determine the cause from the given context, say so."
4392
+ ].join("\n")
4393
+ },
4394
+ {
4395
+ role: "user",
4396
+ content: [
4397
+ `## Error`,
4398
+ `Message: ${entry.message}`,
4399
+ `Level: ${entry.level}`,
4400
+ `Source: ${entry.source}`,
4401
+ `Occurrences: ${entry.totalOccurrences}`,
4402
+ `First seen: ${entry.firstSeen}`,
4403
+ `Last seen: ${entry.lastSeen}`,
4404
+ entry.stackTrace ? `
4405
+ Stack trace:
4406
+ ${entry.stackTrace}` : "",
4407
+ fileSection ? `
4408
+ ## Source files
4409
+ ${fileSection}` : "",
4410
+ "\n## Task",
4411
+ "1. What is the root cause?",
4412
+ "2. What is the minimal fix?",
4413
+ "3. Are there any risks with the fix?"
4414
+ ].join("\n")
4415
+ }
4416
+ ];
4417
+ try {
4418
+ const response = await this.llm.chat(messages);
4419
+ return response.content;
4420
+ } catch (err) {
4421
+ log3.error("LLM diagnosis call failed", {
4422
+ error: err.message
4423
+ });
4424
+ return `Diagnosis failed: ${err.message}`;
4425
+ }
4426
+ }
4427
+ // ---- auto PR -------------------------------------------------------------
4428
+ async attemptFix(entry, diagnosis, fileContents) {
4429
+ const shortId = entry.id.slice(0, 8);
4430
+ const branchName = `${this.config.branch_prefix}/${shortId}`;
4431
+ if (run(`git rev-parse --verify ${branchName}`, this.repoRoot) !== null) {
4432
+ log3.info("fix branch already exists, skipping", { branch: branchName });
4433
+ return;
4434
+ }
4435
+ const fix = await this.generateFix(entry, diagnosis, fileContents);
4436
+ if (!fix) {
4437
+ log3.info("LLM could not generate a fix", { id: entry.id });
4438
+ return;
4439
+ }
4440
+ const baseBranch = run("git rev-parse --abbrev-ref HEAD", this.repoRoot) || "main";
4441
+ if (run(`git checkout -b ${branchName}`, this.repoRoot) === null) {
4442
+ log3.error("failed to create fix branch", { branch: branchName });
4443
+ return;
4444
+ }
4445
+ try {
4446
+ let anyApplied = false;
4447
+ for (const [filePath, newContent] of Object.entries(fix.files)) {
4448
+ try {
4449
+ __require("fs").writeFileSync(filePath, newContent);
4450
+ run(`git add "${filePath}"`, this.repoRoot);
4451
+ anyApplied = true;
4452
+ } catch (err) {
4453
+ log3.warn("failed to write fix file", {
4454
+ file: filePath,
4455
+ error: err.message
4456
+ });
4457
+ }
4458
+ }
4459
+ if (!anyApplied) {
4460
+ log3.info("no files were patched, aborting PR", { id: entry.id });
4461
+ return;
4462
+ }
4463
+ const buildResult = run("npx tsup", this.repoRoot);
4464
+ if (buildResult === null) {
4465
+ log3.warn("build failed after applying fix, aborting", { id: entry.id });
4466
+ return;
4467
+ }
4468
+ const commitMsg = [
4469
+ `fix(auto-debug): ${fix.summary}`,
4470
+ "",
4471
+ `Error: ${entry.message}`,
4472
+ `Occurrences: ${entry.totalOccurrences} over ${this.formatTimespan(entry.firstSeen, entry.lastSeen)}`,
4473
+ `Source: ${entry.source}`,
4474
+ "",
4475
+ `Root cause: ${fix.rootCause}`,
4476
+ "",
4477
+ "Auto-generated by hivemind auto-debug processor"
4478
+ ].join("\n");
4479
+ if (run(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, this.repoRoot) === null) {
4480
+ log3.error("git commit failed", { id: entry.id });
4481
+ return;
4482
+ }
4483
+ if (run(`git push -u origin ${branchName}`, this.repoRoot) === null) {
4484
+ log3.error("git push failed", { id: entry.id });
4485
+ return;
4486
+ }
4487
+ const prBody = this.buildPrBody(entry, diagnosis, fix);
4488
+ const prUrl = run(
4489
+ `gh pr create --base ${baseBranch} --head ${branchName} --title "fix(auto-debug): ${fix.summary}" --body "${prBody.replace(/"/g, '\\"')}"`,
4490
+ this.repoRoot
4491
+ );
4492
+ if (prUrl) {
4493
+ this.registry.updateStatus(entry.id, "fix-submitted", prUrl);
4494
+ log3.info("PR created", { id: entry.id, prUrl });
4495
+ } else {
4496
+ log3.error("gh pr create failed", { id: entry.id });
4497
+ }
4498
+ } finally {
4499
+ run(`git checkout ${baseBranch}`, this.repoRoot);
4500
+ }
4501
+ }
4502
+ async generateFix(entry, diagnosis, fileContents) {
4503
+ if (!this.llm) return null;
4504
+ const fileSection = Object.entries(fileContents).map(([path, content]) => `--- ${path} ---
4505
+ ${content}`).join("\n\n");
4506
+ const messages = [
4507
+ {
4508
+ role: "system",
4509
+ content: [
4510
+ "You are a senior TypeScript engineer. Generate a minimal, safe code fix.",
4511
+ "Output ONLY valid JSON with this shape:",
4512
+ '{ "summary": "short commit subject", "rootCause": "one sentence", "files": { "/absolute/path.ts": "full file content" } }',
4513
+ "Do NOT include explanations outside the JSON. Do NOT modify files that don't need changing.",
4514
+ "If you cannot produce a reliable fix, return null."
4515
+ ].join("\n")
4516
+ },
4517
+ {
4518
+ role: "user",
4519
+ content: [
4520
+ `## Error: ${entry.message}`,
4521
+ entry.stackTrace ? `
4522
+ Stack trace:
4523
+ ${entry.stackTrace}` : "",
4524
+ `
4525
+ ## Diagnosis
4526
+ ${diagnosis}`,
4527
+ `
4528
+ ## Current source files
4529
+ ${fileSection}`,
4530
+ "\nGenerate the fix JSON."
4531
+ ].join("\n")
4532
+ }
4533
+ ];
4534
+ try {
4535
+ const response = await this.llm.chat(messages);
4536
+ const content = response.content.trim();
4537
+ if (content === "null" || content === "null.") return null;
4538
+ const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/) || content.match(/(\{[\s\S]*\})/);
4539
+ if (!jsonMatch) return null;
4540
+ const parsed = JSON.parse(jsonMatch[1]);
4541
+ if (!parsed.summary || !parsed.files || Object.keys(parsed.files).length === 0) {
4542
+ return null;
4543
+ }
4544
+ for (const filePath of Object.keys(parsed.files)) {
4545
+ if (!filePath.startsWith(this.repoRoot)) {
4546
+ log3.warn("fix targets file outside repo, rejecting", { file: filePath });
4547
+ return null;
4548
+ }
4549
+ }
4550
+ return parsed;
4551
+ } catch (err) {
4552
+ log3.error("failed to generate fix", { error: err.message });
4553
+ return null;
4554
+ }
4555
+ }
4556
+ // ---- PR body builder -----------------------------------------------------
4557
+ buildPrBody(entry, diagnosis, fix) {
4558
+ const logSample = entry.stackTrace ? sanitize(entry.stackTrace.split("\n").slice(0, 15).join("\n")) : sanitize(entry.message);
4559
+ return [
4560
+ "## Auto-Debug Fix",
4561
+ "",
4562
+ `**Error:** \`${sanitize(entry.message).slice(0, 200)}\``,
4563
+ `**Source:** ${entry.source}`,
4564
+ `**Frequency:** ${entry.totalOccurrences} occurrences over ${this.formatTimespan(entry.firstSeen, entry.lastSeen)}`,
4565
+ `**Severity:** ${entry.severity}/10`,
4566
+ "",
4567
+ "### Diagnosis",
4568
+ sanitize(diagnosis),
4569
+ "",
4570
+ "### Fix",
4571
+ sanitize(fix.rootCause),
4572
+ "",
4573
+ "### Log Sample",
4574
+ "```",
4575
+ logSample,
4576
+ "```",
4577
+ "",
4578
+ "### Verification",
4579
+ "- [ ] Build passes (`tsup`)",
4580
+ "- [ ] Error no longer reproduces (monitored for 30 min post-deploy)",
4581
+ "",
4582
+ "---",
4583
+ "*Auto-generated by hivemind auto-debug processor. Review before merging.*"
4584
+ ].join("\n");
4585
+ }
4586
+ // ---- utils ---------------------------------------------------------------
4587
+ formatTimespan(firstSeen, lastSeen) {
4588
+ const ms = new Date(lastSeen).getTime() - new Date(firstSeen).getTime();
4589
+ if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
4590
+ if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
4591
+ if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
4592
+ return `${Math.round(ms / 864e5)}d`;
4593
+ }
4594
+ };
4595
+
3588
4596
  // packages/runtime/src/pipeline.ts
3589
4597
  import { createServer as createServer3 } from "http";
3590
4598
 
@@ -3594,23 +4602,23 @@ var HEALTH_PATH = "/health";
3594
4602
 
3595
4603
  // packages/runtime/src/request-logger.ts
3596
4604
  import { randomUUID as randomUUID2 } from "crypto";
3597
- import { mkdirSync as mkdirSync4, existsSync as existsSync6, appendFileSync as appendFileSync2, readFileSync as readFileSync7, writeFileSync } from "fs";
3598
- import { dirname as dirname4 } from "path";
4605
+ import { mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync as appendFileSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2 } from "fs";
4606
+ import { dirname as dirname5 } from "path";
3599
4607
  var RequestLogger = class {
3600
4608
  logPath;
3601
4609
  maxAgeDays = 7;
3602
4610
  constructor(dbPath) {
3603
4611
  this.logPath = dbPath.replace(/\.db$/, ".jsonl");
3604
4612
  if (this.logPath === dbPath) this.logPath = dbPath + ".jsonl";
3605
- const dir = dirname4(this.logPath);
3606
- if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
4613
+ const dir = dirname5(this.logPath);
4614
+ if (!existsSync9(dir)) mkdirSync5(dir, { recursive: true });
3607
4615
  this.prune();
3608
4616
  }
3609
4617
  prune() {
3610
- if (!existsSync6(this.logPath)) return;
4618
+ if (!existsSync9(this.logPath)) return;
3611
4619
  const cutoff = new Date(Date.now() - this.maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
3612
4620
  try {
3613
- const lines = readFileSync7(this.logPath, "utf-8").split("\n").filter(Boolean);
4621
+ const lines = readFileSync10(this.logPath, "utf-8").split("\n").filter(Boolean);
3614
4622
  const kept = [];
3615
4623
  let pruned = 0;
3616
4624
  for (const line of lines) {
@@ -3625,7 +4633,7 @@ var RequestLogger = class {
3625
4633
  }
3626
4634
  }
3627
4635
  if (pruned > 0) {
3628
- writeFileSync(this.logPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
4636
+ writeFileSync2(this.logPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
3629
4637
  console.log(`[dashboard] Pruned ${pruned} old request logs`);
3630
4638
  }
3631
4639
  } catch {
@@ -3686,9 +4694,9 @@ var RequestLogger = class {
3686
4694
  close() {
3687
4695
  }
3688
4696
  readAll() {
3689
- if (!existsSync6(this.logPath)) return [];
4697
+ if (!existsSync9(this.logPath)) return [];
3690
4698
  try {
3691
- const lines = readFileSync7(this.logPath, "utf-8").split("\n").filter(Boolean);
4699
+ const lines = readFileSync10(this.logPath, "utf-8").split("\n").filter(Boolean);
3692
4700
  const entries = [];
3693
4701
  for (const line of lines) {
3694
4702
  try {
@@ -3705,17 +4713,17 @@ var RequestLogger = class {
3705
4713
 
3706
4714
  // packages/runtime/src/dashboard.ts
3707
4715
  import { createServer } from "http";
3708
- import { readFileSync as readFileSync8 } from "fs";
3709
- import { resolve as resolve6, dirname as dirname5 } from "path";
4716
+ import { readFileSync as readFileSync11 } from "fs";
4717
+ import { resolve as resolve7, dirname as dirname6 } from "path";
3710
4718
  import { fileURLToPath as fileURLToPath2 } from "url";
3711
- var __dirname = dirname5(fileURLToPath2(import.meta.url));
4719
+ var __dirname = dirname6(fileURLToPath2(import.meta.url));
3712
4720
  var DASHBOARD_PORT = 9485;
3713
4721
  var spaHtml = null;
3714
4722
  function getSpaHtml() {
3715
4723
  if (!spaHtml) {
3716
- for (const dir of [__dirname, resolve6(__dirname, "../src")]) {
4724
+ for (const dir of [__dirname, resolve7(__dirname, "../src")]) {
3717
4725
  try {
3718
- spaHtml = readFileSync8(resolve6(dir, "dashboard.html"), "utf-8");
4726
+ spaHtml = readFileSync11(resolve7(dir, "dashboard.html"), "utf-8");
3719
4727
  break;
3720
4728
  } catch {
3721
4729
  }
@@ -3741,29 +4749,43 @@ function parseQuery(url) {
3741
4749
  }
3742
4750
  return params;
3743
4751
  }
3744
- async function proxyMemory(memoryUrl, path, method, res) {
4752
+ function readBody(req) {
4753
+ return new Promise((resolve21, reject) => {
4754
+ const chunks = [];
4755
+ req.on("data", (chunk) => chunks.push(chunk));
4756
+ req.on("end", () => resolve21(Buffer.concat(chunks).toString()));
4757
+ req.on("error", reject);
4758
+ });
4759
+ }
4760
+ async function proxyMemory(memoryUrl, path, method, res, body) {
3745
4761
  try {
3746
- const resp = await fetch(`${memoryUrl}${path}`, { method });
3747
- const body = await resp.text();
4762
+ const opts = { method };
4763
+ if (body && (method === "POST" || method === "PATCH" || method === "PUT")) {
4764
+ opts.body = body;
4765
+ opts.headers = { "Content-Type": "application/json" };
4766
+ }
4767
+ const resp = await fetch(`${memoryUrl}${path}`, opts);
4768
+ const respBody = await resp.text();
3748
4769
  res.writeHead(resp.status, {
3749
4770
  "Content-Type": resp.headers.get("content-type") ?? "application/json",
3750
4771
  "Access-Control-Allow-Origin": "*"
3751
4772
  });
3752
- res.end(body);
4773
+ res.end(respBody);
3753
4774
  } catch (err) {
3754
4775
  json(res, { error: err.message }, 502);
3755
4776
  }
3756
4777
  }
3757
- function startDashboardServer(requestLogger, memoryConfig) {
4778
+ function startDashboardServer(requestLogger, memoryConfig, getL1) {
3758
4779
  const memoryUrl = memoryConfig.daemon_url;
3759
4780
  const server = createServer(async (req, res) => {
3760
4781
  const method = req.method ?? "GET";
3761
4782
  const rawUrl = req.url ?? "/";
3762
4783
  const urlPath = rawUrl.split("?")[0];
4784
+ const queryStr = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
3763
4785
  if (method === "OPTIONS") {
3764
4786
  res.writeHead(204, {
3765
4787
  "Access-Control-Allow-Origin": "*",
3766
- "Access-Control-Allow-Methods": "GET, DELETE, OPTIONS",
4788
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
3767
4789
  "Access-Control-Allow-Headers": "Content-Type"
3768
4790
  });
3769
4791
  res.end();
@@ -3796,10 +4818,32 @@ function startDashboardServer(requestLogger, memoryConfig) {
3796
4818
  }
3797
4819
  return;
3798
4820
  }
4821
+ if (method === "GET" && urlPath === "/api/health") {
4822
+ await proxyMemory(memoryUrl, "/health", "GET", res);
4823
+ return;
4824
+ }
4825
+ if (method === "GET" && urlPath === "/api/stats") {
4826
+ await proxyMemory(memoryUrl, "/stats", "GET", res);
4827
+ return;
4828
+ }
4829
+ if (method === "GET" && urlPath === "/api/search") {
4830
+ await proxyMemory(memoryUrl, `/search${queryStr}`, "GET", res);
4831
+ return;
4832
+ }
4833
+ if (method === "GET" && urlPath === "/api/search/cross-context") {
4834
+ await proxyMemory(memoryUrl, `/search/cross-context${queryStr}`, "GET", res);
4835
+ return;
4836
+ }
3799
4837
  if (method === "GET" && urlPath === "/api/contexts") {
3800
4838
  await proxyMemory(memoryUrl, "/contexts", "GET", res);
3801
4839
  return;
3802
4840
  }
4841
+ const ctxDeleteMatch = urlPath.match(/^\/api\/contexts\/([^/]+)$/);
4842
+ if (method === "DELETE" && ctxDeleteMatch) {
4843
+ const name = decodeURIComponent(ctxDeleteMatch[1]);
4844
+ await proxyMemory(memoryUrl, `/contexts/${encodeURIComponent(name)}`, "DELETE", res);
4845
+ return;
4846
+ }
3803
4847
  const episodesMatch = urlPath.match(/^\/api\/contexts\/([^/]+)\/episodes$/);
3804
4848
  if (method === "GET" && episodesMatch) {
3805
4849
  const name = decodeURIComponent(episodesMatch[1]);
@@ -3817,12 +4861,69 @@ function startDashboardServer(requestLogger, memoryConfig) {
3817
4861
  );
3818
4862
  return;
3819
4863
  }
3820
- const l3DeleteMatch = urlPath.match(/^\/api\/l3\/([^/]+)$/);
3821
- if (method === "DELETE" && l3DeleteMatch) {
3822
- const id = decodeURIComponent(l3DeleteMatch[1]);
4864
+ const scoringGetMatch = urlPath.match(/^\/api\/contexts\/([^/]+)\/scoring$/);
4865
+ if (method === "GET" && scoringGetMatch) {
4866
+ const name = decodeURIComponent(scoringGetMatch[1]);
4867
+ await proxyMemory(memoryUrl, `/scoring/${encodeURIComponent(name)}`, "GET", res);
4868
+ return;
4869
+ }
4870
+ if (method === "POST" && scoringGetMatch) {
4871
+ const name = decodeURIComponent(scoringGetMatch[1]);
4872
+ const body = await readBody(req);
4873
+ await proxyMemory(memoryUrl, `/contexts/${encodeURIComponent(name)}/scoring`, "POST", res, body);
4874
+ return;
4875
+ }
4876
+ const l3IdMatch = urlPath.match(/^\/api\/l3\/([^/]+)$/);
4877
+ if (method === "DELETE" && l3IdMatch) {
4878
+ const id = decodeURIComponent(l3IdMatch[1]);
3823
4879
  await proxyMemory(memoryUrl, `/promotion/l3/${encodeURIComponent(id)}`, "DELETE", res);
3824
4880
  return;
3825
4881
  }
4882
+ if (method === "PATCH" && l3IdMatch) {
4883
+ const id = decodeURIComponent(l3IdMatch[1]);
4884
+ const body = await readBody(req);
4885
+ await proxyMemory(memoryUrl, `/promotion/l3/${encodeURIComponent(id)}`, "PATCH", res, body);
4886
+ return;
4887
+ }
4888
+ if (method === "POST" && urlPath === "/api/promotion/run") {
4889
+ await proxyMemory(memoryUrl, `/promotion/run${queryStr}`, "POST", res);
4890
+ return;
4891
+ }
4892
+ if (method === "GET" && urlPath === "/api/access/top") {
4893
+ await proxyMemory(memoryUrl, `/access/top${queryStr}`, "GET", res);
4894
+ return;
4895
+ }
4896
+ const accessMatch = urlPath.match(/^\/api\/episodes\/([^/]+)\/access$/);
4897
+ if (method === "GET" && accessMatch) {
4898
+ const id = decodeURIComponent(accessMatch[1]);
4899
+ await proxyMemory(memoryUrl, `/access/${encodeURIComponent(id)}`, "GET", res);
4900
+ return;
4901
+ }
4902
+ if (method === "GET" && urlPath === "/api/l1") {
4903
+ if (!getL1) {
4904
+ json(res, { contexts: {} });
4905
+ return;
4906
+ }
4907
+ const histories = getL1();
4908
+ const result = {};
4909
+ for (const [ctx, msgs] of histories) {
4910
+ result[ctx] = { count: msgs.length };
4911
+ }
4912
+ json(res, { contexts: result });
4913
+ return;
4914
+ }
4915
+ const l1CtxMatch = urlPath.match(/^\/api\/l1\/([^/]+)$/);
4916
+ if (method === "GET" && l1CtxMatch) {
4917
+ if (!getL1) {
4918
+ json(res, { messages: [] });
4919
+ return;
4920
+ }
4921
+ const ctx = decodeURIComponent(l1CtxMatch[1]);
4922
+ const histories = getL1();
4923
+ const messages = histories.get(ctx) || [];
4924
+ json(res, { messages: messages.map((m) => ({ role: m.role, content: m.content ?? "" })) });
4925
+ return;
4926
+ }
3826
4927
  json(res, { error: "Not found" }, 404);
3827
4928
  } catch (err) {
3828
4929
  console.error("[dashboard] Request error:", err.message);
@@ -3890,8 +4991,8 @@ var ToolRegistry = class {
3890
4991
  };
3891
4992
 
3892
4993
  // packages/runtime/src/tools/shell.ts
3893
- import { execSync as execSync3 } from "child_process";
3894
- import { resolve as resolve7 } from "path";
4994
+ import { execSync as execSync4 } from "child_process";
4995
+ import { resolve as resolve8 } from "path";
3895
4996
  var MAX_OUTPUT = 5e4;
3896
4997
  function registerShellTool(registry, workspaceDir) {
3897
4998
  registry.register(
@@ -3918,9 +5019,9 @@ function registerShellTool(registry, workspaceDir) {
3918
5019
  async (params) => {
3919
5020
  const command = params.command;
3920
5021
  const timeout = (params.timeout || 30) * 1e3;
3921
- const cwd = params.workdir ? resolve7(workspaceDir, params.workdir) : workspaceDir;
5022
+ const cwd = params.workdir ? resolve8(workspaceDir, params.workdir) : workspaceDir;
3922
5023
  try {
3923
- const output = execSync3(command, {
5024
+ const output = execSync4(command, {
3924
5025
  cwd,
3925
5026
  timeout,
3926
5027
  encoding: "utf-8",
@@ -3945,8 +5046,8 @@ ${output || err.message}`;
3945
5046
  }
3946
5047
 
3947
5048
  // packages/runtime/src/tools/files.ts
3948
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync2, existsSync as existsSync7, mkdirSync as mkdirSync5 } from "fs";
3949
- import { resolve as resolve8, dirname as dirname6 } from "path";
5049
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync3, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
5050
+ import { resolve as resolve9, dirname as dirname7 } from "path";
3950
5051
  var MAX_READ = 1e5;
3951
5052
  function registerFileTools(registry, workspaceDir) {
3952
5053
  registry.register(
@@ -3972,11 +5073,11 @@ function registerFileTools(registry, workspaceDir) {
3972
5073
  },
3973
5074
  async (params) => {
3974
5075
  const filePath = resolvePath(workspaceDir, params.path);
3975
- if (!existsSync7(filePath)) {
5076
+ if (!existsSync10(filePath)) {
3976
5077
  return `Error: File not found: ${filePath}`;
3977
5078
  }
3978
5079
  try {
3979
- let content = readFileSync9(filePath, "utf-8");
5080
+ let content = readFileSync12(filePath, "utf-8");
3980
5081
  if (params.offset || params.limit) {
3981
5082
  const lines = content.split("\n");
3982
5083
  const start = (params.offset || 1) - 1;
@@ -4013,9 +5114,9 @@ function registerFileTools(registry, workspaceDir) {
4013
5114
  async (params) => {
4014
5115
  const filePath = resolvePath(workspaceDir, params.path);
4015
5116
  try {
4016
- const dir = dirname6(filePath);
4017
- if (!existsSync7(dir)) mkdirSync5(dir, { recursive: true });
4018
- writeFileSync2(filePath, params.content);
5117
+ const dir = dirname7(filePath);
5118
+ if (!existsSync10(dir)) mkdirSync6(dir, { recursive: true });
5119
+ writeFileSync3(filePath, params.content);
4019
5120
  return `Written ${params.content.length} bytes to ${filePath}`;
4020
5121
  } catch (err) {
4021
5122
  return `Error writing file: ${err.message}`;
@@ -4045,18 +5146,18 @@ function registerFileTools(registry, workspaceDir) {
4045
5146
  },
4046
5147
  async (params) => {
4047
5148
  const filePath = resolvePath(workspaceDir, params.path);
4048
- if (!existsSync7(filePath)) {
5149
+ if (!existsSync10(filePath)) {
4049
5150
  return `Error: File not found: ${filePath}`;
4050
5151
  }
4051
5152
  try {
4052
- const content = readFileSync9(filePath, "utf-8");
5153
+ const content = readFileSync12(filePath, "utf-8");
4053
5154
  const oldText = params.old_text;
4054
5155
  const newText = params.new_text;
4055
5156
  if (!content.includes(oldText)) {
4056
5157
  return `Error: Could not find the exact text to replace in ${filePath}`;
4057
5158
  }
4058
5159
  const updated = content.replace(oldText, newText);
4059
- writeFileSync2(filePath, updated);
5160
+ writeFileSync3(filePath, updated);
4060
5161
  return `Edited ${filePath}: replaced ${oldText.length} chars with ${newText.length} chars`;
4061
5162
  } catch (err) {
4062
5163
  return `Error editing file: ${err.message}`;
@@ -4077,9 +5178,9 @@ function registerFileTools(registry, workspaceDir) {
4077
5178
  required: []
4078
5179
  },
4079
5180
  async (params) => {
4080
- const { readdirSync: readdirSync5, statSync: statSync3 } = await import("fs");
5181
+ const { readdirSync: readdirSync5, statSync: statSync4 } = await import("fs");
4081
5182
  const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
4082
- if (!existsSync7(dirPath)) {
5183
+ if (!existsSync10(dirPath)) {
4083
5184
  return `Error: Directory not found: ${dirPath}`;
4084
5185
  }
4085
5186
  try {
@@ -4088,7 +5189,7 @@ function registerFileTools(registry, workspaceDir) {
4088
5189
  for (const entry of entries) {
4089
5190
  if (entry.startsWith(".")) continue;
4090
5191
  try {
4091
- const stat = statSync3(resolve8(dirPath, entry));
5192
+ const stat = statSync4(resolve9(dirPath, entry));
4092
5193
  results.push(stat.isDirectory() ? `${entry}/` : entry);
4093
5194
  } catch {
4094
5195
  results.push(entry);
@@ -4105,7 +5206,7 @@ function resolvePath(workspace, path) {
4105
5206
  if (path.startsWith("/") || path.startsWith("~")) {
4106
5207
  return path.replace(/^~/, process.env.HOME || "/root");
4107
5208
  }
4108
- return resolve8(workspace, path);
5209
+ return resolve9(workspace, path);
4109
5210
  }
4110
5211
 
4111
5212
  // packages/runtime/src/tools/web.ts
@@ -4358,13 +5459,13 @@ Contexts:
4358
5459
  }
4359
5460
 
4360
5461
  // packages/runtime/src/tools/events.ts
4361
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readdirSync as readdirSync4, readFileSync as readFileSync10, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
5462
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
4362
5463
  import { join as join4 } from "path";
4363
5464
  import { randomUUID as randomUUID3 } from "crypto";
4364
5465
  function registerEventTools(registry, dataDir) {
4365
5466
  const eventsDir = join4(dataDir, "events");
4366
- if (!existsSync8(eventsDir)) {
4367
- mkdirSync6(eventsDir, { recursive: true });
5467
+ if (!existsSync11(eventsDir)) {
5468
+ mkdirSync7(eventsDir, { recursive: true });
4368
5469
  }
4369
5470
  registry.register(
4370
5471
  "create_event",
@@ -4433,7 +5534,7 @@ function registerEventTools(registry, dataDir) {
4433
5534
  const filename = `${randomUUID3()}.json`;
4434
5535
  const filePath = join4(eventsDir, filename);
4435
5536
  try {
4436
- writeFileSync3(filePath, JSON.stringify(event, null, 2));
5537
+ writeFileSync4(filePath, JSON.stringify(event, null, 2));
4437
5538
  return `Event created: ${filename} (type: ${type})`;
4438
5539
  } catch (err) {
4439
5540
  return `Error creating event: ${err.message}`;
@@ -4450,7 +5551,7 @@ function registerEventTools(registry, dataDir) {
4450
5551
  },
4451
5552
  async () => {
4452
5553
  try {
4453
- if (!existsSync8(eventsDir)) {
5554
+ if (!existsSync11(eventsDir)) {
4454
5555
  return "No events directory found.";
4455
5556
  }
4456
5557
  const files = readdirSync4(eventsDir).filter((f) => f.endsWith(".json"));
@@ -4460,7 +5561,7 @@ function registerEventTools(registry, dataDir) {
4460
5561
  const lines = [];
4461
5562
  for (const file of files) {
4462
5563
  try {
4463
- const content = readFileSync10(join4(eventsDir, file), "utf-8");
5564
+ const content = readFileSync13(join4(eventsDir, file), "utf-8");
4464
5565
  const event = JSON.parse(content);
4465
5566
  let detail = `${file} \u2014 type: ${event.type}, channel: ${event.channelId}`;
4466
5567
  if (event.type === "one-shot") {
@@ -4500,7 +5601,7 @@ ${lines.join("\n")}`;
4500
5601
  if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
4501
5602
  return "Error: Invalid filename.";
4502
5603
  }
4503
- if (!existsSync8(filePath)) {
5604
+ if (!existsSync11(filePath)) {
4504
5605
  return `Event not found: ${filename}`;
4505
5606
  }
4506
5607
  try {
@@ -4515,16 +5616,16 @@ ${lines.join("\n")}`;
4515
5616
 
4516
5617
  // packages/runtime/src/tools/spawn.ts
4517
5618
  import { spawn } from "child_process";
4518
- import { existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
4519
- import { join as join5, resolve as resolve9 } from "path";
5619
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
5620
+ import { join as join5, resolve as resolve10 } from "path";
4520
5621
  import { randomUUID as randomUUID4 } from "crypto";
4521
5622
  var spawnedAgents = /* @__PURE__ */ new Map();
4522
5623
  function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
4523
5624
  const spawnDir = join5(dataDir, "spawn");
4524
- if (!existsSync9(spawnDir)) {
4525
- mkdirSync7(spawnDir, { recursive: true });
5625
+ if (!existsSync12(spawnDir)) {
5626
+ mkdirSync8(spawnDir, { recursive: true });
4526
5627
  }
4527
- const hivemindBin = resolve9(hivemindHome, "node_modules", ".bin", "hivemind");
5628
+ const hivemindBin = resolve10(hivemindHome, "node_modules", ".bin", "hivemind");
4528
5629
  registry.register(
4529
5630
  "spawn_agent",
4530
5631
  "Fork a new hivemind process to run an isolated task. The sub-agent gets its own context, processes the task, and exits. Use for parallel work, long-running tasks, or tasks that need isolation.",
@@ -4557,8 +5658,8 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
4557
5658
  const channelId = params.channelId;
4558
5659
  const timeoutSeconds = params.timeoutSeconds || 300;
4559
5660
  const spawnWorkDir = join5(spawnDir, spawnId);
4560
- mkdirSync7(spawnWorkDir, { recursive: true });
4561
- writeFileSync4(join5(spawnWorkDir, "task.md"), task);
5661
+ mkdirSync8(spawnWorkDir, { recursive: true });
5662
+ writeFileSync5(join5(spawnWorkDir, "task.md"), task);
4562
5663
  const childEnv = {
4563
5664
  ...process.env,
4564
5665
  SPAWN_TASK: task,
@@ -4600,7 +5701,7 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
4600
5701
  agent.exitCode = code;
4601
5702
  agent.exited = true;
4602
5703
  try {
4603
- writeFileSync4(logPath, Buffer.concat(logChunks).toString("utf-8"));
5704
+ writeFileSync5(logPath, Buffer.concat(logChunks).toString("utf-8"));
4604
5705
  } catch {
4605
5706
  }
4606
5707
  });
@@ -4613,7 +5714,7 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
4613
5714
  agent.exited = true;
4614
5715
  agent.exitCode = -1;
4615
5716
  try {
4616
- writeFileSync4(
5717
+ writeFileSync5(
4617
5718
  join5(spawnWorkDir, "result.txt"),
4618
5719
  `[TIMEOUT] Sub-agent killed after ${timeoutSeconds}s`
4619
5720
  );
@@ -4668,9 +5769,9 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
4668
5769
  task: "${taskPreview}"`;
4669
5770
  if (agent.exited) {
4670
5771
  const resultPath = join5(spawnDir, id, "result.txt");
4671
- if (existsSync9(resultPath)) {
5772
+ if (existsSync12(resultPath)) {
4672
5773
  try {
4673
- const result = readFileSync11(resultPath, "utf-8");
5774
+ const result = readFileSync14(resultPath, "utf-8");
4674
5775
  const preview = result.length > 200 ? result.slice(0, 200) + "..." : result;
4675
5776
  detail += `
4676
5777
  result: "${preview}"`;
@@ -4710,7 +5811,7 @@ ${lines.join("\n\n")}`;
4710
5811
  agent.process.kill("SIGTERM");
4711
5812
  agent.exited = true;
4712
5813
  agent.exitCode = -2;
4713
- writeFileSync4(
5814
+ writeFileSync5(
4714
5815
  join5(spawnDir, id, "result.txt"),
4715
5816
  `[KILLED] Sub-agent killed by parent agent`
4716
5817
  );
@@ -4793,11 +5894,11 @@ function registerVisionTools(registry) {
4793
5894
  }
4794
5895
 
4795
5896
  // packages/runtime/src/tools/git.ts
4796
- import { execSync as execSync4 } from "child_process";
4797
- import { resolve as resolve10, normalize } from "path";
5897
+ import { execSync as execSync5 } from "child_process";
5898
+ import { resolve as resolve11, normalize } from "path";
4798
5899
  function resolveRepoPath(workspaceDir, repoPath) {
4799
5900
  if (!repoPath) return workspaceDir;
4800
- const resolved = resolve10(workspaceDir, repoPath);
5901
+ const resolved = resolve11(workspaceDir, repoPath);
4801
5902
  const normalizedResolved = normalize(resolved);
4802
5903
  const normalizedWorkspace = normalize(workspaceDir);
4803
5904
  if (!normalizedResolved.startsWith(normalizedWorkspace)) {
@@ -4806,7 +5907,7 @@ function resolveRepoPath(workspaceDir, repoPath) {
4806
5907
  return resolved;
4807
5908
  }
4808
5909
  function runGit(args, cwd) {
4809
- const output = execSync4(`git ${args}`, {
5910
+ const output = execSync5(`git ${args}`, {
4810
5911
  cwd,
4811
5912
  timeout: 3e4,
4812
5913
  encoding: "utf-8",
@@ -5030,11 +6131,12 @@ ${combined}`;
5030
6131
  }
5031
6132
 
5032
6133
  // packages/runtime/src/tools/browser.ts
5033
- import { resolve as resolve11 } from "path";
5034
- import { mkdirSync as mkdirSync8, existsSync as existsSync10 } from "fs";
6134
+ import { resolve as resolve12 } from "path";
6135
+ import { mkdirSync as mkdirSync9, existsSync as existsSync13 } from "fs";
5035
6136
  import { randomUUID as randomUUID5 } from "crypto";
5036
6137
  var MAX_OUTPUT2 = 5e4;
5037
6138
  var browserInstance = null;
6139
+ var contextInstances = /* @__PURE__ */ new Map();
5038
6140
  var lastUsed = 0;
5039
6141
  var idleTimer = null;
5040
6142
  var IDLE_TIMEOUT_MS = 5 * 60 * 1e3;
@@ -5043,7 +6145,16 @@ async function getBrowser() {
5043
6145
  const modName = "playwright";
5044
6146
  const pw = await Function("m", "return import(m)")(modName);
5045
6147
  if (!browserInstance) {
5046
- browserInstance = await pw.chromium.launch({ headless: true });
6148
+ browserInstance = await pw.chromium.launch({
6149
+ headless: true,
6150
+ args: [
6151
+ "--disable-dev-shm-usage",
6152
+ "--disable-setuid-sandbox",
6153
+ "--no-sandbox",
6154
+ "--disable-web-security",
6155
+ "--disable-features=VizDisplayCompositor"
6156
+ ]
6157
+ });
5047
6158
  if (!idleTimer) {
5048
6159
  idleTimer = setInterval(async () => {
5049
6160
  if (browserInstance && Date.now() - lastUsed > IDLE_TIMEOUT_MS) {
@@ -5060,7 +6171,43 @@ async function getBrowser() {
5060
6171
  );
5061
6172
  }
5062
6173
  }
6174
+ async function getBrowserContext(sessionId = "default", options = {}) {
6175
+ const browser = await getBrowser();
6176
+ if (!contextInstances.has(sessionId)) {
6177
+ const contextOptions = {
6178
+ userAgent: options.userAgent || "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
6179
+ viewport: options.viewport || { width: 1280, height: 720 },
6180
+ locale: options.locale || "en-US",
6181
+ timezoneId: options.timezone || "America/New_York",
6182
+ deviceScaleFactor: options.deviceScaleFactor || 1,
6183
+ isMobile: options.isMobile || false,
6184
+ hasTouch: options.hasTouch || false,
6185
+ colorScheme: options.colorScheme || "light",
6186
+ reducedMotion: options.reducedMotion || "no-preference",
6187
+ permissions: options.permissions || [],
6188
+ geolocation: options.geolocation,
6189
+ offline: options.offline || false,
6190
+ acceptDownloads: options.downloadBehavior !== "deny"
6191
+ };
6192
+ const context = await browser.newContext(contextOptions);
6193
+ context.on("request", (request) => {
6194
+ console.log(`\u2192 ${request.method()} ${request.url()}`);
6195
+ });
6196
+ context.on("response", (response) => {
6197
+ console.log(`\u2190 ${response.status()} ${response.url()}`);
6198
+ });
6199
+ contextInstances.set(sessionId, context);
6200
+ }
6201
+ return contextInstances.get(sessionId);
6202
+ }
5063
6203
  async function closeBrowser() {
6204
+ for (const [sessionId, context] of contextInstances) {
6205
+ try {
6206
+ await context.close();
6207
+ } catch {
6208
+ }
6209
+ }
6210
+ contextInstances.clear();
5064
6211
  if (browserInstance) {
5065
6212
  try {
5066
6213
  await browserInstance.close();
@@ -5074,18 +6221,30 @@ async function closeBrowser() {
5074
6221
  }
5075
6222
  }
5076
6223
  function registerBrowserTools(registry, workspaceDir) {
5077
- const screenshotDir = resolve11(workspaceDir, "screenshots");
6224
+ const screenshotDir = resolve12(workspaceDir, "screenshots");
5078
6225
  registry.register(
5079
6226
  "browse",
5080
6227
  [
5081
- "Navigate to a URL and interact with the page using a headless browser.",
6228
+ "Navigate to a URL and interact with the page using an enhanced headless browser with session persistence.",
5082
6229
  "Actions:",
5083
6230
  " extract (default) \u2014 get the readable text content of the page",
5084
6231
  " screenshot \u2014 take a PNG screenshot and return the file path",
5085
6232
  " click \u2014 click an element matching a CSS selector",
5086
6233
  " type \u2014 type text into an element matching a CSS selector",
5087
6234
  " evaluate \u2014 run arbitrary JavaScript in the page and return the result",
5088
- "Supports waitForSelector to wait for dynamic content before acting."
6235
+ " form_fill \u2014 fill out a form with multiple fields at once",
6236
+ " scroll \u2014 scroll the page (up, down, to element, or to coordinates)",
6237
+ " hover \u2014 hover over an element",
6238
+ " select \u2014 select an option from a dropdown",
6239
+ " upload \u2014 upload a file to a file input",
6240
+ " download \u2014 download a file and return the path",
6241
+ " pdf \u2014 generate a PDF of the page",
6242
+ " wait_for \u2014 wait for various conditions (selector, text, url, timeout)",
6243
+ " network_logs \u2014 get network request/response logs",
6244
+ " console_logs \u2014 get browser console logs",
6245
+ " cookies \u2014 get/set/clear cookies",
6246
+ " storage \u2014 get/set/clear localStorage/sessionStorage",
6247
+ "Supports session persistence, mobile simulation, and advanced browser features."
5089
6248
  ].join("\n"),
5090
6249
  {
5091
6250
  type: "object",
@@ -5096,21 +6255,101 @@ function registerBrowserTools(registry, workspaceDir) {
5096
6255
  },
5097
6256
  action: {
5098
6257
  type: "string",
5099
- enum: ["extract", "screenshot", "click", "type", "evaluate"],
6258
+ enum: [
6259
+ "extract",
6260
+ "screenshot",
6261
+ "click",
6262
+ "type",
6263
+ "evaluate",
6264
+ "form_fill",
6265
+ "scroll",
6266
+ "hover",
6267
+ "select",
6268
+ "upload",
6269
+ "download",
6270
+ "pdf",
6271
+ "wait_for",
6272
+ "network_logs",
6273
+ "console_logs",
6274
+ "cookies",
6275
+ "storage"
6276
+ ],
5100
6277
  description: "Action to perform (default: extract)."
5101
6278
  },
5102
6279
  selector: {
5103
6280
  type: "string",
5104
- description: "CSS selector for click/type actions."
6281
+ description: "CSS selector for element-based actions."
5105
6282
  },
5106
6283
  text: {
5107
6284
  type: "string",
5108
- description: "Text to type (for 'type' action)."
6285
+ description: "Text to type or search for."
5109
6286
  },
5110
6287
  javascript: {
5111
6288
  type: "string",
5112
6289
  description: "JavaScript to evaluate in the page (for 'evaluate' action)."
5113
6290
  },
6291
+ formData: {
6292
+ type: "object",
6293
+ description: "Key-value pairs for form filling (selector: value)."
6294
+ },
6295
+ scrollDirection: {
6296
+ type: "string",
6297
+ enum: ["up", "down", "left", "right", "top", "bottom"],
6298
+ description: "Scroll direction or position."
6299
+ },
6300
+ scrollDistance: {
6301
+ type: "number",
6302
+ description: "Pixels to scroll (default: 500)."
6303
+ },
6304
+ coordinates: {
6305
+ type: "object",
6306
+ properties: {
6307
+ x: { type: "number" },
6308
+ y: { type: "number" }
6309
+ },
6310
+ description: "X,Y coordinates for scrolling or clicking."
6311
+ },
6312
+ filePath: {
6313
+ type: "string",
6314
+ description: "Path to file for upload action."
6315
+ },
6316
+ waitCondition: {
6317
+ type: "string",
6318
+ enum: ["selector", "text", "url", "timeout", "networkidle"],
6319
+ description: "What to wait for."
6320
+ },
6321
+ waitValue: {
6322
+ type: "string",
6323
+ description: "Value to wait for (selector, text, URL pattern)."
6324
+ },
6325
+ cookieData: {
6326
+ type: "object",
6327
+ description: "Cookie data for cookie operations."
6328
+ },
6329
+ storageData: {
6330
+ type: "object",
6331
+ description: "Storage data for localStorage/sessionStorage operations."
6332
+ },
6333
+ session: {
6334
+ type: "string",
6335
+ description: "Session ID for persistent browser context (default: 'default')."
6336
+ },
6337
+ viewport: {
6338
+ type: "object",
6339
+ properties: {
6340
+ width: { type: "number" },
6341
+ height: { type: "number" }
6342
+ },
6343
+ description: "Browser viewport size."
6344
+ },
6345
+ userAgent: {
6346
+ type: "string",
6347
+ description: "Custom user agent string."
6348
+ },
6349
+ mobile: {
6350
+ type: "boolean",
6351
+ description: "Simulate mobile device."
6352
+ },
5114
6353
  waitForSelector: {
5115
6354
  type: "string",
5116
6355
  description: "CSS selector to wait for before performing the action."
@@ -5128,19 +6367,33 @@ function registerBrowserTools(registry, workspaceDir) {
5128
6367
  const selector = params.selector;
5129
6368
  const text = params.text;
5130
6369
  const javascript = params.javascript;
6370
+ const formData = params.formData;
6371
+ const scrollDirection = params.scrollDirection;
6372
+ const scrollDistance = params.scrollDistance || 500;
6373
+ const coordinates = params.coordinates;
6374
+ const filePath = params.filePath;
6375
+ const waitCondition = params.waitCondition;
6376
+ const waitValue = params.waitValue;
6377
+ const cookieData = params.cookieData;
6378
+ const storageData = params.storageData;
6379
+ const sessionId = params.session || "default";
6380
+ const viewport = params.viewport;
6381
+ const userAgent = params.userAgent;
6382
+ const mobile = params.mobile;
5131
6383
  const waitForSelector = params.waitForSelector;
5132
6384
  const timeout = params.timeout || 3e4;
5133
- let browser;
5134
- try {
5135
- browser = await getBrowser();
5136
- } catch (err) {
5137
- return err.message;
5138
- }
6385
+ let context;
5139
6386
  let page;
5140
6387
  try {
5141
- page = await browser.newPage({
5142
- userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
5143
- });
6388
+ const browserOptions = {
6389
+ session: sessionId,
6390
+ viewport,
6391
+ userAgent,
6392
+ isMobile: mobile,
6393
+ hasTouch: mobile
6394
+ };
6395
+ context = await getBrowserContext(sessionId, browserOptions);
6396
+ page = await context.newPage();
5144
6397
  page.setDefaultTimeout(timeout);
5145
6398
  await page.goto(url, { waitUntil: "domcontentloaded", timeout });
5146
6399
  if (waitForSelector) {
@@ -5158,20 +6411,39 @@ function registerBrowserTools(registry, workspaceDir) {
5158
6411
  return trimmed;
5159
6412
  }
5160
6413
  case "screenshot": {
5161
- if (!existsSync10(screenshotDir)) {
5162
- mkdirSync8(screenshotDir, { recursive: true });
6414
+ if (!existsSync13(screenshotDir)) {
6415
+ mkdirSync9(screenshotDir, { recursive: true });
5163
6416
  }
5164
6417
  const filename = `${randomUUID5()}.png`;
5165
- const filepath = resolve11(screenshotDir, filename);
6418
+ const filepath = resolve12(screenshotDir, filename);
5166
6419
  await page.screenshot({ path: filepath, fullPage: true });
5167
6420
  return `Screenshot saved: ${filepath}`;
5168
6421
  }
6422
+ case "pdf": {
6423
+ if (!existsSync13(screenshotDir)) {
6424
+ mkdirSync9(screenshotDir, { recursive: true });
6425
+ }
6426
+ const filename = `${randomUUID5()}.pdf`;
6427
+ const filepath = resolve12(screenshotDir, filename);
6428
+ await page.pdf({
6429
+ path: filepath,
6430
+ format: "A4",
6431
+ printBackground: true,
6432
+ margin: { top: "1cm", right: "1cm", bottom: "1cm", left: "1cm" }
6433
+ });
6434
+ return `PDF saved: ${filepath}`;
6435
+ }
5169
6436
  case "click": {
5170
6437
  if (!selector) {
5171
6438
  return "Error: 'selector' parameter is required for click action.";
5172
6439
  }
5173
- await page.click(selector);
5174
- return `Clicked: ${selector}`;
6440
+ if (coordinates) {
6441
+ await page.mouse.click(coordinates.x, coordinates.y);
6442
+ return `Clicked at coordinates: (${coordinates.x}, ${coordinates.y})`;
6443
+ } else {
6444
+ await page.click(selector);
6445
+ return `Clicked: ${selector}`;
6446
+ }
5175
6447
  }
5176
6448
  case "type": {
5177
6449
  if (!selector) {
@@ -5183,6 +6455,169 @@ function registerBrowserTools(registry, workspaceDir) {
5183
6455
  await page.fill(selector, text);
5184
6456
  return `Typed into ${selector}: "${text}"`;
5185
6457
  }
6458
+ case "form_fill": {
6459
+ if (!formData) {
6460
+ return "Error: 'formData' parameter is required for form_fill action.";
6461
+ }
6462
+ const results = [];
6463
+ for (const [sel, value] of Object.entries(formData)) {
6464
+ await page.fill(sel, value);
6465
+ results.push(`${sel}: "${value}"`);
6466
+ }
6467
+ return `Form filled:
6468
+ ${results.join("\n")}`;
6469
+ }
6470
+ case "scroll": {
6471
+ if (coordinates) {
6472
+ await page.evaluate(({ x, y }) => window.scrollTo(x, y), coordinates);
6473
+ return `Scrolled to coordinates: (${coordinates.x}, ${coordinates.y})`;
6474
+ } else if (selector) {
6475
+ await page.locator(selector).scrollIntoViewIfNeeded();
6476
+ return `Scrolled to element: ${selector}`;
6477
+ } else if (scrollDirection) {
6478
+ switch (scrollDirection) {
6479
+ case "up":
6480
+ await page.keyboard.press("PageUp");
6481
+ break;
6482
+ case "down":
6483
+ await page.keyboard.press("PageDown");
6484
+ break;
6485
+ case "top":
6486
+ await page.keyboard.press("Home");
6487
+ break;
6488
+ case "bottom":
6489
+ await page.keyboard.press("End");
6490
+ break;
6491
+ default:
6492
+ await page.mouse.wheel(0, scrollDirection === "down" ? scrollDistance : -scrollDistance);
6493
+ }
6494
+ return `Scrolled ${scrollDirection}`;
6495
+ }
6496
+ return "Error: scroll requires coordinates, selector, or scrollDirection";
6497
+ }
6498
+ case "hover": {
6499
+ if (!selector) {
6500
+ return "Error: 'selector' parameter is required for hover action.";
6501
+ }
6502
+ await page.hover(selector);
6503
+ return `Hovered over: ${selector}`;
6504
+ }
6505
+ case "select": {
6506
+ if (!selector || !text) {
6507
+ return "Error: 'selector' and 'text' parameters are required for select action.";
6508
+ }
6509
+ await page.selectOption(selector, text);
6510
+ return `Selected "${text}" in ${selector}`;
6511
+ }
6512
+ case "upload": {
6513
+ if (!selector || !filePath) {
6514
+ return "Error: 'selector' and 'filePath' parameters are required for upload action.";
6515
+ }
6516
+ const absolutePath = resolve12(workspaceDir, filePath);
6517
+ if (!existsSync13(absolutePath)) {
6518
+ return `Error: File not found: ${absolutePath}`;
6519
+ }
6520
+ await page.setInputFiles(selector, absolutePath);
6521
+ return `Uploaded file ${filePath} to ${selector}`;
6522
+ }
6523
+ case "download": {
6524
+ const downloadPromise = page.waitForEvent("download");
6525
+ if (selector) {
6526
+ await page.click(selector);
6527
+ }
6528
+ const download = await downloadPromise;
6529
+ const filename = download.suggestedFilename() || `download_${randomUUID5()}`;
6530
+ const downloadPath = resolve12(workspaceDir, "downloads", filename);
6531
+ if (!existsSync13(resolve12(workspaceDir, "downloads"))) {
6532
+ mkdirSync9(resolve12(workspaceDir, "downloads"), { recursive: true });
6533
+ }
6534
+ await download.saveAs(downloadPath);
6535
+ return `Downloaded: ${downloadPath}`;
6536
+ }
6537
+ case "wait_for": {
6538
+ if (!waitCondition || !waitValue) {
6539
+ return "Error: 'waitCondition' and 'waitValue' parameters are required for wait_for action.";
6540
+ }
6541
+ switch (waitCondition) {
6542
+ case "selector":
6543
+ await page.waitForSelector(waitValue, { timeout });
6544
+ return `Waited for selector: ${waitValue}`;
6545
+ case "text":
6546
+ await page.waitForFunction(
6547
+ (text2) => document.body.innerText.includes(text2),
6548
+ waitValue,
6549
+ { timeout }
6550
+ );
6551
+ return `Waited for text: ${waitValue}`;
6552
+ case "url":
6553
+ await page.waitForURL(waitValue, { timeout });
6554
+ return `Waited for URL: ${waitValue}`;
6555
+ case "networkidle":
6556
+ await page.waitForLoadState("networkidle", { timeout });
6557
+ return "Waited for network idle";
6558
+ case "timeout":
6559
+ await page.waitForTimeout(parseInt(waitValue));
6560
+ return `Waited for ${waitValue}ms`;
6561
+ default:
6562
+ return `Unknown wait condition: ${waitCondition}`;
6563
+ }
6564
+ }
6565
+ case "network_logs": {
6566
+ const logs = await page.evaluate(() => {
6567
+ return "Network logging not yet implemented - use browser dev tools";
6568
+ });
6569
+ return logs;
6570
+ }
6571
+ case "console_logs": {
6572
+ const logs = [];
6573
+ page.on("console", (msg) => {
6574
+ logs.push(`[${msg.type()}] ${msg.text()}`);
6575
+ });
6576
+ await page.waitForTimeout(1e3);
6577
+ return logs.length > 0 ? logs.join("\n") : "No console logs";
6578
+ }
6579
+ case "cookies": {
6580
+ if (cookieData) {
6581
+ if (cookieData.action === "set") {
6582
+ await context.addCookies([cookieData.cookie]);
6583
+ return `Cookie set: ${cookieData.cookie.name}`;
6584
+ } else if (cookieData.action === "clear") {
6585
+ await context.clearCookies();
6586
+ return "Cookies cleared";
6587
+ }
6588
+ }
6589
+ const cookies = await context.cookies();
6590
+ return JSON.stringify(cookies, null, 2);
6591
+ }
6592
+ case "storage": {
6593
+ if (storageData) {
6594
+ const storageType = storageData.type || "localStorage";
6595
+ if (storageData.action === "set") {
6596
+ await page.evaluate(({ type, key, value }) => {
6597
+ if (type === "localStorage") {
6598
+ localStorage.setItem(key, value);
6599
+ } else {
6600
+ sessionStorage.setItem(key, value);
6601
+ }
6602
+ }, { type: storageType, key: storageData.key, value: storageData.value });
6603
+ return `${storageType} set: ${storageData.key}`;
6604
+ } else if (storageData.action === "clear") {
6605
+ await page.evaluate((type) => {
6606
+ if (type === "localStorage") {
6607
+ localStorage.clear();
6608
+ } else {
6609
+ sessionStorage.clear();
6610
+ }
6611
+ }, storageType);
6612
+ return `${storageType} cleared`;
6613
+ }
6614
+ }
6615
+ const storage = await page.evaluate(() => ({
6616
+ localStorage: Object.fromEntries(Object.entries(localStorage)),
6617
+ sessionStorage: Object.fromEntries(Object.entries(sessionStorage))
6618
+ }));
6619
+ return JSON.stringify(storage, null, 2);
6620
+ }
5186
6621
  case "evaluate": {
5187
6622
  if (!javascript) {
5188
6623
  return "Error: 'javascript' parameter is required for evaluate action.";
@@ -5196,7 +6631,7 @@ function registerBrowserTools(registry, workspaceDir) {
5196
6631
  return str ?? "(no result)";
5197
6632
  }
5198
6633
  default:
5199
- return `Unknown action: ${action}. Use: extract, screenshot, click, type, evaluate.`;
6634
+ return `Unknown action: ${action}. Available actions: extract, screenshot, click, type, evaluate, form_fill, scroll, hover, select, upload, download, pdf, wait_for, network_logs, console_logs, cookies, storage.`;
5200
6635
  }
5201
6636
  } catch (err) {
5202
6637
  return `browse error: ${err.message}`;
@@ -5210,10 +6645,102 @@ function registerBrowserTools(registry, workspaceDir) {
5210
6645
  }
5211
6646
  }
5212
6647
  );
6648
+ registry.register(
6649
+ "browser_session",
6650
+ [
6651
+ "Manage browser sessions for persistent contexts across multiple browse operations.",
6652
+ "Actions:",
6653
+ " list \u2014 list all active browser sessions",
6654
+ " create \u2014 create a new browser session with custom options",
6655
+ " close \u2014 close a specific browser session",
6656
+ " close_all \u2014 close all browser sessions",
6657
+ " info \u2014 get information about a specific session"
6658
+ ].join("\n"),
6659
+ {
6660
+ type: "object",
6661
+ properties: {
6662
+ action: {
6663
+ type: "string",
6664
+ enum: ["list", "create", "close", "close_all", "info"],
6665
+ description: "Session management action."
6666
+ },
6667
+ sessionId: {
6668
+ type: "string",
6669
+ description: "Session ID for create, close, or info actions."
6670
+ },
6671
+ options: {
6672
+ type: "object",
6673
+ description: "Browser options for create action (viewport, userAgent, mobile, etc.)."
6674
+ }
6675
+ },
6676
+ required: ["action"]
6677
+ },
6678
+ async (params) => {
6679
+ const action = params.action;
6680
+ const sessionId = params.sessionId;
6681
+ const options = params.options;
6682
+ try {
6683
+ switch (action) {
6684
+ case "list": {
6685
+ const sessions = Array.from(contextInstances.keys());
6686
+ return sessions.length > 0 ? `Active sessions: ${sessions.join(", ")}` : "No active browser sessions";
6687
+ }
6688
+ case "create": {
6689
+ if (!sessionId) {
6690
+ return "Error: 'sessionId' parameter is required for create action.";
6691
+ }
6692
+ if (contextInstances.has(sessionId)) {
6693
+ return `Error: Session '${sessionId}' already exists.`;
6694
+ }
6695
+ await getBrowserContext(sessionId, options || {});
6696
+ return `Created browser session: ${sessionId}`;
6697
+ }
6698
+ case "close": {
6699
+ if (!sessionId) {
6700
+ return "Error: 'sessionId' parameter is required for close action.";
6701
+ }
6702
+ const context = contextInstances.get(sessionId);
6703
+ if (!context) {
6704
+ return `Error: Session '${sessionId}' not found.`;
6705
+ }
6706
+ await context.close();
6707
+ contextInstances.delete(sessionId);
6708
+ return `Closed browser session: ${sessionId}`;
6709
+ }
6710
+ case "close_all": {
6711
+ const sessionCount = contextInstances.size;
6712
+ for (const [id, context] of contextInstances) {
6713
+ try {
6714
+ await context.close();
6715
+ } catch {
6716
+ }
6717
+ }
6718
+ contextInstances.clear();
6719
+ return `Closed ${sessionCount} browser sessions`;
6720
+ }
6721
+ case "info": {
6722
+ if (!sessionId) {
6723
+ return "Error: 'sessionId' parameter is required for info action.";
6724
+ }
6725
+ const context = contextInstances.get(sessionId);
6726
+ if (!context) {
6727
+ return `Error: Session '${sessionId}' not found.`;
6728
+ }
6729
+ const pages = context.pages();
6730
+ return `Session '${sessionId}': ${pages.length} active pages`;
6731
+ }
6732
+ default:
6733
+ return `Unknown action: ${action}. Available actions: list, create, close, close_all, info.`;
6734
+ }
6735
+ } catch (err) {
6736
+ return `browser_session error: ${err.message}`;
6737
+ }
6738
+ }
6739
+ );
5213
6740
  }
5214
6741
 
5215
6742
  // packages/runtime/src/tools/system.ts
5216
- import { execSync as execSync5 } from "child_process";
6743
+ import { execSync as execSync6 } from "child_process";
5217
6744
  import * as os from "os";
5218
6745
  var MAX_OUTPUT3 = 5e4;
5219
6746
  function truncate(output) {
@@ -5224,7 +6751,7 @@ function truncate(output) {
5224
6751
  return output;
5225
6752
  }
5226
6753
  function exec(cmd, timeoutS = 10) {
5227
- return execSync5(cmd, {
6754
+ return execSync6(cmd, {
5228
6755
  encoding: "utf-8",
5229
6756
  timeout: timeoutS * 1e3,
5230
6757
  maxBuffer: 10 * 1024 * 1024,
@@ -5471,8 +6998,8 @@ function fmt(bytes) {
5471
6998
 
5472
6999
  // packages/runtime/src/tools/http-server.ts
5473
7000
  import { createServer as createServer2 } from "http";
5474
- import { createReadStream, existsSync as existsSync11, statSync as statSync2 } from "fs";
5475
- import { join as join6, extname, resolve as resolve12 } from "path";
7001
+ import { createReadStream, existsSync as existsSync14, statSync as statSync3 } from "fs";
7002
+ import { join as join6, extname, resolve as resolve13 } from "path";
5476
7003
  var MAX_BODY = 5e4;
5477
7004
  var activeServers = /* @__PURE__ */ new Map();
5478
7005
  var MIME_TYPES = {
@@ -5489,16 +7016,16 @@ var MIME_TYPES = {
5489
7016
  function serveStatic(baseDir, req, res) {
5490
7017
  const urlPath = (req.url || "/").split("?")[0];
5491
7018
  let filePath = join6(baseDir, urlPath === "/" ? "index.html" : urlPath);
5492
- if (!resolve12(filePath).startsWith(resolve12(baseDir))) {
7019
+ if (!resolve13(filePath).startsWith(resolve13(baseDir))) {
5493
7020
  res.writeHead(403);
5494
7021
  res.end("Forbidden");
5495
7022
  return true;
5496
7023
  }
5497
- if (!existsSync11(filePath)) return false;
5498
- const stat = statSync2(filePath);
7024
+ if (!existsSync14(filePath)) return false;
7025
+ const stat = statSync3(filePath);
5499
7026
  if (stat.isDirectory()) {
5500
7027
  filePath = join6(filePath, "index.html");
5501
- if (!existsSync11(filePath)) return false;
7028
+ if (!existsSync14(filePath)) return false;
5502
7029
  }
5503
7030
  const ext = extname(filePath);
5504
7031
  const mime = MIME_TYPES[ext] || "application/octet-stream";
@@ -5551,8 +7078,8 @@ function registerHttpTools(registry, workspaceDir) {
5551
7078
  if (activeServers.has(port)) {
5552
7079
  return `Error: A server is already running on port ${port}. Stop it first with http_stop.`;
5553
7080
  }
5554
- const staticDir = directory ? resolve12(workspaceDir, directory) : void 0;
5555
- if (staticDir && !existsSync11(staticDir)) {
7081
+ const staticDir = directory ? resolve13(workspaceDir, directory) : void 0;
7082
+ if (staticDir && !existsSync14(staticDir)) {
5556
7083
  return `Error: Directory not found: ${directory}`;
5557
7084
  }
5558
7085
  return new Promise((resolvePromise) => {
@@ -5688,8 +7215,8 @@ function registerHttpTools(registry, workspaceDir) {
5688
7215
  }
5689
7216
 
5690
7217
  // packages/runtime/src/tools/watch.ts
5691
- import { watch as watch3, existsSync as existsSync12, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "fs";
5692
- import { join as join7, resolve as resolve13 } from "path";
7218
+ import { watch as watch3, existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "fs";
7219
+ import { join as join7, resolve as resolve14 } from "path";
5693
7220
  import { randomUUID as randomUUID6 } from "crypto";
5694
7221
  var activeWatchers = /* @__PURE__ */ new Map();
5695
7222
  function registerWatchTools(registry, workspaceDir, dataDir) {
@@ -5722,15 +7249,15 @@ function registerWatchTools(registry, workspaceDir, dataDir) {
5722
7249
  if (activeWatchers.has(id)) {
5723
7250
  return `Error: A watcher with id '${id}' already exists. Stop it first or use a different id.`;
5724
7251
  }
5725
- const absolutePath = resolve13(workspaceDir, relPath);
5726
- if (!absolutePath.startsWith(resolve13(workspaceDir))) {
7252
+ const absolutePath = resolve14(workspaceDir, relPath);
7253
+ if (!absolutePath.startsWith(resolve14(workspaceDir))) {
5727
7254
  return "Error: Path must be within the workspace directory.";
5728
7255
  }
5729
- if (!existsSync12(absolutePath)) {
7256
+ if (!existsSync15(absolutePath)) {
5730
7257
  return `Error: Path not found: ${relPath}`;
5731
7258
  }
5732
- if (!existsSync12(eventsDir)) {
5733
- mkdirSync9(eventsDir, { recursive: true });
7259
+ if (!existsSync15(eventsDir)) {
7260
+ mkdirSync10(eventsDir, { recursive: true });
5734
7261
  }
5735
7262
  try {
5736
7263
  let debounceTimer = null;
@@ -5746,7 +7273,7 @@ function registerWatchTools(registry, workspaceDir, dataDir) {
5746
7273
  };
5747
7274
  const eventFile = join7(eventsDir, `watch-${id}-${Date.now()}.json`);
5748
7275
  try {
5749
- writeFileSync5(eventFile, JSON.stringify(event, null, 2));
7276
+ writeFileSync6(eventFile, JSON.stringify(event, null, 2));
5750
7277
  } catch {
5751
7278
  }
5752
7279
  });
@@ -5815,13 +7342,13 @@ ${lines.join("\n")}`;
5815
7342
  }
5816
7343
 
5817
7344
  // packages/runtime/src/tools/macos.ts
5818
- import { execSync as execSync6 } from "child_process";
5819
- import { resolve as resolve14, normalize as normalize2 } from "path";
5820
- import { mkdirSync as mkdirSync10, existsSync as existsSync13 } from "fs";
7345
+ import { execSync as execSync7 } from "child_process";
7346
+ import { resolve as resolve15, normalize as normalize2 } from "path";
7347
+ import { mkdirSync as mkdirSync11, existsSync as existsSync16 } from "fs";
5821
7348
  import { randomUUID as randomUUID7 } from "crypto";
5822
7349
  var MAX_OUTPUT4 = 5e4;
5823
7350
  function shellExec(command, timeoutMs) {
5824
- return execSync6(command, {
7351
+ return execSync7(command, {
5825
7352
  timeout: timeoutMs,
5826
7353
  encoding: "utf-8",
5827
7354
  maxBuffer: 10 * 1024 * 1024,
@@ -5932,7 +7459,7 @@ ${output || err.message}`;
5932
7459
  async (params) => {
5933
7460
  const content = params.content;
5934
7461
  try {
5935
- execSync6("pbcopy", {
7462
+ execSync7("pbcopy", {
5936
7463
  input: content,
5937
7464
  timeout: 5e3,
5938
7465
  encoding: "utf-8",
@@ -5995,26 +7522,26 @@ ${output || err.message}`;
5995
7522
  try {
5996
7523
  let savePath;
5997
7524
  if (params.path) {
5998
- savePath = resolve14(workspaceDir, params.path);
7525
+ savePath = resolve15(workspaceDir, params.path);
5999
7526
  const normalizedSave = normalize2(savePath);
6000
7527
  const normalizedWorkspace = normalize2(workspaceDir);
6001
7528
  if (!normalizedSave.startsWith(normalizedWorkspace)) {
6002
7529
  return "Error: path must be within the workspace.";
6003
7530
  }
6004
7531
  } else {
6005
- const screenshotDir = resolve14(workspaceDir, "screenshots");
6006
- if (!existsSync13(screenshotDir)) {
6007
- mkdirSync10(screenshotDir, { recursive: true });
7532
+ const screenshotDir = resolve15(workspaceDir, "screenshots");
7533
+ if (!existsSync16(screenshotDir)) {
7534
+ mkdirSync11(screenshotDir, { recursive: true });
6008
7535
  }
6009
- savePath = resolve14(screenshotDir, `${randomUUID7()}.png`);
7536
+ savePath = resolve15(screenshotDir, `${randomUUID7()}.png`);
6010
7537
  }
6011
- const parentDir = resolve14(savePath, "..");
6012
- if (!existsSync13(parentDir)) {
6013
- mkdirSync10(parentDir, { recursive: true });
7538
+ const parentDir = resolve15(savePath, "..");
7539
+ if (!existsSync16(parentDir)) {
7540
+ mkdirSync11(parentDir, { recursive: true });
6014
7541
  }
6015
7542
  const args = params.region ? `screencapture -i ${escapeShellArg(savePath)}` : `screencapture -x ${escapeShellArg(savePath)}`;
6016
7543
  shellExec(args, 15e3);
6017
- if (!existsSync13(savePath)) {
7544
+ if (!existsSync16(savePath)) {
6018
7545
  return "Screenshot cancelled or failed \u2014 no file was created.";
6019
7546
  }
6020
7547
  return `Screenshot saved: ${savePath}`;
@@ -6032,12 +7559,12 @@ function escapeAppleString(s) {
6032
7559
  }
6033
7560
 
6034
7561
  // packages/runtime/src/tools/data.ts
6035
- import { execSync as execSync7 } from "child_process";
6036
- import { resolve as resolve15, normalize as normalize3, extname as extname2 } from "path";
6037
- import { mkdirSync as mkdirSync11, existsSync as existsSync14 } from "fs";
7562
+ import { execSync as execSync8 } from "child_process";
7563
+ import { resolve as resolve16, normalize as normalize3, extname as extname2 } from "path";
7564
+ import { mkdirSync as mkdirSync12, existsSync as existsSync17 } from "fs";
6038
7565
  var MAX_OUTPUT5 = 5e4;
6039
7566
  function shellExec2(command, cwd, timeoutMs) {
6040
- return execSync7(command, {
7567
+ return execSync8(command, {
6041
7568
  cwd,
6042
7569
  timeout: timeoutMs,
6043
7570
  encoding: "utf-8",
@@ -6047,7 +7574,7 @@ function shellExec2(command, cwd, timeoutMs) {
6047
7574
  });
6048
7575
  }
6049
7576
  function safePath(workspaceDir, filePath) {
6050
- const resolved = resolve15(workspaceDir, filePath);
7577
+ const resolved = resolve16(workspaceDir, filePath);
6051
7578
  const normalizedResolved = normalize3(resolved);
6052
7579
  const normalizedWorkspace = normalize3(workspaceDir);
6053
7580
  if (!normalizedResolved.startsWith(normalizedWorkspace)) {
@@ -6087,7 +7614,7 @@ function registerDataTools(registry, workspaceDir) {
6087
7614
  const queryParams = params.params;
6088
7615
  try {
6089
7616
  const dbPath = safePath(workspaceDir, dbRelative);
6090
- if (!existsSync14(dbPath) && !query.trim().toUpperCase().startsWith("CREATE")) {
7617
+ if (!existsSync17(dbPath) && !query.trim().toUpperCase().startsWith("CREATE")) {
6091
7618
  return `Error: database file not found: ${dbRelative}`;
6092
7619
  }
6093
7620
  let fullQuery = query;
@@ -6148,16 +7675,16 @@ function registerDataTools(registry, workspaceDir) {
6148
7675
  try {
6149
7676
  const sourcePath = safePath(workspaceDir, source);
6150
7677
  const outputPath = safePath(workspaceDir, output);
6151
- if (!existsSync14(sourcePath)) {
7678
+ if (!existsSync17(sourcePath)) {
6152
7679
  return `Error: source not found: ${source}`;
6153
7680
  }
6154
- const outputParent = resolve15(outputPath, "..");
6155
- if (!existsSync14(outputParent)) {
6156
- mkdirSync11(outputParent, { recursive: true });
7681
+ const outputParent = resolve16(outputPath, "..");
7682
+ if (!existsSync17(outputParent)) {
7683
+ mkdirSync12(outputParent, { recursive: true });
6157
7684
  }
6158
7685
  let command;
6159
7686
  if (format === "tar.gz") {
6160
- command = `tar czf ${escapeShellArg2(outputPath)} -C ${escapeShellArg2(resolve15(sourcePath, ".."))} ${escapeShellArg2(sourcePath.split("/").pop())}`;
7687
+ command = `tar czf ${escapeShellArg2(outputPath)} -C ${escapeShellArg2(resolve16(sourcePath, ".."))} ${escapeShellArg2(sourcePath.split("/").pop())}`;
6161
7688
  } else {
6162
7689
  command = `zip -r ${escapeShellArg2(outputPath)} ${escapeShellArg2(source)}`;
6163
7690
  }
@@ -6192,11 +7719,11 @@ function registerDataTools(registry, workspaceDir) {
6192
7719
  try {
6193
7720
  const archivePath = safePath(workspaceDir, archive);
6194
7721
  const destPath = destination ? safePath(workspaceDir, destination) : workspaceDir;
6195
- if (!existsSync14(archivePath)) {
7722
+ if (!existsSync17(archivePath)) {
6196
7723
  return `Error: archive not found: ${archive}`;
6197
7724
  }
6198
- if (!existsSync14(destPath)) {
6199
- mkdirSync11(destPath, { recursive: true });
7725
+ if (!existsSync17(destPath)) {
7726
+ mkdirSync12(destPath, { recursive: true });
6200
7727
  }
6201
7728
  const ext = extname2(archivePath).toLowerCase();
6202
7729
  const isGz = archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz");
@@ -6237,7 +7764,7 @@ ${trimmed}`.trim();
6237
7764
  const filePath = params.path;
6238
7765
  try {
6239
7766
  const pdfPath = safePath(workspaceDir, filePath);
6240
- if (!existsSync14(pdfPath)) {
7767
+ if (!existsSync17(pdfPath)) {
6241
7768
  return `Error: PDF not found: ${filePath}`;
6242
7769
  }
6243
7770
  try {
@@ -6278,8 +7805,8 @@ function truncate2(text) {
6278
7805
  }
6279
7806
 
6280
7807
  // packages/runtime/src/tools/coding-agent.ts
6281
- import { execSync as execSync8 } from "child_process";
6282
- import { resolve as resolve16 } from "path";
7808
+ import { execSync as execSync9 } from "child_process";
7809
+ import { resolve as resolve17 } from "path";
6283
7810
  var MAX_OUTPUT6 = 5e4;
6284
7811
  function registerCodingAgentTools(registry, workspaceDir) {
6285
7812
  registry.register(
@@ -6306,18 +7833,18 @@ function registerCodingAgentTools(registry, workspaceDir) {
6306
7833
  async (params) => {
6307
7834
  const task = params.task;
6308
7835
  const timeoutSeconds = params.timeout || 300;
6309
- const cwd = params.workdir ? resolve16(workspaceDir, params.workdir) : workspaceDir;
7836
+ const cwd = params.workdir ? resolve17(workspaceDir, params.workdir) : workspaceDir;
6310
7837
  const homedir = process.env.HOME || "/root";
6311
7838
  const extendedPath = `${homedir}/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${process.env.PATH}`;
6312
7839
  try {
6313
- execSync8("which claude", { stdio: "ignore", env: { ...process.env, PATH: extendedPath } });
7840
+ execSync9("which claude", { stdio: "ignore", env: { ...process.env, PATH: extendedPath } });
6314
7841
  } catch {
6315
7842
  return "Error: 'claude' CLI not found. Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code";
6316
7843
  }
6317
7844
  const escapedTask = task.replace(/'/g, "'\\''");
6318
7845
  const command = `claude --dangerously-skip-permissions -p '${escapedTask}'`;
6319
7846
  try {
6320
- const output = execSync8(command, {
7847
+ const output = execSync9(command, {
6321
7848
  cwd,
6322
7849
  timeout: timeoutSeconds * 1e3,
6323
7850
  encoding: "utf-8",
@@ -6348,22 +7875,22 @@ ${output || err.message}`;
6348
7875
  }
6349
7876
 
6350
7877
  // packages/runtime/src/tools/register.ts
6351
- import { resolve as resolve17 } from "path";
6352
- import { mkdirSync as mkdirSync12, existsSync as existsSync15 } from "fs";
7878
+ import { resolve as resolve18 } from "path";
7879
+ import { mkdirSync as mkdirSync13, existsSync as existsSync18 } from "fs";
6353
7880
  function registerAllTools(hivemindHome, config) {
6354
7881
  const registry = new ToolRegistry();
6355
7882
  if (config?.enabled === false) {
6356
7883
  return registry;
6357
7884
  }
6358
- const workspaceDir = resolve17(hivemindHome, config?.workspace || "workspace");
6359
- if (!existsSync15(workspaceDir)) {
6360
- mkdirSync12(workspaceDir, { recursive: true });
7885
+ const workspaceDir = resolve18(hivemindHome, config?.workspace || "workspace");
7886
+ if (!existsSync18(workspaceDir)) {
7887
+ mkdirSync13(workspaceDir, { recursive: true });
6361
7888
  }
6362
7889
  registerShellTool(registry, workspaceDir);
6363
7890
  registerFileTools(registry, workspaceDir);
6364
7891
  registerWebTools(registry, { braveApiKey: config?.braveApiKey });
6365
7892
  registerMemoryTools(registry, config?.memoryDaemonUrl || "http://localhost:3434");
6366
- const dataDir = resolve17(hivemindHome, "data");
7893
+ const dataDir = resolve18(hivemindHome, "data");
6367
7894
  registerEventTools(registry, dataDir);
6368
7895
  if (config?.configPath && !process.env.SPAWN_TASK) {
6369
7896
  registerSpawnTools(registry, hivemindHome, dataDir, config.configPath);
@@ -6413,10 +7940,10 @@ function registerMessagingTools(registry, sesame) {
6413
7940
  }
6414
7941
 
6415
7942
  // packages/runtime/src/tools/skills-tools.ts
6416
- import { existsSync as existsSync16, mkdirSync as mkdirSync13, writeFileSync as writeFileSync6, rmSync } from "fs";
6417
- import { resolve as resolve18 } from "path";
7943
+ import { existsSync as existsSync19, mkdirSync as mkdirSync14, writeFileSync as writeFileSync7, rmSync } from "fs";
7944
+ import { resolve as resolve19 } from "path";
6418
7945
  function registerSkillsTools(registry, skillsEngine, workspaceDir) {
6419
- const skillsDir = resolve18(workspaceDir, "skills");
7946
+ const skillsDir = resolve19(workspaceDir, "skills");
6420
7947
  registry.register(
6421
7948
  "skill_list",
6422
7949
  "List all loaded skills with their registered tools and status.",
@@ -6483,11 +8010,11 @@ ${lines.join("\n\n")}`;
6483
8010
  const name = params.name;
6484
8011
  const description = params.description;
6485
8012
  const tools = params.tools;
6486
- const skillDir = resolve18(skillsDir, name);
6487
- if (existsSync16(skillDir)) {
8013
+ const skillDir = resolve19(skillsDir, name);
8014
+ if (existsSync19(skillDir)) {
6488
8015
  return `Error: Skill directory "${name}" already exists. Use skill_reload to update it.`;
6489
8016
  }
6490
- mkdirSync13(skillDir, { recursive: true });
8017
+ mkdirSync14(skillDir, { recursive: true });
6491
8018
  const skillMd = `---
6492
8019
  name: "${name}"
6493
8020
  description: "${description}"
@@ -6497,7 +8024,7 @@ description: "${description}"
6497
8024
 
6498
8025
  ${description}
6499
8026
  `;
6500
- writeFileSync6(resolve18(skillDir, "SKILL.md"), skillMd);
8027
+ writeFileSync7(resolve19(skillDir, "SKILL.md"), skillMd);
6501
8028
  if (tools && tools.length > 0) {
6502
8029
  const toolsDef = {
6503
8030
  tools: tools.map((t) => ({
@@ -6507,7 +8034,7 @@ ${description}
6507
8034
  command: t.command
6508
8035
  }))
6509
8036
  };
6510
- writeFileSync6(resolve18(skillDir, "tools.json"), JSON.stringify(toolsDef, null, 2) + "\n");
8037
+ writeFileSync7(resolve19(skillDir, "tools.json"), JSON.stringify(toolsDef, null, 2) + "\n");
6511
8038
  }
6512
8039
  try {
6513
8040
  await skillsEngine.loadSkill(name);
@@ -6560,8 +8087,8 @@ Path: ${skillDir}`;
6560
8087
  },
6561
8088
  async (params) => {
6562
8089
  const name = params.name;
6563
- const skillDir = resolve18(skillsDir, name);
6564
- if (!existsSync16(skillDir)) {
8090
+ const skillDir = resolve19(skillsDir, name);
8091
+ if (!existsSync19(skillDir)) {
6565
8092
  return `Error: Skill directory "${name}" does not exist.`;
6566
8093
  }
6567
8094
  try {
@@ -6580,13 +8107,13 @@ Path: ${skillDir}`;
6580
8107
  }
6581
8108
 
6582
8109
  // packages/runtime/src/pipeline.ts
6583
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, unlinkSync as unlinkSync3 } from "fs";
6584
- import { resolve as resolve19, dirname as dirname7 } from "path";
8110
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
8111
+ import { resolve as resolve20, dirname as dirname8 } from "path";
6585
8112
  import { fileURLToPath as fileURLToPath3 } from "url";
6586
8113
  var PACKAGE_VERSION = "unknown";
6587
8114
  try {
6588
- const __dirname2 = dirname7(fileURLToPath3(import.meta.url));
6589
- const pkg = JSON.parse(readFileSync12(resolve19(__dirname2, "../package.json"), "utf-8"));
8115
+ const __dirname2 = dirname8(fileURLToPath3(import.meta.url));
8116
+ const pkg = JSON.parse(readFileSync15(resolve20(__dirname2, "../package.json"), "utf-8"));
6590
8117
  PACKAGE_VERSION = pkg.version ?? "unknown";
6591
8118
  } catch {
6592
8119
  }
@@ -6628,7 +8155,7 @@ function startHealthServer(port) {
6628
8155
  return server;
6629
8156
  }
6630
8157
  function writePidFile(path) {
6631
- writeFileSync7(path, String(process.pid));
8158
+ writeFileSync8(path, String(process.pid));
6632
8159
  console.log(`[hivemind] PID file written: ${path}`);
6633
8160
  }
6634
8161
  function cleanupPidFile(path) {
@@ -6669,11 +8196,11 @@ async function startPipeline(configPath) {
6669
8196
  console.log("[hivemind] Global context already exists in memory daemon");
6670
8197
  }
6671
8198
  }
6672
- const requestLogger = new RequestLogger(resolve19(dirname7(configPath), "data", "dashboard.db"));
6673
- startDashboardServer(requestLogger, config.memory);
8199
+ const requestLogger = new RequestLogger(resolve20(dirname8(configPath), "data", "dashboard.db"));
6674
8200
  const agent = new Agent(config);
8201
+ startDashboardServer(requestLogger, config.memory, () => agent.getConversationHistories());
6675
8202
  agent.setRequestLogger(requestLogger);
6676
- const hivemindHome = process.env.HIVEMIND_HOME || resolve19(process.env.HOME || "/root", "hivemind");
8203
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
6677
8204
  const toolRegistry = registerAllTools(hivemindHome, {
6678
8205
  enabled: true,
6679
8206
  workspace: config.agent.workspace || "workspace",
@@ -6681,7 +8208,7 @@ async function startPipeline(configPath) {
6681
8208
  memoryDaemonUrl: config.memory.daemon_url,
6682
8209
  configPath
6683
8210
  });
6684
- const workspaceDir = resolve19(hivemindHome, config.agent.workspace || "workspace");
8211
+ const workspaceDir = resolve20(hivemindHome, config.agent.workspace || "workspace");
6685
8212
  const skillsEngine = new SkillsEngine(workspaceDir, toolRegistry);
6686
8213
  await skillsEngine.loadAll();
6687
8214
  registerSkillsTools(toolRegistry, skillsEngine, workspaceDir);
@@ -6689,7 +8216,50 @@ async function startPipeline(configPath) {
6689
8216
  process.on("exit", () => skillsEngine.stopWatching());
6690
8217
  agent.setToolRegistry(toolRegistry);
6691
8218
  console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
6692
- const dataDir = resolve19(hivemindHome, "data");
8219
+ if (config.auto_debug?.enabled) {
8220
+ const dataDir2 = resolve20(hivemindHome, "data");
8221
+ const autoDebugConfig = config.auto_debug;
8222
+ const logWatcher = new LogWatcher(
8223
+ {
8224
+ log_files: autoDebugConfig.log_files,
8225
+ severity_threshold: autoDebugConfig.severity_threshold,
8226
+ auto_pr: autoDebugConfig.auto_pr,
8227
+ repo: autoDebugConfig.repo,
8228
+ branch_prefix: autoDebugConfig.branch_prefix,
8229
+ max_concurrent_fixes: autoDebugConfig.max_concurrent_fixes,
8230
+ cooldown_minutes: autoDebugConfig.cooldown_minutes,
8231
+ notify_channel: autoDebugConfig.notify_channel
8232
+ },
8233
+ dataDir2
8234
+ );
8235
+ const autoDebugger = new AutoDebugger(
8236
+ {
8237
+ severity_threshold: autoDebugConfig.severity_threshold,
8238
+ auto_pr: autoDebugConfig.auto_pr,
8239
+ repo: autoDebugConfig.repo,
8240
+ branch_prefix: autoDebugConfig.branch_prefix,
8241
+ max_concurrent_fixes: autoDebugConfig.max_concurrent_fixes,
8242
+ cooldown_minutes: autoDebugConfig.cooldown_minutes
8243
+ },
8244
+ logWatcher.getRegistry(),
8245
+ config.llm,
8246
+ hivemindHome
8247
+ );
8248
+ const bgContext = {
8249
+ memoryClient: memory,
8250
+ workspacePath: hivemindHome,
8251
+ currentContext: "auto-debug",
8252
+ agentId: config.agent.name,
8253
+ sharedState: /* @__PURE__ */ new Map()
8254
+ };
8255
+ const processManager = new ProcessManager(bgContext);
8256
+ processManager.register(logWatcher);
8257
+ processManager.register(autoDebugger);
8258
+ processManager.start();
8259
+ process.on("exit", () => processManager.stop());
8260
+ console.log("[hivemind] Auto-debug processors started");
8261
+ }
8262
+ const dataDir = resolve20(hivemindHome, "data");
6693
8263
  if (config.sesame.api_key) {
6694
8264
  await startSesameLoop(config, agent, toolRegistry, dataDir);
6695
8265
  } else {
@@ -6863,8 +8433,8 @@ ${response.content}
6863
8433
  console.error("Error:", err.message);
6864
8434
  }
6865
8435
  });
6866
- return new Promise((resolve20) => {
6867
- rl.on("close", resolve20);
8436
+ return new Promise((resolve21) => {
8437
+ rl.on("close", resolve21);
6868
8438
  });
6869
8439
  }
6870
8440
  async function runSpawnTask(config, configPath) {
@@ -6875,7 +8445,7 @@ async function runSpawnTask(config, configPath) {
6875
8445
  const spawnDir = process.env.SPAWN_DIR;
6876
8446
  console.log(`[spawn] Sub-agent starting (id: ${spawnId}, context: ${context})`);
6877
8447
  const agent = new Agent(config, context);
6878
- const hivemindHome = process.env.HIVEMIND_HOME || resolve19(process.env.HOME || "/root", "hivemind");
8448
+ const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
6879
8449
  const toolRegistry = registerAllTools(hivemindHome, {
6880
8450
  enabled: true,
6881
8451
  workspace: config.agent.workspace || "workspace",
@@ -6888,7 +8458,7 @@ async function runSpawnTask(config, configPath) {
6888
8458
  const result = response.content;
6889
8459
  console.log(`[spawn] Task completed (context: ${response.context})`);
6890
8460
  if (spawnDir) {
6891
- writeFileSync7(resolve19(spawnDir, "result.txt"), result);
8461
+ writeFileSync8(resolve20(spawnDir, "result.txt"), result);
6892
8462
  }
6893
8463
  if (channelId && config.sesame.api_key) {
6894
8464
  try {
@@ -6905,7 +8475,7 @@ async function runSpawnTask(config, configPath) {
6905
8475
  const errorMsg = `[SPAWN ERROR] ${err.message}`;
6906
8476
  console.error(`[spawn] ${errorMsg}`);
6907
8477
  if (spawnDir) {
6908
- writeFileSync7(resolve19(spawnDir, "result.txt"), errorMsg);
8478
+ writeFileSync8(resolve20(spawnDir, "result.txt"), errorMsg);
6909
8479
  }
6910
8480
  process.exitCode = 1;
6911
8481
  }
@@ -6936,20 +8506,20 @@ var WorkerServer = class {
6936
8506
  }
6937
8507
  /** Start listening. */
6938
8508
  async start() {
6939
- return new Promise((resolve20, reject) => {
8509
+ return new Promise((resolve21, reject) => {
6940
8510
  this.server = createServer4((req, res) => this.handleRequest(req, res));
6941
8511
  this.server.on("error", reject);
6942
- this.server.listen(this.port, () => resolve20());
8512
+ this.server.listen(this.port, () => resolve21());
6943
8513
  });
6944
8514
  }
6945
8515
  /** Stop the server. */
6946
8516
  async stop() {
6947
- return new Promise((resolve20) => {
8517
+ return new Promise((resolve21) => {
6948
8518
  if (!this.server) {
6949
- resolve20();
8519
+ resolve21();
6950
8520
  return;
6951
8521
  }
6952
- this.server.close(() => resolve20());
8522
+ this.server.close(() => resolve21());
6953
8523
  });
6954
8524
  }
6955
8525
  getPort() {
@@ -6982,7 +8552,7 @@ var WorkerServer = class {
6982
8552
  return this.handleHealth(res);
6983
8553
  }
6984
8554
  if (method === "POST" && path === "/assign") {
6985
- const body = await readBody(req);
8555
+ const body = await readBody2(req);
6986
8556
  return this.handleAssign(body, res);
6987
8557
  }
6988
8558
  if (method === "DELETE" && path.startsWith("/assign/")) {
@@ -6993,7 +8563,7 @@ var WorkerServer = class {
6993
8563
  return this.handleStatus(res);
6994
8564
  }
6995
8565
  if (method === "POST" && path === "/sync/push") {
6996
- const body = await readBody(req);
8566
+ const body = await readBody2(req);
6997
8567
  return this.handleSyncPush(body, res);
6998
8568
  }
6999
8569
  sendJson(res, 404, { error: "Not found" });
@@ -7071,11 +8641,11 @@ var WorkerServer = class {
7071
8641
  sendJson(res, 200, result);
7072
8642
  }
7073
8643
  };
7074
- function readBody(req) {
7075
- return new Promise((resolve20, reject) => {
8644
+ function readBody2(req) {
8645
+ return new Promise((resolve21, reject) => {
7076
8646
  const chunks = [];
7077
8647
  req.on("data", (chunk) => chunks.push(chunk));
7078
- req.on("end", () => resolve20(Buffer.concat(chunks).toString("utf-8")));
8648
+ req.on("end", () => resolve21(Buffer.concat(chunks).toString("utf-8")));
7079
8649
  req.on("error", reject);
7080
8650
  });
7081
8651
  }
@@ -7342,6 +8912,8 @@ async function startWorker(config) {
7342
8912
  }
7343
8913
 
7344
8914
  export {
8915
+ setLogLevel,
8916
+ createLogger,
7345
8917
  getClaudeCodeOAuthToken,
7346
8918
  LLMClient,
7347
8919
  MemoryClient,
@@ -7362,6 +8934,10 @@ export {
7362
8934
  SesameClient2,
7363
8935
  HEALTH_PATH,
7364
8936
  SkillsEngine,
8937
+ hashError,
8938
+ ErrorRegistry,
8939
+ LogWatcher,
8940
+ AutoDebugger,
7365
8941
  startPipeline,
7366
8942
  PRIMARY_ROUTES,
7367
8943
  WORKER_ROUTES,
@@ -7411,4 +8987,4 @@ smol-toml/dist/index.js:
7411
8987
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
7412
8988
  *)
7413
8989
  */
7414
- //# sourceMappingURL=chunk-OG6GPQDK.js.map
8990
+ //# sourceMappingURL=chunk-M3A2WRXM.js.map