@sesamespace/hivemind 0.8.11 → 0.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTO-DEBUG-DESIGN.md +267 -0
- package/config/default.toml +18 -0
- package/dist/{chunk-A5LMEJIN.js → chunk-4Y7A25UG.js} +64 -17
- package/dist/chunk-4Y7A25UG.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/{chunk-SMUG64N7.js → chunk-HTLHMXAL.js} +2 -2
- package/dist/{chunk-F6EMICWP.js → chunk-MLY4VFOO.js} +3 -3
- package/dist/{chunk-XBJGLZ7V.js → chunk-NSTTILSN.js} +2 -2
- package/dist/{chunk-OVRYYS5I.js → chunk-PFZO67E2.js} +2 -2
- package/dist/{chunk-OG6GPQDK.js → chunk-ZM7RK5YV.js} +1206 -153
- package/dist/chunk-ZM7RK5YV.js.map +1 -0
- package/dist/commands/fleet.js +4 -3
- package/dist/commands/init.js +4 -3
- package/dist/commands/service.js +1 -0
- package/dist/commands/start.js +4 -3
- package/dist/commands/upgrade.js +1 -0
- package/dist/commands/watchdog.js +4 -3
- package/dist/index.js +15 -2
- package/dist/main.js +7 -6
- package/dist/main.js.map +1 -1
- package/dist/start.js +2 -1
- package/dist/start.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-A5LMEJIN.js.map +0 -1
- package/dist/chunk-OG6GPQDK.js.map +0 -1
- /package/dist/{chunk-SMUG64N7.js.map → chunk-HTLHMXAL.js.map} +0 -0
- /package/dist/{chunk-F6EMICWP.js.map → chunk-MLY4VFOO.js.map} +0 -0
- /package/dist/{chunk-XBJGLZ7V.js.map → chunk-NSTTILSN.js.map} +0 -0
- /package/dist/{chunk-OVRYYS5I.js.map → chunk-PFZO67E2.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) {
|
|
@@ -3012,7 +3045,7 @@ var SesameClient = class {
|
|
|
3012
3045
|
* Connect to the real-time WebSocket gateway.
|
|
3013
3046
|
*/
|
|
3014
3047
|
connect() {
|
|
3015
|
-
return new Promise((
|
|
3048
|
+
return new Promise((resolve21, reject) => {
|
|
3016
3049
|
try {
|
|
3017
3050
|
this.ws = new WebSocket(`${this.config.wsUrl}/v1/connect`);
|
|
3018
3051
|
this.ws.on("open", () => {
|
|
@@ -3026,7 +3059,7 @@ var SesameClient = class {
|
|
|
3026
3059
|
this.authenticated = true;
|
|
3027
3060
|
this.startHeartbeat(event.heartbeatIntervalMs ?? 3e4);
|
|
3028
3061
|
this.sendReplay();
|
|
3029
|
-
|
|
3062
|
+
resolve21();
|
|
3030
3063
|
return;
|
|
3031
3064
|
}
|
|
3032
3065
|
if (event.type === "pong")
|
|
@@ -3585,6 +3618,977 @@ var SkillsEngine = class {
|
|
|
3585
3618
|
}
|
|
3586
3619
|
};
|
|
3587
3620
|
|
|
3621
|
+
// packages/runtime/src/processors/error-registry.ts
|
|
3622
|
+
import { readFileSync as readFileSync7, writeFileSync, mkdirSync as mkdirSync4, existsSync as existsSync6 } from "fs";
|
|
3623
|
+
import { createHash } from "crypto";
|
|
3624
|
+
import { dirname as dirname4 } from "path";
|
|
3625
|
+
var log = createLogger("error-registry");
|
|
3626
|
+
function normalizeForHash(msg, stack) {
|
|
3627
|
+
let text = msg;
|
|
3628
|
+
if (stack) {
|
|
3629
|
+
text += "\n" + stack;
|
|
3630
|
+
}
|
|
3631
|
+
text = text.replace(/:\d+:\d+/g, ":X:X");
|
|
3632
|
+
text = text.replace(/\d{4}-\d{2}-\d{2}T[\d:.Z]+/g, "T");
|
|
3633
|
+
text = text.replace(/\b\d{4,}\b/g, "N");
|
|
3634
|
+
return text;
|
|
3635
|
+
}
|
|
3636
|
+
function hashError(msg, stack) {
|
|
3637
|
+
const normalized = normalizeForHash(msg, stack);
|
|
3638
|
+
return createHash("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
3639
|
+
}
|
|
3640
|
+
function scoreSeverity(entry) {
|
|
3641
|
+
let score = 0;
|
|
3642
|
+
if (entry.level === "crash") score += 5;
|
|
3643
|
+
const firstMs = new Date(entry.firstSeen).getTime();
|
|
3644
|
+
const lastMs = new Date(entry.lastSeen).getTime();
|
|
3645
|
+
const spanMinutes = (lastMs - firstMs) / 6e4;
|
|
3646
|
+
if (entry.totalOccurrences >= 3 && spanMinutes < 5) score += 3;
|
|
3647
|
+
const spanHours = Math.max(spanMinutes / 60, 1 / 60);
|
|
3648
|
+
if (entry.totalOccurrences / spanHours > 10) score += 2;
|
|
3649
|
+
if (/sesame/i.test(entry.message) || /sesame/i.test(entry.stackTrace ?? "")) {
|
|
3650
|
+
score += 2;
|
|
3651
|
+
}
|
|
3652
|
+
if (/memory/i.test(entry.message) || /memory/i.test(entry.stackTrace ?? "")) {
|
|
3653
|
+
score += 1;
|
|
3654
|
+
}
|
|
3655
|
+
if (entry.status === "fix-submitted" || entry.status === "wont-fix") {
|
|
3656
|
+
score -= 10;
|
|
3657
|
+
}
|
|
3658
|
+
return score;
|
|
3659
|
+
}
|
|
3660
|
+
var ErrorRegistry = class {
|
|
3661
|
+
filePath;
|
|
3662
|
+
entries = /* @__PURE__ */ new Map();
|
|
3663
|
+
constructor(dataDir) {
|
|
3664
|
+
this.filePath = `${dataDir}/error-registry.json`;
|
|
3665
|
+
this.load();
|
|
3666
|
+
}
|
|
3667
|
+
// ---- persistence -------------------------------------------------------
|
|
3668
|
+
load() {
|
|
3669
|
+
try {
|
|
3670
|
+
if (!existsSync6(this.filePath)) return;
|
|
3671
|
+
const raw = readFileSync7(this.filePath, "utf-8");
|
|
3672
|
+
const data = JSON.parse(raw);
|
|
3673
|
+
if (data.version === 1 && data.errors) {
|
|
3674
|
+
for (const [id, entry] of Object.entries(data.errors)) {
|
|
3675
|
+
this.entries.set(id, entry);
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
log.debug("loaded error registry", { count: this.entries.size });
|
|
3679
|
+
} catch (err) {
|
|
3680
|
+
log.warn("failed to load error registry, starting fresh", {
|
|
3681
|
+
error: err.message
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
save() {
|
|
3686
|
+
try {
|
|
3687
|
+
const dir = dirname4(this.filePath);
|
|
3688
|
+
if (!existsSync6(dir)) mkdirSync4(dir, { recursive: true });
|
|
3689
|
+
const data = {
|
|
3690
|
+
version: 1,
|
|
3691
|
+
errors: Object.fromEntries(this.entries)
|
|
3692
|
+
};
|
|
3693
|
+
writeFileSync(this.filePath, JSON.stringify(data, null, 2));
|
|
3694
|
+
} catch (err) {
|
|
3695
|
+
log.error("failed to save error registry", {
|
|
3696
|
+
error: err.message
|
|
3697
|
+
});
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
// ---- public API --------------------------------------------------------
|
|
3701
|
+
/** Record an error event, creating or updating the registry entry. */
|
|
3702
|
+
recordError(event) {
|
|
3703
|
+
const existing = this.entries.get(event.id);
|
|
3704
|
+
if (existing) {
|
|
3705
|
+
existing.lastSeen = event.timestamp.toISOString();
|
|
3706
|
+
existing.totalOccurrences += event.occurrences;
|
|
3707
|
+
if (event.level === "crash" && existing.level !== "crash") {
|
|
3708
|
+
existing.level = "crash";
|
|
3709
|
+
}
|
|
3710
|
+
existing.severity = scoreSeverity(existing);
|
|
3711
|
+
this.save();
|
|
3712
|
+
log.debug("updated error entry", {
|
|
3713
|
+
id: event.id,
|
|
3714
|
+
total: existing.totalOccurrences,
|
|
3715
|
+
severity: existing.severity
|
|
3716
|
+
});
|
|
3717
|
+
return existing;
|
|
3718
|
+
}
|
|
3719
|
+
const entry = {
|
|
3720
|
+
id: event.id,
|
|
3721
|
+
message: event.message,
|
|
3722
|
+
source: event.source,
|
|
3723
|
+
level: event.level,
|
|
3724
|
+
stackTrace: event.stackTrace,
|
|
3725
|
+
firstSeen: event.timestamp.toISOString(),
|
|
3726
|
+
lastSeen: event.timestamp.toISOString(),
|
|
3727
|
+
totalOccurrences: event.occurrences,
|
|
3728
|
+
status: "new",
|
|
3729
|
+
severity: 0
|
|
3730
|
+
};
|
|
3731
|
+
entry.severity = scoreSeverity(entry);
|
|
3732
|
+
this.entries.set(event.id, entry);
|
|
3733
|
+
this.save();
|
|
3734
|
+
log.info("new error registered", {
|
|
3735
|
+
id: event.id,
|
|
3736
|
+
message: event.message.slice(0, 120),
|
|
3737
|
+
severity: entry.severity
|
|
3738
|
+
});
|
|
3739
|
+
return entry;
|
|
3740
|
+
}
|
|
3741
|
+
/** Return errors above the severity threshold that are actionable (new or investigating). */
|
|
3742
|
+
getActionable(threshold) {
|
|
3743
|
+
const results = [];
|
|
3744
|
+
for (const entry of this.entries.values()) {
|
|
3745
|
+
entry.severity = scoreSeverity(entry);
|
|
3746
|
+
if (entry.severity >= threshold && (entry.status === "new" || entry.status === "investigating")) {
|
|
3747
|
+
results.push(entry);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
results.sort((a, b) => b.severity - a.severity);
|
|
3751
|
+
return results;
|
|
3752
|
+
}
|
|
3753
|
+
/** Update the status (and optional PR url) for an error. */
|
|
3754
|
+
updateStatus(id, status, prUrl) {
|
|
3755
|
+
const entry = this.entries.get(id);
|
|
3756
|
+
if (!entry) return;
|
|
3757
|
+
entry.status = status;
|
|
3758
|
+
if (prUrl) entry.prUrl = prUrl;
|
|
3759
|
+
entry.severity = scoreSeverity(entry);
|
|
3760
|
+
this.save();
|
|
3761
|
+
log.info("error status updated", { id, status, prUrl });
|
|
3762
|
+
}
|
|
3763
|
+
/** Remove entries older than the given date. */
|
|
3764
|
+
cleanup(olderThan) {
|
|
3765
|
+
let removed = 0;
|
|
3766
|
+
for (const [id, entry] of this.entries) {
|
|
3767
|
+
if (new Date(entry.lastSeen) < olderThan) {
|
|
3768
|
+
this.entries.delete(id);
|
|
3769
|
+
removed++;
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
if (removed > 0) {
|
|
3773
|
+
this.save();
|
|
3774
|
+
log.info("cleaned up old errors", { removed });
|
|
3775
|
+
}
|
|
3776
|
+
return removed;
|
|
3777
|
+
}
|
|
3778
|
+
/** Get a single entry by id. */
|
|
3779
|
+
get(id) {
|
|
3780
|
+
return this.entries.get(id);
|
|
3781
|
+
}
|
|
3782
|
+
/** Total number of tracked errors. */
|
|
3783
|
+
get size() {
|
|
3784
|
+
return this.entries.size;
|
|
3785
|
+
}
|
|
3786
|
+
};
|
|
3787
|
+
|
|
3788
|
+
// packages/runtime/src/processors/log-watcher.ts
|
|
3789
|
+
import { readFileSync as readFileSync8, statSync as statSync2, existsSync as existsSync7 } from "fs";
|
|
3790
|
+
|
|
3791
|
+
// packages/runtime/src/memory/background-processor.ts
|
|
3792
|
+
import { EventEmitter } from "events";
|
|
3793
|
+
var BackgroundProcess = class extends EventEmitter {
|
|
3794
|
+
name;
|
|
3795
|
+
interval;
|
|
3796
|
+
enabled = true;
|
|
3797
|
+
timer;
|
|
3798
|
+
running = false;
|
|
3799
|
+
lastRun;
|
|
3800
|
+
lastResult;
|
|
3801
|
+
constructor(name, interval) {
|
|
3802
|
+
super();
|
|
3803
|
+
this.name = name;
|
|
3804
|
+
this.interval = interval;
|
|
3805
|
+
}
|
|
3806
|
+
/**
|
|
3807
|
+
* Determine if this process should run now
|
|
3808
|
+
*/
|
|
3809
|
+
async shouldRun(context) {
|
|
3810
|
+
if (!this.enabled || this.running) return false;
|
|
3811
|
+
return true;
|
|
3812
|
+
}
|
|
3813
|
+
/**
|
|
3814
|
+
* Start the background process
|
|
3815
|
+
*/
|
|
3816
|
+
start(context) {
|
|
3817
|
+
if (this.timer) return;
|
|
3818
|
+
this.runOnce(context);
|
|
3819
|
+
this.timer = setInterval(() => {
|
|
3820
|
+
this.runOnce(context);
|
|
3821
|
+
}, this.interval);
|
|
3822
|
+
}
|
|
3823
|
+
/**
|
|
3824
|
+
* Stop the background process
|
|
3825
|
+
*/
|
|
3826
|
+
stop() {
|
|
3827
|
+
if (this.timer) {
|
|
3828
|
+
clearInterval(this.timer);
|
|
3829
|
+
this.timer = void 0;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
/**
|
|
3833
|
+
* Run the process once
|
|
3834
|
+
*/
|
|
3835
|
+
async runOnce(context) {
|
|
3836
|
+
if (!await this.shouldRun(context)) return;
|
|
3837
|
+
this.running = true;
|
|
3838
|
+
const startTime2 = Date.now();
|
|
3839
|
+
try {
|
|
3840
|
+
this.emit("start", { name: this.name, time: /* @__PURE__ */ new Date() });
|
|
3841
|
+
const result = await this.process(context);
|
|
3842
|
+
this.lastResult = result;
|
|
3843
|
+
this.lastRun = /* @__PURE__ */ new Date();
|
|
3844
|
+
if (result.nextRunIn && result.nextRunIn !== this.interval) {
|
|
3845
|
+
this.stop();
|
|
3846
|
+
this.interval = result.nextRunIn;
|
|
3847
|
+
this.start(context);
|
|
3848
|
+
}
|
|
3849
|
+
this.emit("complete", {
|
|
3850
|
+
name: this.name,
|
|
3851
|
+
duration: Date.now() - startTime2,
|
|
3852
|
+
result
|
|
3853
|
+
});
|
|
3854
|
+
if (result.errors.length > 0) {
|
|
3855
|
+
this.emit("error", {
|
|
3856
|
+
name: this.name,
|
|
3857
|
+
errors: result.errors
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
} catch (error) {
|
|
3861
|
+
this.emit("error", {
|
|
3862
|
+
name: this.name,
|
|
3863
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3864
|
+
});
|
|
3865
|
+
} finally {
|
|
3866
|
+
this.running = false;
|
|
3867
|
+
}
|
|
3868
|
+
}
|
|
3869
|
+
/**
|
|
3870
|
+
* Get process status
|
|
3871
|
+
*/
|
|
3872
|
+
getStatus() {
|
|
3873
|
+
return {
|
|
3874
|
+
name: this.name,
|
|
3875
|
+
enabled: this.enabled,
|
|
3876
|
+
running: this.running,
|
|
3877
|
+
interval: this.interval,
|
|
3878
|
+
lastRun: this.lastRun,
|
|
3879
|
+
lastResult: this.lastResult
|
|
3880
|
+
};
|
|
3881
|
+
}
|
|
3882
|
+
};
|
|
3883
|
+
var ProcessManager = class extends EventEmitter {
|
|
3884
|
+
processes = /* @__PURE__ */ new Map();
|
|
3885
|
+
context;
|
|
3886
|
+
started = false;
|
|
3887
|
+
constructor(context) {
|
|
3888
|
+
super();
|
|
3889
|
+
this.context = context;
|
|
3890
|
+
}
|
|
3891
|
+
/**
|
|
3892
|
+
* Register a background process
|
|
3893
|
+
*/
|
|
3894
|
+
register(process2) {
|
|
3895
|
+
if (this.processes.has(process2.name)) {
|
|
3896
|
+
throw new Error(`Process ${process2.name} already registered`);
|
|
3897
|
+
}
|
|
3898
|
+
process2.on("start", (data) => this.emit("process:start", data));
|
|
3899
|
+
process2.on("complete", (data) => this.emit("process:complete", data));
|
|
3900
|
+
process2.on("error", (data) => this.emit("process:error", data));
|
|
3901
|
+
this.processes.set(process2.name, process2);
|
|
3902
|
+
if (this.started) {
|
|
3903
|
+
process2.start(this.context);
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
/**
|
|
3907
|
+
* Start all registered processes
|
|
3908
|
+
*/
|
|
3909
|
+
start() {
|
|
3910
|
+
if (this.started) return;
|
|
3911
|
+
for (const process2 of this.processes.values()) {
|
|
3912
|
+
process2.start(this.context);
|
|
3913
|
+
}
|
|
3914
|
+
this.started = true;
|
|
3915
|
+
this.emit("started");
|
|
3916
|
+
}
|
|
3917
|
+
/**
|
|
3918
|
+
* Stop all processes
|
|
3919
|
+
*/
|
|
3920
|
+
stop() {
|
|
3921
|
+
if (!this.started) return;
|
|
3922
|
+
for (const process2 of this.processes.values()) {
|
|
3923
|
+
process2.stop();
|
|
3924
|
+
}
|
|
3925
|
+
this.started = false;
|
|
3926
|
+
this.emit("stopped");
|
|
3927
|
+
}
|
|
3928
|
+
/**
|
|
3929
|
+
* Get status of all processes
|
|
3930
|
+
*/
|
|
3931
|
+
getStatus() {
|
|
3932
|
+
const statuses = {};
|
|
3933
|
+
for (const [name, process2] of this.processes) {
|
|
3934
|
+
statuses[name] = process2.getStatus();
|
|
3935
|
+
}
|
|
3936
|
+
return {
|
|
3937
|
+
started: this.started,
|
|
3938
|
+
processCount: this.processes.size,
|
|
3939
|
+
processes: statuses
|
|
3940
|
+
};
|
|
3941
|
+
}
|
|
3942
|
+
/**
|
|
3943
|
+
* Enable/disable a specific process
|
|
3944
|
+
*/
|
|
3945
|
+
setProcessEnabled(name, enabled) {
|
|
3946
|
+
const process2 = this.processes.get(name);
|
|
3947
|
+
if (!process2) {
|
|
3948
|
+
throw new Error(`Process ${name} not found`);
|
|
3949
|
+
}
|
|
3950
|
+
process2.enabled = enabled;
|
|
3951
|
+
if (!enabled) {
|
|
3952
|
+
process2.stop();
|
|
3953
|
+
} else if (this.started) {
|
|
3954
|
+
process2.start(this.context);
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3957
|
+
/**
|
|
3958
|
+
* Manually trigger a process run
|
|
3959
|
+
*/
|
|
3960
|
+
async runProcess(name) {
|
|
3961
|
+
const process2 = this.processes.get(name);
|
|
3962
|
+
if (!process2) {
|
|
3963
|
+
throw new Error(`Process ${name} not found`);
|
|
3964
|
+
}
|
|
3965
|
+
return process2.process(this.context);
|
|
3966
|
+
}
|
|
3967
|
+
};
|
|
3968
|
+
|
|
3969
|
+
// packages/runtime/src/processors/log-watcher.ts
|
|
3970
|
+
var log2 = createLogger("log-watcher");
|
|
3971
|
+
var STACK_TRACE_START = /^(?:Error|TypeError|RangeError|ReferenceError|SyntaxError|URIError|EvalError|AggregateError|UnhandledPromiseRejectionWarning|Uncaught):/;
|
|
3972
|
+
var STACK_FRAME = /^\s+at\s+/;
|
|
3973
|
+
var CRASH_INDICATOR = /(?:exit\s+code\s+[1-9]\d*|SIGKILL|SIGTERM|SIGSEGV|SIGABRT|uncaughtException|unhandledRejection)/i;
|
|
3974
|
+
var WARNING_DEDUP_WINDOW_MS = 5 * 60 * 1e3;
|
|
3975
|
+
var WARNING_THRESHOLD = 3;
|
|
3976
|
+
var LogWatcher = class extends BackgroundProcess {
|
|
3977
|
+
config;
|
|
3978
|
+
registry;
|
|
3979
|
+
tailPositions = /* @__PURE__ */ new Map();
|
|
3980
|
+
warningAccumulators = /* @__PURE__ */ new Map();
|
|
3981
|
+
positionsFile;
|
|
3982
|
+
constructor(config, dataDir) {
|
|
3983
|
+
super("log-watcher", 1e4);
|
|
3984
|
+
this.config = config;
|
|
3985
|
+
this.registry = new ErrorRegistry(dataDir);
|
|
3986
|
+
this.positionsFile = `${dataDir}/log-watcher-positions.json`;
|
|
3987
|
+
this.loadPositions();
|
|
3988
|
+
}
|
|
3989
|
+
/** Expose the registry so the auto-debugger can consume it. */
|
|
3990
|
+
getRegistry() {
|
|
3991
|
+
return this.registry;
|
|
3992
|
+
}
|
|
3993
|
+
// ---- BackgroundProcess implementation ------------------------------------
|
|
3994
|
+
async process(context) {
|
|
3995
|
+
const errors = [];
|
|
3996
|
+
let itemsProcessed = 0;
|
|
3997
|
+
for (const logFile of this.config.log_files) {
|
|
3998
|
+
try {
|
|
3999
|
+
const result = this.processLogFile(logFile, context);
|
|
4000
|
+
itemsProcessed += result.linesRead;
|
|
4001
|
+
} catch (err) {
|
|
4002
|
+
errors.push(`${logFile}: ${err.message}`);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
this.pruneWarnings();
|
|
4006
|
+
this.savePositions();
|
|
4007
|
+
this.registry.cleanup(new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3));
|
|
4008
|
+
return { itemsProcessed, errors };
|
|
4009
|
+
}
|
|
4010
|
+
// ---- log tailing ---------------------------------------------------------
|
|
4011
|
+
processLogFile(logFile, context) {
|
|
4012
|
+
if (!existsSync7(logFile)) return { linesRead: 0 };
|
|
4013
|
+
let fileSize;
|
|
4014
|
+
try {
|
|
4015
|
+
fileSize = statSync2(logFile).size;
|
|
4016
|
+
} catch {
|
|
4017
|
+
return { linesRead: 0 };
|
|
4018
|
+
}
|
|
4019
|
+
const lastPos = this.tailPositions.get(logFile) ?? 0;
|
|
4020
|
+
if (fileSize < lastPos) {
|
|
4021
|
+
this.tailPositions.set(logFile, 0);
|
|
4022
|
+
return this.processLogFile(logFile, context);
|
|
4023
|
+
}
|
|
4024
|
+
if (fileSize === lastPos) return { linesRead: 0 };
|
|
4025
|
+
let chunk;
|
|
4026
|
+
try {
|
|
4027
|
+
const buf = Buffer.alloc(fileSize - lastPos);
|
|
4028
|
+
const fd = __require("fs").openSync(logFile, "r");
|
|
4029
|
+
try {
|
|
4030
|
+
__require("fs").readSync(fd, buf, 0, buf.length, lastPos);
|
|
4031
|
+
} finally {
|
|
4032
|
+
__require("fs").closeSync(fd);
|
|
4033
|
+
}
|
|
4034
|
+
chunk = buf.toString("utf-8");
|
|
4035
|
+
} catch {
|
|
4036
|
+
return { linesRead: 0 };
|
|
4037
|
+
}
|
|
4038
|
+
this.tailPositions.set(logFile, fileSize);
|
|
4039
|
+
const lines = chunk.split("\n").filter((l) => l.length > 0);
|
|
4040
|
+
if (lines.length === 0) return { linesRead: 0 };
|
|
4041
|
+
let baseLineNumber;
|
|
4042
|
+
try {
|
|
4043
|
+
const fullContent = readFileSync8(logFile, "utf-8");
|
|
4044
|
+
const preContent = fullContent.substring(0, lastPos);
|
|
4045
|
+
baseLineNumber = preContent.split("\n").length;
|
|
4046
|
+
} catch {
|
|
4047
|
+
baseLineNumber = 1;
|
|
4048
|
+
}
|
|
4049
|
+
this.detectErrors(lines, logFile, baseLineNumber, context);
|
|
4050
|
+
return { linesRead: lines.length };
|
|
4051
|
+
}
|
|
4052
|
+
// ---- error detection -----------------------------------------------------
|
|
4053
|
+
detectErrors(lines, logFile, baseLineNumber, context) {
|
|
4054
|
+
const source = this.inferSource(logFile);
|
|
4055
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4056
|
+
const line = lines[i];
|
|
4057
|
+
const lineNumber = baseLineNumber + i;
|
|
4058
|
+
const jsonError = this.tryParseJsonError(line);
|
|
4059
|
+
if (jsonError) {
|
|
4060
|
+
this.emitError({
|
|
4061
|
+
message: jsonError.msg,
|
|
4062
|
+
stackTrace: jsonError.stack,
|
|
4063
|
+
level: jsonError.level === "crash" ? "crash" : "error",
|
|
4064
|
+
source,
|
|
4065
|
+
logFile,
|
|
4066
|
+
lineNumber,
|
|
4067
|
+
context: this.getSurroundingLines(lines, i)
|
|
4068
|
+
});
|
|
4069
|
+
continue;
|
|
4070
|
+
}
|
|
4071
|
+
if (STACK_TRACE_START.test(line)) {
|
|
4072
|
+
const { message, stack } = this.collectStackTrace(lines, i);
|
|
4073
|
+
const level = /uncaught|unhandled/i.test(line) ? "crash" : "error";
|
|
4074
|
+
this.emitError({
|
|
4075
|
+
message,
|
|
4076
|
+
stackTrace: stack,
|
|
4077
|
+
level,
|
|
4078
|
+
source,
|
|
4079
|
+
logFile,
|
|
4080
|
+
lineNumber,
|
|
4081
|
+
context: this.getSurroundingLines(lines, i)
|
|
4082
|
+
});
|
|
4083
|
+
continue;
|
|
4084
|
+
}
|
|
4085
|
+
if (CRASH_INDICATOR.test(line)) {
|
|
4086
|
+
this.emitError({
|
|
4087
|
+
message: line.trim(),
|
|
4088
|
+
level: "crash",
|
|
4089
|
+
source,
|
|
4090
|
+
logFile,
|
|
4091
|
+
lineNumber,
|
|
4092
|
+
context: this.getSurroundingLines(lines, i)
|
|
4093
|
+
});
|
|
4094
|
+
continue;
|
|
4095
|
+
}
|
|
4096
|
+
this.accumulateWarning(line, logFile, lineNumber);
|
|
4097
|
+
}
|
|
4098
|
+
}
|
|
4099
|
+
tryParseJsonError(line) {
|
|
4100
|
+
if (!line.startsWith("{")) return null;
|
|
4101
|
+
try {
|
|
4102
|
+
const entry = JSON.parse(line);
|
|
4103
|
+
if (entry.level === "error" || entry.level === "crash") {
|
|
4104
|
+
return {
|
|
4105
|
+
msg: entry.msg || entry.message || "unknown error",
|
|
4106
|
+
stack: entry.stack || entry.stackTrace,
|
|
4107
|
+
level: entry.level
|
|
4108
|
+
};
|
|
4109
|
+
}
|
|
4110
|
+
} catch {
|
|
4111
|
+
}
|
|
4112
|
+
return null;
|
|
4113
|
+
}
|
|
4114
|
+
collectStackTrace(lines, startIdx) {
|
|
4115
|
+
const message = lines[startIdx].trim();
|
|
4116
|
+
const stackLines = [message];
|
|
4117
|
+
for (let j = startIdx + 1; j < lines.length; j++) {
|
|
4118
|
+
if (STACK_FRAME.test(lines[j])) {
|
|
4119
|
+
stackLines.push(lines[j]);
|
|
4120
|
+
} else {
|
|
4121
|
+
break;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
return { message, stack: stackLines.join("\n") };
|
|
4125
|
+
}
|
|
4126
|
+
getSurroundingLines(lines, idx) {
|
|
4127
|
+
const start = Math.max(0, idx - 10);
|
|
4128
|
+
const end = Math.min(lines.length, idx + 11);
|
|
4129
|
+
return lines.slice(start, end);
|
|
4130
|
+
}
|
|
4131
|
+
emitError(params) {
|
|
4132
|
+
const id = hashError(params.message, params.stackTrace);
|
|
4133
|
+
const event = {
|
|
4134
|
+
id,
|
|
4135
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
4136
|
+
source: params.source,
|
|
4137
|
+
level: params.level,
|
|
4138
|
+
message: params.message,
|
|
4139
|
+
stackTrace: params.stackTrace,
|
|
4140
|
+
logFile: params.logFile,
|
|
4141
|
+
lineNumber: params.lineNumber,
|
|
4142
|
+
occurrences: 1,
|
|
4143
|
+
context: params.context
|
|
4144
|
+
};
|
|
4145
|
+
const entry = this.registry.recordError(event);
|
|
4146
|
+
log2.info("error detected", {
|
|
4147
|
+
id,
|
|
4148
|
+
severity: entry.severity,
|
|
4149
|
+
message: params.message.slice(0, 120),
|
|
4150
|
+
logFile: params.logFile
|
|
4151
|
+
});
|
|
4152
|
+
}
|
|
4153
|
+
// ---- repeated warning detection ------------------------------------------
|
|
4154
|
+
accumulateWarning(line, logFile, lineNumber) {
|
|
4155
|
+
const isWarning = /\bwarn\b/i.test(line) || line.startsWith("{") && line.includes('"warn"');
|
|
4156
|
+
if (!isWarning) return;
|
|
4157
|
+
const normalized = line.replace(/\d{4}-\d{2}-\d{2}T[\d:.Z]+/g, "T").replace(/\b\d{4,}\b/g, "N").trim();
|
|
4158
|
+
const key = `${logFile}::${normalized}`;
|
|
4159
|
+
const existing = this.warningAccumulators.get(key);
|
|
4160
|
+
const now = Date.now();
|
|
4161
|
+
if (existing) {
|
|
4162
|
+
existing.count++;
|
|
4163
|
+
existing.lastLine = lineNumber;
|
|
4164
|
+
if (existing.count >= WARNING_THRESHOLD && now - existing.firstSeen <= WARNING_DEDUP_WINDOW_MS) {
|
|
4165
|
+
const id = hashError(existing.message);
|
|
4166
|
+
const event = {
|
|
4167
|
+
id,
|
|
4168
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
4169
|
+
source: this.inferSource(logFile),
|
|
4170
|
+
level: "repeated-warning",
|
|
4171
|
+
message: `Repeated warning (${existing.count}x in 5 min): ${existing.message}`,
|
|
4172
|
+
logFile,
|
|
4173
|
+
lineNumber: existing.lastLine,
|
|
4174
|
+
occurrences: existing.count,
|
|
4175
|
+
context: []
|
|
4176
|
+
};
|
|
4177
|
+
this.registry.recordError(event);
|
|
4178
|
+
log2.info("repeated warning detected", {
|
|
4179
|
+
id,
|
|
4180
|
+
count: existing.count,
|
|
4181
|
+
message: existing.message.slice(0, 100)
|
|
4182
|
+
});
|
|
4183
|
+
this.warningAccumulators.delete(key);
|
|
4184
|
+
}
|
|
4185
|
+
} else {
|
|
4186
|
+
this.warningAccumulators.set(key, {
|
|
4187
|
+
message: normalized,
|
|
4188
|
+
count: 1,
|
|
4189
|
+
firstSeen: now,
|
|
4190
|
+
logFile,
|
|
4191
|
+
lastLine: lineNumber
|
|
4192
|
+
});
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
pruneWarnings() {
|
|
4196
|
+
const now = Date.now();
|
|
4197
|
+
for (const [key, acc] of this.warningAccumulators) {
|
|
4198
|
+
if (now - acc.firstSeen > WARNING_DEDUP_WINDOW_MS) {
|
|
4199
|
+
this.warningAccumulators.delete(key);
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
// ---- helpers -------------------------------------------------------------
|
|
4204
|
+
inferSource(logFile) {
|
|
4205
|
+
if (logFile.includes("watchdog")) return "watchdog";
|
|
4206
|
+
if (logFile.includes("memory")) return "memory";
|
|
4207
|
+
return "agent";
|
|
4208
|
+
}
|
|
4209
|
+
// ---- position persistence ------------------------------------------------
|
|
4210
|
+
loadPositions() {
|
|
4211
|
+
try {
|
|
4212
|
+
if (!existsSync7(this.positionsFile)) return;
|
|
4213
|
+
const raw = readFileSync8(this.positionsFile, "utf-8");
|
|
4214
|
+
const data = JSON.parse(raw);
|
|
4215
|
+
for (const [file, pos] of Object.entries(data)) {
|
|
4216
|
+
this.tailPositions.set(file, pos);
|
|
4217
|
+
}
|
|
4218
|
+
log2.debug("loaded tail positions", { count: this.tailPositions.size });
|
|
4219
|
+
} catch {
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
savePositions() {
|
|
4223
|
+
try {
|
|
4224
|
+
const { mkdirSync: mkdirSync15, writeFileSync: writeFileSync9 } = __require("fs");
|
|
4225
|
+
const { dirname: dirname9 } = __require("path");
|
|
4226
|
+
const dir = dirname9(this.positionsFile);
|
|
4227
|
+
if (!existsSync7(dir)) mkdirSync15(dir, { recursive: true });
|
|
4228
|
+
const data = Object.fromEntries(this.tailPositions);
|
|
4229
|
+
writeFileSync9(this.positionsFile, JSON.stringify(data));
|
|
4230
|
+
} catch (err) {
|
|
4231
|
+
log2.warn("failed to save tail positions", {
|
|
4232
|
+
error: err.message
|
|
4233
|
+
});
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
};
|
|
4237
|
+
|
|
4238
|
+
// packages/runtime/src/processors/auto-debugger.ts
|
|
4239
|
+
import { execSync as execSync3 } from "child_process";
|
|
4240
|
+
import { readFileSync as readFileSync9, existsSync as existsSync8 } from "fs";
|
|
4241
|
+
import { resolve as resolve6 } from "path";
|
|
4242
|
+
var log3 = createLogger("auto-debugger");
|
|
4243
|
+
var SECRET_PATTERNS = [
|
|
4244
|
+
/(?:api[_-]?key|token|secret|password|credential|auth)[\s=:]+\S+/gi,
|
|
4245
|
+
/sk-[a-zA-Z0-9]{20,}/g,
|
|
4246
|
+
/ghp_[a-zA-Z0-9]{36}/g,
|
|
4247
|
+
/Bearer\s+\S+/g
|
|
4248
|
+
];
|
|
4249
|
+
function sanitize(text) {
|
|
4250
|
+
let clean = text;
|
|
4251
|
+
for (const pat of SECRET_PATTERNS) {
|
|
4252
|
+
clean = clean.replace(pat, "[REDACTED]");
|
|
4253
|
+
}
|
|
4254
|
+
return clean;
|
|
4255
|
+
}
|
|
4256
|
+
function extractSourceFiles(stackTrace, repoRoot) {
|
|
4257
|
+
const files = /* @__PURE__ */ new Set();
|
|
4258
|
+
const pathPattern = /(?:[(]|\s)((?:\/[^\s:()]+|[a-zA-Z][a-zA-Z0-9/_.-]+)\.[a-zA-Z]+)(?::\d+){0,2}/g;
|
|
4259
|
+
let m;
|
|
4260
|
+
while ((m = pathPattern.exec(stackTrace)) !== null) {
|
|
4261
|
+
let filePath = m[1];
|
|
4262
|
+
if (filePath.includes("node_modules")) continue;
|
|
4263
|
+
if (!filePath.startsWith("/")) {
|
|
4264
|
+
filePath = resolve6(repoRoot, filePath);
|
|
4265
|
+
}
|
|
4266
|
+
if (filePath.startsWith(repoRoot) && existsSync8(filePath)) {
|
|
4267
|
+
files.add(filePath);
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4270
|
+
return [...files].slice(0, 5);
|
|
4271
|
+
}
|
|
4272
|
+
function ghAvailable() {
|
|
4273
|
+
try {
|
|
4274
|
+
execSync3("gh auth status", { stdio: "ignore", timeout: 5e3 });
|
|
4275
|
+
return true;
|
|
4276
|
+
} catch {
|
|
4277
|
+
return false;
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
function run(cmd, cwd) {
|
|
4281
|
+
try {
|
|
4282
|
+
return execSync3(cmd, { cwd, encoding: "utf-8", timeout: 6e4 }).trim();
|
|
4283
|
+
} catch {
|
|
4284
|
+
return null;
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
var AutoDebugger = class extends BackgroundProcess {
|
|
4288
|
+
config;
|
|
4289
|
+
registry;
|
|
4290
|
+
llm;
|
|
4291
|
+
repoRoot;
|
|
4292
|
+
activeFixes = /* @__PURE__ */ new Set();
|
|
4293
|
+
// error ids currently being fixed
|
|
4294
|
+
cooldowns = /* @__PURE__ */ new Map();
|
|
4295
|
+
// error id → epoch ms of last fix attempt
|
|
4296
|
+
constructor(config, registry, llmConfig, repoRoot) {
|
|
4297
|
+
super("auto-debugger", 6e4);
|
|
4298
|
+
this.config = config;
|
|
4299
|
+
this.registry = registry;
|
|
4300
|
+
this.repoRoot = repoRoot;
|
|
4301
|
+
if (llmConfig && llmConfig.api_key) {
|
|
4302
|
+
try {
|
|
4303
|
+
this.llm = new LLMClient(llmConfig);
|
|
4304
|
+
} catch {
|
|
4305
|
+
this.llm = null;
|
|
4306
|
+
}
|
|
4307
|
+
} else {
|
|
4308
|
+
this.llm = null;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
// ---- BackgroundProcess implementation ------------------------------------
|
|
4312
|
+
async process(_context) {
|
|
4313
|
+
const errors = [];
|
|
4314
|
+
let itemsProcessed = 0;
|
|
4315
|
+
const actionable = this.registry.getActionable(this.config.severity_threshold);
|
|
4316
|
+
if (actionable.length === 0) return { itemsProcessed: 0, errors: [] };
|
|
4317
|
+
log3.info("actionable errors found", { count: actionable.length });
|
|
4318
|
+
for (const entry of actionable) {
|
|
4319
|
+
if (this.activeFixes.size >= this.config.max_concurrent_fixes) {
|
|
4320
|
+
log3.debug("max concurrent fixes reached, skipping remaining");
|
|
4321
|
+
break;
|
|
4322
|
+
}
|
|
4323
|
+
const lastAttempt = this.cooldowns.get(entry.id);
|
|
4324
|
+
if (lastAttempt && Date.now() - lastAttempt < this.config.cooldown_minutes * 6e4) {
|
|
4325
|
+
continue;
|
|
4326
|
+
}
|
|
4327
|
+
try {
|
|
4328
|
+
await this.investigate(entry);
|
|
4329
|
+
itemsProcessed++;
|
|
4330
|
+
} catch (err) {
|
|
4331
|
+
errors.push(`${entry.id}: ${err.message}`);
|
|
4332
|
+
log3.error("investigation failed", {
|
|
4333
|
+
id: entry.id,
|
|
4334
|
+
error: err.message
|
|
4335
|
+
});
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
return { itemsProcessed, errors };
|
|
4339
|
+
}
|
|
4340
|
+
// ---- investigation pipeline ----------------------------------------------
|
|
4341
|
+
async investigate(entry) {
|
|
4342
|
+
this.registry.updateStatus(entry.id, "investigating");
|
|
4343
|
+
this.activeFixes.add(entry.id);
|
|
4344
|
+
this.cooldowns.set(entry.id, Date.now());
|
|
4345
|
+
try {
|
|
4346
|
+
const sourceFiles = entry.stackTrace ? extractSourceFiles(entry.stackTrace, this.repoRoot) : [];
|
|
4347
|
+
const fileContents = {};
|
|
4348
|
+
for (const f of sourceFiles) {
|
|
4349
|
+
try {
|
|
4350
|
+
const content = readFileSync9(f, "utf-8");
|
|
4351
|
+
fileContents[f] = content.split("\n").slice(0, 200).join("\n");
|
|
4352
|
+
} catch {
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
if (this.llm) {
|
|
4356
|
+
const diagnosis = await this.diagnose(entry, fileContents);
|
|
4357
|
+
log3.info("diagnosis complete", {
|
|
4358
|
+
id: entry.id,
|
|
4359
|
+
diagnosis: diagnosis.slice(0, 200)
|
|
4360
|
+
});
|
|
4361
|
+
if (this.config.auto_pr && ghAvailable()) {
|
|
4362
|
+
await this.attemptFix(entry, diagnosis, fileContents);
|
|
4363
|
+
}
|
|
4364
|
+
} else {
|
|
4365
|
+
log3.info("error detected (no LLM configured for diagnosis)", {
|
|
4366
|
+
id: entry.id,
|
|
4367
|
+
message: entry.message.slice(0, 120),
|
|
4368
|
+
severity: entry.severity,
|
|
4369
|
+
sourceFiles
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4372
|
+
} finally {
|
|
4373
|
+
this.activeFixes.delete(entry.id);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
// ---- LLM diagnosis -------------------------------------------------------
|
|
4377
|
+
async diagnose(entry, fileContents) {
|
|
4378
|
+
const fileSection = Object.entries(fileContents).map(([path, content]) => `--- ${path} ---
|
|
4379
|
+
${content}`).join("\n\n");
|
|
4380
|
+
const messages = [
|
|
4381
|
+
{
|
|
4382
|
+
role: "system",
|
|
4383
|
+
content: [
|
|
4384
|
+
"You are a senior software engineer diagnosing a bug in the Hivemind agent framework (TypeScript/Node.js).",
|
|
4385
|
+
"Analyze the error, identify the root cause, and suggest a concise fix.",
|
|
4386
|
+
"Be specific: reference exact lines, variables, and logic flows.",
|
|
4387
|
+
"If you cannot determine the cause from the given context, say so."
|
|
4388
|
+
].join("\n")
|
|
4389
|
+
},
|
|
4390
|
+
{
|
|
4391
|
+
role: "user",
|
|
4392
|
+
content: [
|
|
4393
|
+
`## Error`,
|
|
4394
|
+
`Message: ${entry.message}`,
|
|
4395
|
+
`Level: ${entry.level}`,
|
|
4396
|
+
`Source: ${entry.source}`,
|
|
4397
|
+
`Occurrences: ${entry.totalOccurrences}`,
|
|
4398
|
+
`First seen: ${entry.firstSeen}`,
|
|
4399
|
+
`Last seen: ${entry.lastSeen}`,
|
|
4400
|
+
entry.stackTrace ? `
|
|
4401
|
+
Stack trace:
|
|
4402
|
+
${entry.stackTrace}` : "",
|
|
4403
|
+
fileSection ? `
|
|
4404
|
+
## Source files
|
|
4405
|
+
${fileSection}` : "",
|
|
4406
|
+
"\n## Task",
|
|
4407
|
+
"1. What is the root cause?",
|
|
4408
|
+
"2. What is the minimal fix?",
|
|
4409
|
+
"3. Are there any risks with the fix?"
|
|
4410
|
+
].join("\n")
|
|
4411
|
+
}
|
|
4412
|
+
];
|
|
4413
|
+
try {
|
|
4414
|
+
const response = await this.llm.chat(messages);
|
|
4415
|
+
return response.content;
|
|
4416
|
+
} catch (err) {
|
|
4417
|
+
log3.error("LLM diagnosis call failed", {
|
|
4418
|
+
error: err.message
|
|
4419
|
+
});
|
|
4420
|
+
return `Diagnosis failed: ${err.message}`;
|
|
4421
|
+
}
|
|
4422
|
+
}
|
|
4423
|
+
// ---- auto PR -------------------------------------------------------------
|
|
4424
|
+
async attemptFix(entry, diagnosis, fileContents) {
|
|
4425
|
+
const shortId = entry.id.slice(0, 8);
|
|
4426
|
+
const branchName = `${this.config.branch_prefix}/${shortId}`;
|
|
4427
|
+
if (run(`git rev-parse --verify ${branchName}`, this.repoRoot) !== null) {
|
|
4428
|
+
log3.info("fix branch already exists, skipping", { branch: branchName });
|
|
4429
|
+
return;
|
|
4430
|
+
}
|
|
4431
|
+
const fix = await this.generateFix(entry, diagnosis, fileContents);
|
|
4432
|
+
if (!fix) {
|
|
4433
|
+
log3.info("LLM could not generate a fix", { id: entry.id });
|
|
4434
|
+
return;
|
|
4435
|
+
}
|
|
4436
|
+
const baseBranch = run("git rev-parse --abbrev-ref HEAD", this.repoRoot) || "main";
|
|
4437
|
+
if (run(`git checkout -b ${branchName}`, this.repoRoot) === null) {
|
|
4438
|
+
log3.error("failed to create fix branch", { branch: branchName });
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
try {
|
|
4442
|
+
let anyApplied = false;
|
|
4443
|
+
for (const [filePath, newContent] of Object.entries(fix.files)) {
|
|
4444
|
+
try {
|
|
4445
|
+
__require("fs").writeFileSync(filePath, newContent);
|
|
4446
|
+
run(`git add "${filePath}"`, this.repoRoot);
|
|
4447
|
+
anyApplied = true;
|
|
4448
|
+
} catch (err) {
|
|
4449
|
+
log3.warn("failed to write fix file", {
|
|
4450
|
+
file: filePath,
|
|
4451
|
+
error: err.message
|
|
4452
|
+
});
|
|
4453
|
+
}
|
|
4454
|
+
}
|
|
4455
|
+
if (!anyApplied) {
|
|
4456
|
+
log3.info("no files were patched, aborting PR", { id: entry.id });
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
const buildResult = run("npx tsup", this.repoRoot);
|
|
4460
|
+
if (buildResult === null) {
|
|
4461
|
+
log3.warn("build failed after applying fix, aborting", { id: entry.id });
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4464
|
+
const commitMsg = [
|
|
4465
|
+
`fix(auto-debug): ${fix.summary}`,
|
|
4466
|
+
"",
|
|
4467
|
+
`Error: ${entry.message}`,
|
|
4468
|
+
`Occurrences: ${entry.totalOccurrences} over ${this.formatTimespan(entry.firstSeen, entry.lastSeen)}`,
|
|
4469
|
+
`Source: ${entry.source}`,
|
|
4470
|
+
"",
|
|
4471
|
+
`Root cause: ${fix.rootCause}`,
|
|
4472
|
+
"",
|
|
4473
|
+
"Auto-generated by hivemind auto-debug processor"
|
|
4474
|
+
].join("\n");
|
|
4475
|
+
if (run(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, this.repoRoot) === null) {
|
|
4476
|
+
log3.error("git commit failed", { id: entry.id });
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
if (run(`git push -u origin ${branchName}`, this.repoRoot) === null) {
|
|
4480
|
+
log3.error("git push failed", { id: entry.id });
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
const prBody = this.buildPrBody(entry, diagnosis, fix);
|
|
4484
|
+
const prUrl = run(
|
|
4485
|
+
`gh pr create --base ${baseBranch} --head ${branchName} --title "fix(auto-debug): ${fix.summary}" --body "${prBody.replace(/"/g, '\\"')}"`,
|
|
4486
|
+
this.repoRoot
|
|
4487
|
+
);
|
|
4488
|
+
if (prUrl) {
|
|
4489
|
+
this.registry.updateStatus(entry.id, "fix-submitted", prUrl);
|
|
4490
|
+
log3.info("PR created", { id: entry.id, prUrl });
|
|
4491
|
+
} else {
|
|
4492
|
+
log3.error("gh pr create failed", { id: entry.id });
|
|
4493
|
+
}
|
|
4494
|
+
} finally {
|
|
4495
|
+
run(`git checkout ${baseBranch}`, this.repoRoot);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
async generateFix(entry, diagnosis, fileContents) {
|
|
4499
|
+
if (!this.llm) return null;
|
|
4500
|
+
const fileSection = Object.entries(fileContents).map(([path, content]) => `--- ${path} ---
|
|
4501
|
+
${content}`).join("\n\n");
|
|
4502
|
+
const messages = [
|
|
4503
|
+
{
|
|
4504
|
+
role: "system",
|
|
4505
|
+
content: [
|
|
4506
|
+
"You are a senior TypeScript engineer. Generate a minimal, safe code fix.",
|
|
4507
|
+
"Output ONLY valid JSON with this shape:",
|
|
4508
|
+
'{ "summary": "short commit subject", "rootCause": "one sentence", "files": { "/absolute/path.ts": "full file content" } }',
|
|
4509
|
+
"Do NOT include explanations outside the JSON. Do NOT modify files that don't need changing.",
|
|
4510
|
+
"If you cannot produce a reliable fix, return null."
|
|
4511
|
+
].join("\n")
|
|
4512
|
+
},
|
|
4513
|
+
{
|
|
4514
|
+
role: "user",
|
|
4515
|
+
content: [
|
|
4516
|
+
`## Error: ${entry.message}`,
|
|
4517
|
+
entry.stackTrace ? `
|
|
4518
|
+
Stack trace:
|
|
4519
|
+
${entry.stackTrace}` : "",
|
|
4520
|
+
`
|
|
4521
|
+
## Diagnosis
|
|
4522
|
+
${diagnosis}`,
|
|
4523
|
+
`
|
|
4524
|
+
## Current source files
|
|
4525
|
+
${fileSection}`,
|
|
4526
|
+
"\nGenerate the fix JSON."
|
|
4527
|
+
].join("\n")
|
|
4528
|
+
}
|
|
4529
|
+
];
|
|
4530
|
+
try {
|
|
4531
|
+
const response = await this.llm.chat(messages);
|
|
4532
|
+
const content = response.content.trim();
|
|
4533
|
+
if (content === "null" || content === "null.") return null;
|
|
4534
|
+
const jsonMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/) || content.match(/(\{[\s\S]*\})/);
|
|
4535
|
+
if (!jsonMatch) return null;
|
|
4536
|
+
const parsed = JSON.parse(jsonMatch[1]);
|
|
4537
|
+
if (!parsed.summary || !parsed.files || Object.keys(parsed.files).length === 0) {
|
|
4538
|
+
return null;
|
|
4539
|
+
}
|
|
4540
|
+
for (const filePath of Object.keys(parsed.files)) {
|
|
4541
|
+
if (!filePath.startsWith(this.repoRoot)) {
|
|
4542
|
+
log3.warn("fix targets file outside repo, rejecting", { file: filePath });
|
|
4543
|
+
return null;
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
return parsed;
|
|
4547
|
+
} catch (err) {
|
|
4548
|
+
log3.error("failed to generate fix", { error: err.message });
|
|
4549
|
+
return null;
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
// ---- PR body builder -----------------------------------------------------
|
|
4553
|
+
buildPrBody(entry, diagnosis, fix) {
|
|
4554
|
+
const logSample = entry.stackTrace ? sanitize(entry.stackTrace.split("\n").slice(0, 15).join("\n")) : sanitize(entry.message);
|
|
4555
|
+
return [
|
|
4556
|
+
"## Auto-Debug Fix",
|
|
4557
|
+
"",
|
|
4558
|
+
`**Error:** \`${sanitize(entry.message).slice(0, 200)}\``,
|
|
4559
|
+
`**Source:** ${entry.source}`,
|
|
4560
|
+
`**Frequency:** ${entry.totalOccurrences} occurrences over ${this.formatTimespan(entry.firstSeen, entry.lastSeen)}`,
|
|
4561
|
+
`**Severity:** ${entry.severity}/10`,
|
|
4562
|
+
"",
|
|
4563
|
+
"### Diagnosis",
|
|
4564
|
+
sanitize(diagnosis),
|
|
4565
|
+
"",
|
|
4566
|
+
"### Fix",
|
|
4567
|
+
sanitize(fix.rootCause),
|
|
4568
|
+
"",
|
|
4569
|
+
"### Log Sample",
|
|
4570
|
+
"```",
|
|
4571
|
+
logSample,
|
|
4572
|
+
"```",
|
|
4573
|
+
"",
|
|
4574
|
+
"### Verification",
|
|
4575
|
+
"- [ ] Build passes (`tsup`)",
|
|
4576
|
+
"- [ ] Error no longer reproduces (monitored for 30 min post-deploy)",
|
|
4577
|
+
"",
|
|
4578
|
+
"---",
|
|
4579
|
+
"*Auto-generated by hivemind auto-debug processor. Review before merging.*"
|
|
4580
|
+
].join("\n");
|
|
4581
|
+
}
|
|
4582
|
+
// ---- utils ---------------------------------------------------------------
|
|
4583
|
+
formatTimespan(firstSeen, lastSeen) {
|
|
4584
|
+
const ms = new Date(lastSeen).getTime() - new Date(firstSeen).getTime();
|
|
4585
|
+
if (ms < 6e4) return `${Math.round(ms / 1e3)}s`;
|
|
4586
|
+
if (ms < 36e5) return `${Math.round(ms / 6e4)}m`;
|
|
4587
|
+
if (ms < 864e5) return `${Math.round(ms / 36e5)}h`;
|
|
4588
|
+
return `${Math.round(ms / 864e5)}d`;
|
|
4589
|
+
}
|
|
4590
|
+
};
|
|
4591
|
+
|
|
3588
4592
|
// packages/runtime/src/pipeline.ts
|
|
3589
4593
|
import { createServer as createServer3 } from "http";
|
|
3590
4594
|
|
|
@@ -3594,23 +4598,23 @@ var HEALTH_PATH = "/health";
|
|
|
3594
4598
|
|
|
3595
4599
|
// packages/runtime/src/request-logger.ts
|
|
3596
4600
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
3597
|
-
import { mkdirSync as
|
|
3598
|
-
import { dirname as
|
|
4601
|
+
import { mkdirSync as mkdirSync5, existsSync as existsSync9, appendFileSync as appendFileSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2 } from "fs";
|
|
4602
|
+
import { dirname as dirname5 } from "path";
|
|
3599
4603
|
var RequestLogger = class {
|
|
3600
4604
|
logPath;
|
|
3601
4605
|
maxAgeDays = 7;
|
|
3602
4606
|
constructor(dbPath) {
|
|
3603
4607
|
this.logPath = dbPath.replace(/\.db$/, ".jsonl");
|
|
3604
4608
|
if (this.logPath === dbPath) this.logPath = dbPath + ".jsonl";
|
|
3605
|
-
const dir =
|
|
3606
|
-
if (!
|
|
4609
|
+
const dir = dirname5(this.logPath);
|
|
4610
|
+
if (!existsSync9(dir)) mkdirSync5(dir, { recursive: true });
|
|
3607
4611
|
this.prune();
|
|
3608
4612
|
}
|
|
3609
4613
|
prune() {
|
|
3610
|
-
if (!
|
|
4614
|
+
if (!existsSync9(this.logPath)) return;
|
|
3611
4615
|
const cutoff = new Date(Date.now() - this.maxAgeDays * 24 * 60 * 60 * 1e3).toISOString();
|
|
3612
4616
|
try {
|
|
3613
|
-
const lines =
|
|
4617
|
+
const lines = readFileSync10(this.logPath, "utf-8").split("\n").filter(Boolean);
|
|
3614
4618
|
const kept = [];
|
|
3615
4619
|
let pruned = 0;
|
|
3616
4620
|
for (const line of lines) {
|
|
@@ -3625,7 +4629,7 @@ var RequestLogger = class {
|
|
|
3625
4629
|
}
|
|
3626
4630
|
}
|
|
3627
4631
|
if (pruned > 0) {
|
|
3628
|
-
|
|
4632
|
+
writeFileSync2(this.logPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
|
|
3629
4633
|
console.log(`[dashboard] Pruned ${pruned} old request logs`);
|
|
3630
4634
|
}
|
|
3631
4635
|
} catch {
|
|
@@ -3686,9 +4690,9 @@ var RequestLogger = class {
|
|
|
3686
4690
|
close() {
|
|
3687
4691
|
}
|
|
3688
4692
|
readAll() {
|
|
3689
|
-
if (!
|
|
4693
|
+
if (!existsSync9(this.logPath)) return [];
|
|
3690
4694
|
try {
|
|
3691
|
-
const lines =
|
|
4695
|
+
const lines = readFileSync10(this.logPath, "utf-8").split("\n").filter(Boolean);
|
|
3692
4696
|
const entries = [];
|
|
3693
4697
|
for (const line of lines) {
|
|
3694
4698
|
try {
|
|
@@ -3705,17 +4709,17 @@ var RequestLogger = class {
|
|
|
3705
4709
|
|
|
3706
4710
|
// packages/runtime/src/dashboard.ts
|
|
3707
4711
|
import { createServer } from "http";
|
|
3708
|
-
import { readFileSync as
|
|
3709
|
-
import { resolve as
|
|
4712
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4713
|
+
import { resolve as resolve7, dirname as dirname6 } from "path";
|
|
3710
4714
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3711
|
-
var __dirname =
|
|
4715
|
+
var __dirname = dirname6(fileURLToPath2(import.meta.url));
|
|
3712
4716
|
var DASHBOARD_PORT = 9485;
|
|
3713
4717
|
var spaHtml = null;
|
|
3714
4718
|
function getSpaHtml() {
|
|
3715
4719
|
if (!spaHtml) {
|
|
3716
|
-
for (const dir of [__dirname,
|
|
4720
|
+
for (const dir of [__dirname, resolve7(__dirname, "../src")]) {
|
|
3717
4721
|
try {
|
|
3718
|
-
spaHtml =
|
|
4722
|
+
spaHtml = readFileSync11(resolve7(dir, "dashboard.html"), "utf-8");
|
|
3719
4723
|
break;
|
|
3720
4724
|
} catch {
|
|
3721
4725
|
}
|
|
@@ -3890,8 +4894,8 @@ var ToolRegistry = class {
|
|
|
3890
4894
|
};
|
|
3891
4895
|
|
|
3892
4896
|
// packages/runtime/src/tools/shell.ts
|
|
3893
|
-
import { execSync as
|
|
3894
|
-
import { resolve as
|
|
4897
|
+
import { execSync as execSync4 } from "child_process";
|
|
4898
|
+
import { resolve as resolve8 } from "path";
|
|
3895
4899
|
var MAX_OUTPUT = 5e4;
|
|
3896
4900
|
function registerShellTool(registry, workspaceDir) {
|
|
3897
4901
|
registry.register(
|
|
@@ -3918,9 +4922,9 @@ function registerShellTool(registry, workspaceDir) {
|
|
|
3918
4922
|
async (params) => {
|
|
3919
4923
|
const command = params.command;
|
|
3920
4924
|
const timeout = (params.timeout || 30) * 1e3;
|
|
3921
|
-
const cwd = params.workdir ?
|
|
4925
|
+
const cwd = params.workdir ? resolve8(workspaceDir, params.workdir) : workspaceDir;
|
|
3922
4926
|
try {
|
|
3923
|
-
const output =
|
|
4927
|
+
const output = execSync4(command, {
|
|
3924
4928
|
cwd,
|
|
3925
4929
|
timeout,
|
|
3926
4930
|
encoding: "utf-8",
|
|
@@ -3945,8 +4949,8 @@ ${output || err.message}`;
|
|
|
3945
4949
|
}
|
|
3946
4950
|
|
|
3947
4951
|
// packages/runtime/src/tools/files.ts
|
|
3948
|
-
import { readFileSync as
|
|
3949
|
-
import { resolve as
|
|
4952
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync3, existsSync as existsSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
4953
|
+
import { resolve as resolve9, dirname as dirname7 } from "path";
|
|
3950
4954
|
var MAX_READ = 1e5;
|
|
3951
4955
|
function registerFileTools(registry, workspaceDir) {
|
|
3952
4956
|
registry.register(
|
|
@@ -3972,11 +4976,11 @@ function registerFileTools(registry, workspaceDir) {
|
|
|
3972
4976
|
},
|
|
3973
4977
|
async (params) => {
|
|
3974
4978
|
const filePath = resolvePath(workspaceDir, params.path);
|
|
3975
|
-
if (!
|
|
4979
|
+
if (!existsSync10(filePath)) {
|
|
3976
4980
|
return `Error: File not found: ${filePath}`;
|
|
3977
4981
|
}
|
|
3978
4982
|
try {
|
|
3979
|
-
let content =
|
|
4983
|
+
let content = readFileSync12(filePath, "utf-8");
|
|
3980
4984
|
if (params.offset || params.limit) {
|
|
3981
4985
|
const lines = content.split("\n");
|
|
3982
4986
|
const start = (params.offset || 1) - 1;
|
|
@@ -4013,9 +5017,9 @@ function registerFileTools(registry, workspaceDir) {
|
|
|
4013
5017
|
async (params) => {
|
|
4014
5018
|
const filePath = resolvePath(workspaceDir, params.path);
|
|
4015
5019
|
try {
|
|
4016
|
-
const dir =
|
|
4017
|
-
if (!
|
|
4018
|
-
|
|
5020
|
+
const dir = dirname7(filePath);
|
|
5021
|
+
if (!existsSync10(dir)) mkdirSync6(dir, { recursive: true });
|
|
5022
|
+
writeFileSync3(filePath, params.content);
|
|
4019
5023
|
return `Written ${params.content.length} bytes to ${filePath}`;
|
|
4020
5024
|
} catch (err) {
|
|
4021
5025
|
return `Error writing file: ${err.message}`;
|
|
@@ -4045,18 +5049,18 @@ function registerFileTools(registry, workspaceDir) {
|
|
|
4045
5049
|
},
|
|
4046
5050
|
async (params) => {
|
|
4047
5051
|
const filePath = resolvePath(workspaceDir, params.path);
|
|
4048
|
-
if (!
|
|
5052
|
+
if (!existsSync10(filePath)) {
|
|
4049
5053
|
return `Error: File not found: ${filePath}`;
|
|
4050
5054
|
}
|
|
4051
5055
|
try {
|
|
4052
|
-
const content =
|
|
5056
|
+
const content = readFileSync12(filePath, "utf-8");
|
|
4053
5057
|
const oldText = params.old_text;
|
|
4054
5058
|
const newText = params.new_text;
|
|
4055
5059
|
if (!content.includes(oldText)) {
|
|
4056
5060
|
return `Error: Could not find the exact text to replace in ${filePath}`;
|
|
4057
5061
|
}
|
|
4058
5062
|
const updated = content.replace(oldText, newText);
|
|
4059
|
-
|
|
5063
|
+
writeFileSync3(filePath, updated);
|
|
4060
5064
|
return `Edited ${filePath}: replaced ${oldText.length} chars with ${newText.length} chars`;
|
|
4061
5065
|
} catch (err) {
|
|
4062
5066
|
return `Error editing file: ${err.message}`;
|
|
@@ -4077,9 +5081,9 @@ function registerFileTools(registry, workspaceDir) {
|
|
|
4077
5081
|
required: []
|
|
4078
5082
|
},
|
|
4079
5083
|
async (params) => {
|
|
4080
|
-
const { readdirSync: readdirSync5, statSync:
|
|
5084
|
+
const { readdirSync: readdirSync5, statSync: statSync4 } = await import("fs");
|
|
4081
5085
|
const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
|
|
4082
|
-
if (!
|
|
5086
|
+
if (!existsSync10(dirPath)) {
|
|
4083
5087
|
return `Error: Directory not found: ${dirPath}`;
|
|
4084
5088
|
}
|
|
4085
5089
|
try {
|
|
@@ -4088,7 +5092,7 @@ function registerFileTools(registry, workspaceDir) {
|
|
|
4088
5092
|
for (const entry of entries) {
|
|
4089
5093
|
if (entry.startsWith(".")) continue;
|
|
4090
5094
|
try {
|
|
4091
|
-
const stat =
|
|
5095
|
+
const stat = statSync4(resolve9(dirPath, entry));
|
|
4092
5096
|
results.push(stat.isDirectory() ? `${entry}/` : entry);
|
|
4093
5097
|
} catch {
|
|
4094
5098
|
results.push(entry);
|
|
@@ -4105,7 +5109,7 @@ function resolvePath(workspace, path) {
|
|
|
4105
5109
|
if (path.startsWith("/") || path.startsWith("~")) {
|
|
4106
5110
|
return path.replace(/^~/, process.env.HOME || "/root");
|
|
4107
5111
|
}
|
|
4108
|
-
return
|
|
5112
|
+
return resolve9(workspace, path);
|
|
4109
5113
|
}
|
|
4110
5114
|
|
|
4111
5115
|
// packages/runtime/src/tools/web.ts
|
|
@@ -4358,13 +5362,13 @@ Contexts:
|
|
|
4358
5362
|
}
|
|
4359
5363
|
|
|
4360
5364
|
// packages/runtime/src/tools/events.ts
|
|
4361
|
-
import { existsSync as
|
|
5365
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
4362
5366
|
import { join as join4 } from "path";
|
|
4363
5367
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
4364
5368
|
function registerEventTools(registry, dataDir) {
|
|
4365
5369
|
const eventsDir = join4(dataDir, "events");
|
|
4366
|
-
if (!
|
|
4367
|
-
|
|
5370
|
+
if (!existsSync11(eventsDir)) {
|
|
5371
|
+
mkdirSync7(eventsDir, { recursive: true });
|
|
4368
5372
|
}
|
|
4369
5373
|
registry.register(
|
|
4370
5374
|
"create_event",
|
|
@@ -4433,7 +5437,7 @@ function registerEventTools(registry, dataDir) {
|
|
|
4433
5437
|
const filename = `${randomUUID3()}.json`;
|
|
4434
5438
|
const filePath = join4(eventsDir, filename);
|
|
4435
5439
|
try {
|
|
4436
|
-
|
|
5440
|
+
writeFileSync4(filePath, JSON.stringify(event, null, 2));
|
|
4437
5441
|
return `Event created: ${filename} (type: ${type})`;
|
|
4438
5442
|
} catch (err) {
|
|
4439
5443
|
return `Error creating event: ${err.message}`;
|
|
@@ -4450,7 +5454,7 @@ function registerEventTools(registry, dataDir) {
|
|
|
4450
5454
|
},
|
|
4451
5455
|
async () => {
|
|
4452
5456
|
try {
|
|
4453
|
-
if (!
|
|
5457
|
+
if (!existsSync11(eventsDir)) {
|
|
4454
5458
|
return "No events directory found.";
|
|
4455
5459
|
}
|
|
4456
5460
|
const files = readdirSync4(eventsDir).filter((f) => f.endsWith(".json"));
|
|
@@ -4460,7 +5464,7 @@ function registerEventTools(registry, dataDir) {
|
|
|
4460
5464
|
const lines = [];
|
|
4461
5465
|
for (const file of files) {
|
|
4462
5466
|
try {
|
|
4463
|
-
const content =
|
|
5467
|
+
const content = readFileSync13(join4(eventsDir, file), "utf-8");
|
|
4464
5468
|
const event = JSON.parse(content);
|
|
4465
5469
|
let detail = `${file} \u2014 type: ${event.type}, channel: ${event.channelId}`;
|
|
4466
5470
|
if (event.type === "one-shot") {
|
|
@@ -4500,7 +5504,7 @@ ${lines.join("\n")}`;
|
|
|
4500
5504
|
if (filename.includes("/") || filename.includes("\\") || filename.includes("..")) {
|
|
4501
5505
|
return "Error: Invalid filename.";
|
|
4502
5506
|
}
|
|
4503
|
-
if (!
|
|
5507
|
+
if (!existsSync11(filePath)) {
|
|
4504
5508
|
return `Event not found: ${filename}`;
|
|
4505
5509
|
}
|
|
4506
5510
|
try {
|
|
@@ -4515,16 +5519,16 @@ ${lines.join("\n")}`;
|
|
|
4515
5519
|
|
|
4516
5520
|
// packages/runtime/src/tools/spawn.ts
|
|
4517
5521
|
import { spawn } from "child_process";
|
|
4518
|
-
import { existsSync as
|
|
4519
|
-
import { join as join5, resolve as
|
|
5522
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
|
|
5523
|
+
import { join as join5, resolve as resolve10 } from "path";
|
|
4520
5524
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
4521
5525
|
var spawnedAgents = /* @__PURE__ */ new Map();
|
|
4522
5526
|
function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
|
|
4523
5527
|
const spawnDir = join5(dataDir, "spawn");
|
|
4524
|
-
if (!
|
|
4525
|
-
|
|
5528
|
+
if (!existsSync12(spawnDir)) {
|
|
5529
|
+
mkdirSync8(spawnDir, { recursive: true });
|
|
4526
5530
|
}
|
|
4527
|
-
const hivemindBin =
|
|
5531
|
+
const hivemindBin = resolve10(hivemindHome, "node_modules", ".bin", "hivemind");
|
|
4528
5532
|
registry.register(
|
|
4529
5533
|
"spawn_agent",
|
|
4530
5534
|
"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 +5561,8 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
|
|
|
4557
5561
|
const channelId = params.channelId;
|
|
4558
5562
|
const timeoutSeconds = params.timeoutSeconds || 300;
|
|
4559
5563
|
const spawnWorkDir = join5(spawnDir, spawnId);
|
|
4560
|
-
|
|
4561
|
-
|
|
5564
|
+
mkdirSync8(spawnWorkDir, { recursive: true });
|
|
5565
|
+
writeFileSync5(join5(spawnWorkDir, "task.md"), task);
|
|
4562
5566
|
const childEnv = {
|
|
4563
5567
|
...process.env,
|
|
4564
5568
|
SPAWN_TASK: task,
|
|
@@ -4600,7 +5604,7 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
|
|
|
4600
5604
|
agent.exitCode = code;
|
|
4601
5605
|
agent.exited = true;
|
|
4602
5606
|
try {
|
|
4603
|
-
|
|
5607
|
+
writeFileSync5(logPath, Buffer.concat(logChunks).toString("utf-8"));
|
|
4604
5608
|
} catch {
|
|
4605
5609
|
}
|
|
4606
5610
|
});
|
|
@@ -4613,7 +5617,7 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
|
|
|
4613
5617
|
agent.exited = true;
|
|
4614
5618
|
agent.exitCode = -1;
|
|
4615
5619
|
try {
|
|
4616
|
-
|
|
5620
|
+
writeFileSync5(
|
|
4617
5621
|
join5(spawnWorkDir, "result.txt"),
|
|
4618
5622
|
`[TIMEOUT] Sub-agent killed after ${timeoutSeconds}s`
|
|
4619
5623
|
);
|
|
@@ -4668,9 +5672,9 @@ function registerSpawnTools(registry, hivemindHome, dataDir, configPath) {
|
|
|
4668
5672
|
task: "${taskPreview}"`;
|
|
4669
5673
|
if (agent.exited) {
|
|
4670
5674
|
const resultPath = join5(spawnDir, id, "result.txt");
|
|
4671
|
-
if (
|
|
5675
|
+
if (existsSync12(resultPath)) {
|
|
4672
5676
|
try {
|
|
4673
|
-
const result =
|
|
5677
|
+
const result = readFileSync14(resultPath, "utf-8");
|
|
4674
5678
|
const preview = result.length > 200 ? result.slice(0, 200) + "..." : result;
|
|
4675
5679
|
detail += `
|
|
4676
5680
|
result: "${preview}"`;
|
|
@@ -4710,7 +5714,7 @@ ${lines.join("\n\n")}`;
|
|
|
4710
5714
|
agent.process.kill("SIGTERM");
|
|
4711
5715
|
agent.exited = true;
|
|
4712
5716
|
agent.exitCode = -2;
|
|
4713
|
-
|
|
5717
|
+
writeFileSync5(
|
|
4714
5718
|
join5(spawnDir, id, "result.txt"),
|
|
4715
5719
|
`[KILLED] Sub-agent killed by parent agent`
|
|
4716
5720
|
);
|
|
@@ -4793,11 +5797,11 @@ function registerVisionTools(registry) {
|
|
|
4793
5797
|
}
|
|
4794
5798
|
|
|
4795
5799
|
// packages/runtime/src/tools/git.ts
|
|
4796
|
-
import { execSync as
|
|
4797
|
-
import { resolve as
|
|
5800
|
+
import { execSync as execSync5 } from "child_process";
|
|
5801
|
+
import { resolve as resolve11, normalize } from "path";
|
|
4798
5802
|
function resolveRepoPath(workspaceDir, repoPath) {
|
|
4799
5803
|
if (!repoPath) return workspaceDir;
|
|
4800
|
-
const resolved =
|
|
5804
|
+
const resolved = resolve11(workspaceDir, repoPath);
|
|
4801
5805
|
const normalizedResolved = normalize(resolved);
|
|
4802
5806
|
const normalizedWorkspace = normalize(workspaceDir);
|
|
4803
5807
|
if (!normalizedResolved.startsWith(normalizedWorkspace)) {
|
|
@@ -4806,7 +5810,7 @@ function resolveRepoPath(workspaceDir, repoPath) {
|
|
|
4806
5810
|
return resolved;
|
|
4807
5811
|
}
|
|
4808
5812
|
function runGit(args, cwd) {
|
|
4809
|
-
const output =
|
|
5813
|
+
const output = execSync5(`git ${args}`, {
|
|
4810
5814
|
cwd,
|
|
4811
5815
|
timeout: 3e4,
|
|
4812
5816
|
encoding: "utf-8",
|
|
@@ -5030,8 +6034,8 @@ ${combined}`;
|
|
|
5030
6034
|
}
|
|
5031
6035
|
|
|
5032
6036
|
// packages/runtime/src/tools/browser.ts
|
|
5033
|
-
import { resolve as
|
|
5034
|
-
import { mkdirSync as
|
|
6037
|
+
import { resolve as resolve12 } from "path";
|
|
6038
|
+
import { mkdirSync as mkdirSync9, existsSync as existsSync13 } from "fs";
|
|
5035
6039
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
5036
6040
|
var MAX_OUTPUT2 = 5e4;
|
|
5037
6041
|
var browserInstance = null;
|
|
@@ -5074,7 +6078,7 @@ async function closeBrowser() {
|
|
|
5074
6078
|
}
|
|
5075
6079
|
}
|
|
5076
6080
|
function registerBrowserTools(registry, workspaceDir) {
|
|
5077
|
-
const screenshotDir =
|
|
6081
|
+
const screenshotDir = resolve12(workspaceDir, "screenshots");
|
|
5078
6082
|
registry.register(
|
|
5079
6083
|
"browse",
|
|
5080
6084
|
[
|
|
@@ -5158,11 +6162,11 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
5158
6162
|
return trimmed;
|
|
5159
6163
|
}
|
|
5160
6164
|
case "screenshot": {
|
|
5161
|
-
if (!
|
|
5162
|
-
|
|
6165
|
+
if (!existsSync13(screenshotDir)) {
|
|
6166
|
+
mkdirSync9(screenshotDir, { recursive: true });
|
|
5163
6167
|
}
|
|
5164
6168
|
const filename = `${randomUUID5()}.png`;
|
|
5165
|
-
const filepath =
|
|
6169
|
+
const filepath = resolve12(screenshotDir, filename);
|
|
5166
6170
|
await page.screenshot({ path: filepath, fullPage: true });
|
|
5167
6171
|
return `Screenshot saved: ${filepath}`;
|
|
5168
6172
|
}
|
|
@@ -5213,7 +6217,7 @@ function registerBrowserTools(registry, workspaceDir) {
|
|
|
5213
6217
|
}
|
|
5214
6218
|
|
|
5215
6219
|
// packages/runtime/src/tools/system.ts
|
|
5216
|
-
import { execSync as
|
|
6220
|
+
import { execSync as execSync6 } from "child_process";
|
|
5217
6221
|
import * as os from "os";
|
|
5218
6222
|
var MAX_OUTPUT3 = 5e4;
|
|
5219
6223
|
function truncate(output) {
|
|
@@ -5224,7 +6228,7 @@ function truncate(output) {
|
|
|
5224
6228
|
return output;
|
|
5225
6229
|
}
|
|
5226
6230
|
function exec(cmd, timeoutS = 10) {
|
|
5227
|
-
return
|
|
6231
|
+
return execSync6(cmd, {
|
|
5228
6232
|
encoding: "utf-8",
|
|
5229
6233
|
timeout: timeoutS * 1e3,
|
|
5230
6234
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -5471,8 +6475,8 @@ function fmt(bytes) {
|
|
|
5471
6475
|
|
|
5472
6476
|
// packages/runtime/src/tools/http-server.ts
|
|
5473
6477
|
import { createServer as createServer2 } from "http";
|
|
5474
|
-
import { createReadStream, existsSync as
|
|
5475
|
-
import { join as join6, extname, resolve as
|
|
6478
|
+
import { createReadStream, existsSync as existsSync14, statSync as statSync3 } from "fs";
|
|
6479
|
+
import { join as join6, extname, resolve as resolve13 } from "path";
|
|
5476
6480
|
var MAX_BODY = 5e4;
|
|
5477
6481
|
var activeServers = /* @__PURE__ */ new Map();
|
|
5478
6482
|
var MIME_TYPES = {
|
|
@@ -5489,16 +6493,16 @@ var MIME_TYPES = {
|
|
|
5489
6493
|
function serveStatic(baseDir, req, res) {
|
|
5490
6494
|
const urlPath = (req.url || "/").split("?")[0];
|
|
5491
6495
|
let filePath = join6(baseDir, urlPath === "/" ? "index.html" : urlPath);
|
|
5492
|
-
if (!
|
|
6496
|
+
if (!resolve13(filePath).startsWith(resolve13(baseDir))) {
|
|
5493
6497
|
res.writeHead(403);
|
|
5494
6498
|
res.end("Forbidden");
|
|
5495
6499
|
return true;
|
|
5496
6500
|
}
|
|
5497
|
-
if (!
|
|
5498
|
-
const stat =
|
|
6501
|
+
if (!existsSync14(filePath)) return false;
|
|
6502
|
+
const stat = statSync3(filePath);
|
|
5499
6503
|
if (stat.isDirectory()) {
|
|
5500
6504
|
filePath = join6(filePath, "index.html");
|
|
5501
|
-
if (!
|
|
6505
|
+
if (!existsSync14(filePath)) return false;
|
|
5502
6506
|
}
|
|
5503
6507
|
const ext = extname(filePath);
|
|
5504
6508
|
const mime = MIME_TYPES[ext] || "application/octet-stream";
|
|
@@ -5551,8 +6555,8 @@ function registerHttpTools(registry, workspaceDir) {
|
|
|
5551
6555
|
if (activeServers.has(port)) {
|
|
5552
6556
|
return `Error: A server is already running on port ${port}. Stop it first with http_stop.`;
|
|
5553
6557
|
}
|
|
5554
|
-
const staticDir = directory ?
|
|
5555
|
-
if (staticDir && !
|
|
6558
|
+
const staticDir = directory ? resolve13(workspaceDir, directory) : void 0;
|
|
6559
|
+
if (staticDir && !existsSync14(staticDir)) {
|
|
5556
6560
|
return `Error: Directory not found: ${directory}`;
|
|
5557
6561
|
}
|
|
5558
6562
|
return new Promise((resolvePromise) => {
|
|
@@ -5688,8 +6692,8 @@ function registerHttpTools(registry, workspaceDir) {
|
|
|
5688
6692
|
}
|
|
5689
6693
|
|
|
5690
6694
|
// packages/runtime/src/tools/watch.ts
|
|
5691
|
-
import { watch as watch3, existsSync as
|
|
5692
|
-
import { join as join7, resolve as
|
|
6695
|
+
import { watch as watch3, existsSync as existsSync15, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "fs";
|
|
6696
|
+
import { join as join7, resolve as resolve14 } from "path";
|
|
5693
6697
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
5694
6698
|
var activeWatchers = /* @__PURE__ */ new Map();
|
|
5695
6699
|
function registerWatchTools(registry, workspaceDir, dataDir) {
|
|
@@ -5722,15 +6726,15 @@ function registerWatchTools(registry, workspaceDir, dataDir) {
|
|
|
5722
6726
|
if (activeWatchers.has(id)) {
|
|
5723
6727
|
return `Error: A watcher with id '${id}' already exists. Stop it first or use a different id.`;
|
|
5724
6728
|
}
|
|
5725
|
-
const absolutePath =
|
|
5726
|
-
if (!absolutePath.startsWith(
|
|
6729
|
+
const absolutePath = resolve14(workspaceDir, relPath);
|
|
6730
|
+
if (!absolutePath.startsWith(resolve14(workspaceDir))) {
|
|
5727
6731
|
return "Error: Path must be within the workspace directory.";
|
|
5728
6732
|
}
|
|
5729
|
-
if (!
|
|
6733
|
+
if (!existsSync15(absolutePath)) {
|
|
5730
6734
|
return `Error: Path not found: ${relPath}`;
|
|
5731
6735
|
}
|
|
5732
|
-
if (!
|
|
5733
|
-
|
|
6736
|
+
if (!existsSync15(eventsDir)) {
|
|
6737
|
+
mkdirSync10(eventsDir, { recursive: true });
|
|
5734
6738
|
}
|
|
5735
6739
|
try {
|
|
5736
6740
|
let debounceTimer = null;
|
|
@@ -5746,7 +6750,7 @@ function registerWatchTools(registry, workspaceDir, dataDir) {
|
|
|
5746
6750
|
};
|
|
5747
6751
|
const eventFile = join7(eventsDir, `watch-${id}-${Date.now()}.json`);
|
|
5748
6752
|
try {
|
|
5749
|
-
|
|
6753
|
+
writeFileSync6(eventFile, JSON.stringify(event, null, 2));
|
|
5750
6754
|
} catch {
|
|
5751
6755
|
}
|
|
5752
6756
|
});
|
|
@@ -5815,13 +6819,13 @@ ${lines.join("\n")}`;
|
|
|
5815
6819
|
}
|
|
5816
6820
|
|
|
5817
6821
|
// packages/runtime/src/tools/macos.ts
|
|
5818
|
-
import { execSync as
|
|
5819
|
-
import { resolve as
|
|
5820
|
-
import { mkdirSync as
|
|
6822
|
+
import { execSync as execSync7 } from "child_process";
|
|
6823
|
+
import { resolve as resolve15, normalize as normalize2 } from "path";
|
|
6824
|
+
import { mkdirSync as mkdirSync11, existsSync as existsSync16 } from "fs";
|
|
5821
6825
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
5822
6826
|
var MAX_OUTPUT4 = 5e4;
|
|
5823
6827
|
function shellExec(command, timeoutMs) {
|
|
5824
|
-
return
|
|
6828
|
+
return execSync7(command, {
|
|
5825
6829
|
timeout: timeoutMs,
|
|
5826
6830
|
encoding: "utf-8",
|
|
5827
6831
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -5932,7 +6936,7 @@ ${output || err.message}`;
|
|
|
5932
6936
|
async (params) => {
|
|
5933
6937
|
const content = params.content;
|
|
5934
6938
|
try {
|
|
5935
|
-
|
|
6939
|
+
execSync7("pbcopy", {
|
|
5936
6940
|
input: content,
|
|
5937
6941
|
timeout: 5e3,
|
|
5938
6942
|
encoding: "utf-8",
|
|
@@ -5995,26 +6999,26 @@ ${output || err.message}`;
|
|
|
5995
6999
|
try {
|
|
5996
7000
|
let savePath;
|
|
5997
7001
|
if (params.path) {
|
|
5998
|
-
savePath =
|
|
7002
|
+
savePath = resolve15(workspaceDir, params.path);
|
|
5999
7003
|
const normalizedSave = normalize2(savePath);
|
|
6000
7004
|
const normalizedWorkspace = normalize2(workspaceDir);
|
|
6001
7005
|
if (!normalizedSave.startsWith(normalizedWorkspace)) {
|
|
6002
7006
|
return "Error: path must be within the workspace.";
|
|
6003
7007
|
}
|
|
6004
7008
|
} else {
|
|
6005
|
-
const screenshotDir =
|
|
6006
|
-
if (!
|
|
6007
|
-
|
|
7009
|
+
const screenshotDir = resolve15(workspaceDir, "screenshots");
|
|
7010
|
+
if (!existsSync16(screenshotDir)) {
|
|
7011
|
+
mkdirSync11(screenshotDir, { recursive: true });
|
|
6008
7012
|
}
|
|
6009
|
-
savePath =
|
|
7013
|
+
savePath = resolve15(screenshotDir, `${randomUUID7()}.png`);
|
|
6010
7014
|
}
|
|
6011
|
-
const parentDir =
|
|
6012
|
-
if (!
|
|
6013
|
-
|
|
7015
|
+
const parentDir = resolve15(savePath, "..");
|
|
7016
|
+
if (!existsSync16(parentDir)) {
|
|
7017
|
+
mkdirSync11(parentDir, { recursive: true });
|
|
6014
7018
|
}
|
|
6015
7019
|
const args = params.region ? `screencapture -i ${escapeShellArg(savePath)}` : `screencapture -x ${escapeShellArg(savePath)}`;
|
|
6016
7020
|
shellExec(args, 15e3);
|
|
6017
|
-
if (!
|
|
7021
|
+
if (!existsSync16(savePath)) {
|
|
6018
7022
|
return "Screenshot cancelled or failed \u2014 no file was created.";
|
|
6019
7023
|
}
|
|
6020
7024
|
return `Screenshot saved: ${savePath}`;
|
|
@@ -6032,12 +7036,12 @@ function escapeAppleString(s) {
|
|
|
6032
7036
|
}
|
|
6033
7037
|
|
|
6034
7038
|
// packages/runtime/src/tools/data.ts
|
|
6035
|
-
import { execSync as
|
|
6036
|
-
import { resolve as
|
|
6037
|
-
import { mkdirSync as
|
|
7039
|
+
import { execSync as execSync8 } from "child_process";
|
|
7040
|
+
import { resolve as resolve16, normalize as normalize3, extname as extname2 } from "path";
|
|
7041
|
+
import { mkdirSync as mkdirSync12, existsSync as existsSync17 } from "fs";
|
|
6038
7042
|
var MAX_OUTPUT5 = 5e4;
|
|
6039
7043
|
function shellExec2(command, cwd, timeoutMs) {
|
|
6040
|
-
return
|
|
7044
|
+
return execSync8(command, {
|
|
6041
7045
|
cwd,
|
|
6042
7046
|
timeout: timeoutMs,
|
|
6043
7047
|
encoding: "utf-8",
|
|
@@ -6047,7 +7051,7 @@ function shellExec2(command, cwd, timeoutMs) {
|
|
|
6047
7051
|
});
|
|
6048
7052
|
}
|
|
6049
7053
|
function safePath(workspaceDir, filePath) {
|
|
6050
|
-
const resolved =
|
|
7054
|
+
const resolved = resolve16(workspaceDir, filePath);
|
|
6051
7055
|
const normalizedResolved = normalize3(resolved);
|
|
6052
7056
|
const normalizedWorkspace = normalize3(workspaceDir);
|
|
6053
7057
|
if (!normalizedResolved.startsWith(normalizedWorkspace)) {
|
|
@@ -6087,7 +7091,7 @@ function registerDataTools(registry, workspaceDir) {
|
|
|
6087
7091
|
const queryParams = params.params;
|
|
6088
7092
|
try {
|
|
6089
7093
|
const dbPath = safePath(workspaceDir, dbRelative);
|
|
6090
|
-
if (!
|
|
7094
|
+
if (!existsSync17(dbPath) && !query.trim().toUpperCase().startsWith("CREATE")) {
|
|
6091
7095
|
return `Error: database file not found: ${dbRelative}`;
|
|
6092
7096
|
}
|
|
6093
7097
|
let fullQuery = query;
|
|
@@ -6148,16 +7152,16 @@ function registerDataTools(registry, workspaceDir) {
|
|
|
6148
7152
|
try {
|
|
6149
7153
|
const sourcePath = safePath(workspaceDir, source);
|
|
6150
7154
|
const outputPath = safePath(workspaceDir, output);
|
|
6151
|
-
if (!
|
|
7155
|
+
if (!existsSync17(sourcePath)) {
|
|
6152
7156
|
return `Error: source not found: ${source}`;
|
|
6153
7157
|
}
|
|
6154
|
-
const outputParent =
|
|
6155
|
-
if (!
|
|
6156
|
-
|
|
7158
|
+
const outputParent = resolve16(outputPath, "..");
|
|
7159
|
+
if (!existsSync17(outputParent)) {
|
|
7160
|
+
mkdirSync12(outputParent, { recursive: true });
|
|
6157
7161
|
}
|
|
6158
7162
|
let command;
|
|
6159
7163
|
if (format === "tar.gz") {
|
|
6160
|
-
command = `tar czf ${escapeShellArg2(outputPath)} -C ${escapeShellArg2(
|
|
7164
|
+
command = `tar czf ${escapeShellArg2(outputPath)} -C ${escapeShellArg2(resolve16(sourcePath, ".."))} ${escapeShellArg2(sourcePath.split("/").pop())}`;
|
|
6161
7165
|
} else {
|
|
6162
7166
|
command = `zip -r ${escapeShellArg2(outputPath)} ${escapeShellArg2(source)}`;
|
|
6163
7167
|
}
|
|
@@ -6192,11 +7196,11 @@ function registerDataTools(registry, workspaceDir) {
|
|
|
6192
7196
|
try {
|
|
6193
7197
|
const archivePath = safePath(workspaceDir, archive);
|
|
6194
7198
|
const destPath = destination ? safePath(workspaceDir, destination) : workspaceDir;
|
|
6195
|
-
if (!
|
|
7199
|
+
if (!existsSync17(archivePath)) {
|
|
6196
7200
|
return `Error: archive not found: ${archive}`;
|
|
6197
7201
|
}
|
|
6198
|
-
if (!
|
|
6199
|
-
|
|
7202
|
+
if (!existsSync17(destPath)) {
|
|
7203
|
+
mkdirSync12(destPath, { recursive: true });
|
|
6200
7204
|
}
|
|
6201
7205
|
const ext = extname2(archivePath).toLowerCase();
|
|
6202
7206
|
const isGz = archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz");
|
|
@@ -6237,7 +7241,7 @@ ${trimmed}`.trim();
|
|
|
6237
7241
|
const filePath = params.path;
|
|
6238
7242
|
try {
|
|
6239
7243
|
const pdfPath = safePath(workspaceDir, filePath);
|
|
6240
|
-
if (!
|
|
7244
|
+
if (!existsSync17(pdfPath)) {
|
|
6241
7245
|
return `Error: PDF not found: ${filePath}`;
|
|
6242
7246
|
}
|
|
6243
7247
|
try {
|
|
@@ -6278,8 +7282,8 @@ function truncate2(text) {
|
|
|
6278
7282
|
}
|
|
6279
7283
|
|
|
6280
7284
|
// packages/runtime/src/tools/coding-agent.ts
|
|
6281
|
-
import { execSync as
|
|
6282
|
-
import { resolve as
|
|
7285
|
+
import { execSync as execSync9 } from "child_process";
|
|
7286
|
+
import { resolve as resolve17 } from "path";
|
|
6283
7287
|
var MAX_OUTPUT6 = 5e4;
|
|
6284
7288
|
function registerCodingAgentTools(registry, workspaceDir) {
|
|
6285
7289
|
registry.register(
|
|
@@ -6306,18 +7310,18 @@ function registerCodingAgentTools(registry, workspaceDir) {
|
|
|
6306
7310
|
async (params) => {
|
|
6307
7311
|
const task = params.task;
|
|
6308
7312
|
const timeoutSeconds = params.timeout || 300;
|
|
6309
|
-
const cwd = params.workdir ?
|
|
7313
|
+
const cwd = params.workdir ? resolve17(workspaceDir, params.workdir) : workspaceDir;
|
|
6310
7314
|
const homedir = process.env.HOME || "/root";
|
|
6311
7315
|
const extendedPath = `${homedir}/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:${process.env.PATH}`;
|
|
6312
7316
|
try {
|
|
6313
|
-
|
|
7317
|
+
execSync9("which claude", { stdio: "ignore", env: { ...process.env, PATH: extendedPath } });
|
|
6314
7318
|
} catch {
|
|
6315
7319
|
return "Error: 'claude' CLI not found. Install Claude Code first: https://docs.anthropic.com/en/docs/claude-code";
|
|
6316
7320
|
}
|
|
6317
7321
|
const escapedTask = task.replace(/'/g, "'\\''");
|
|
6318
7322
|
const command = `claude --dangerously-skip-permissions -p '${escapedTask}'`;
|
|
6319
7323
|
try {
|
|
6320
|
-
const output =
|
|
7324
|
+
const output = execSync9(command, {
|
|
6321
7325
|
cwd,
|
|
6322
7326
|
timeout: timeoutSeconds * 1e3,
|
|
6323
7327
|
encoding: "utf-8",
|
|
@@ -6348,22 +7352,22 @@ ${output || err.message}`;
|
|
|
6348
7352
|
}
|
|
6349
7353
|
|
|
6350
7354
|
// packages/runtime/src/tools/register.ts
|
|
6351
|
-
import { resolve as
|
|
6352
|
-
import { mkdirSync as
|
|
7355
|
+
import { resolve as resolve18 } from "path";
|
|
7356
|
+
import { mkdirSync as mkdirSync13, existsSync as existsSync18 } from "fs";
|
|
6353
7357
|
function registerAllTools(hivemindHome, config) {
|
|
6354
7358
|
const registry = new ToolRegistry();
|
|
6355
7359
|
if (config?.enabled === false) {
|
|
6356
7360
|
return registry;
|
|
6357
7361
|
}
|
|
6358
|
-
const workspaceDir =
|
|
6359
|
-
if (!
|
|
6360
|
-
|
|
7362
|
+
const workspaceDir = resolve18(hivemindHome, config?.workspace || "workspace");
|
|
7363
|
+
if (!existsSync18(workspaceDir)) {
|
|
7364
|
+
mkdirSync13(workspaceDir, { recursive: true });
|
|
6361
7365
|
}
|
|
6362
7366
|
registerShellTool(registry, workspaceDir);
|
|
6363
7367
|
registerFileTools(registry, workspaceDir);
|
|
6364
7368
|
registerWebTools(registry, { braveApiKey: config?.braveApiKey });
|
|
6365
7369
|
registerMemoryTools(registry, config?.memoryDaemonUrl || "http://localhost:3434");
|
|
6366
|
-
const dataDir =
|
|
7370
|
+
const dataDir = resolve18(hivemindHome, "data");
|
|
6367
7371
|
registerEventTools(registry, dataDir);
|
|
6368
7372
|
if (config?.configPath && !process.env.SPAWN_TASK) {
|
|
6369
7373
|
registerSpawnTools(registry, hivemindHome, dataDir, config.configPath);
|
|
@@ -6413,10 +7417,10 @@ function registerMessagingTools(registry, sesame) {
|
|
|
6413
7417
|
}
|
|
6414
7418
|
|
|
6415
7419
|
// packages/runtime/src/tools/skills-tools.ts
|
|
6416
|
-
import { existsSync as
|
|
6417
|
-
import { resolve as
|
|
7420
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync14, writeFileSync as writeFileSync7, rmSync } from "fs";
|
|
7421
|
+
import { resolve as resolve19 } from "path";
|
|
6418
7422
|
function registerSkillsTools(registry, skillsEngine, workspaceDir) {
|
|
6419
|
-
const skillsDir =
|
|
7423
|
+
const skillsDir = resolve19(workspaceDir, "skills");
|
|
6420
7424
|
registry.register(
|
|
6421
7425
|
"skill_list",
|
|
6422
7426
|
"List all loaded skills with their registered tools and status.",
|
|
@@ -6483,11 +7487,11 @@ ${lines.join("\n\n")}`;
|
|
|
6483
7487
|
const name = params.name;
|
|
6484
7488
|
const description = params.description;
|
|
6485
7489
|
const tools = params.tools;
|
|
6486
|
-
const skillDir =
|
|
6487
|
-
if (
|
|
7490
|
+
const skillDir = resolve19(skillsDir, name);
|
|
7491
|
+
if (existsSync19(skillDir)) {
|
|
6488
7492
|
return `Error: Skill directory "${name}" already exists. Use skill_reload to update it.`;
|
|
6489
7493
|
}
|
|
6490
|
-
|
|
7494
|
+
mkdirSync14(skillDir, { recursive: true });
|
|
6491
7495
|
const skillMd = `---
|
|
6492
7496
|
name: "${name}"
|
|
6493
7497
|
description: "${description}"
|
|
@@ -6497,7 +7501,7 @@ description: "${description}"
|
|
|
6497
7501
|
|
|
6498
7502
|
${description}
|
|
6499
7503
|
`;
|
|
6500
|
-
|
|
7504
|
+
writeFileSync7(resolve19(skillDir, "SKILL.md"), skillMd);
|
|
6501
7505
|
if (tools && tools.length > 0) {
|
|
6502
7506
|
const toolsDef = {
|
|
6503
7507
|
tools: tools.map((t) => ({
|
|
@@ -6507,7 +7511,7 @@ ${description}
|
|
|
6507
7511
|
command: t.command
|
|
6508
7512
|
}))
|
|
6509
7513
|
};
|
|
6510
|
-
|
|
7514
|
+
writeFileSync7(resolve19(skillDir, "tools.json"), JSON.stringify(toolsDef, null, 2) + "\n");
|
|
6511
7515
|
}
|
|
6512
7516
|
try {
|
|
6513
7517
|
await skillsEngine.loadSkill(name);
|
|
@@ -6560,8 +7564,8 @@ Path: ${skillDir}`;
|
|
|
6560
7564
|
},
|
|
6561
7565
|
async (params) => {
|
|
6562
7566
|
const name = params.name;
|
|
6563
|
-
const skillDir =
|
|
6564
|
-
if (!
|
|
7567
|
+
const skillDir = resolve19(skillsDir, name);
|
|
7568
|
+
if (!existsSync19(skillDir)) {
|
|
6565
7569
|
return `Error: Skill directory "${name}" does not exist.`;
|
|
6566
7570
|
}
|
|
6567
7571
|
try {
|
|
@@ -6580,13 +7584,13 @@ Path: ${skillDir}`;
|
|
|
6580
7584
|
}
|
|
6581
7585
|
|
|
6582
7586
|
// packages/runtime/src/pipeline.ts
|
|
6583
|
-
import { readFileSync as
|
|
6584
|
-
import { resolve as
|
|
7587
|
+
import { readFileSync as readFileSync15, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3 } from "fs";
|
|
7588
|
+
import { resolve as resolve20, dirname as dirname8 } from "path";
|
|
6585
7589
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6586
7590
|
var PACKAGE_VERSION = "unknown";
|
|
6587
7591
|
try {
|
|
6588
|
-
const __dirname2 =
|
|
6589
|
-
const pkg = JSON.parse(
|
|
7592
|
+
const __dirname2 = dirname8(fileURLToPath3(import.meta.url));
|
|
7593
|
+
const pkg = JSON.parse(readFileSync15(resolve20(__dirname2, "../package.json"), "utf-8"));
|
|
6590
7594
|
PACKAGE_VERSION = pkg.version ?? "unknown";
|
|
6591
7595
|
} catch {
|
|
6592
7596
|
}
|
|
@@ -6628,7 +7632,7 @@ function startHealthServer(port) {
|
|
|
6628
7632
|
return server;
|
|
6629
7633
|
}
|
|
6630
7634
|
function writePidFile(path) {
|
|
6631
|
-
|
|
7635
|
+
writeFileSync8(path, String(process.pid));
|
|
6632
7636
|
console.log(`[hivemind] PID file written: ${path}`);
|
|
6633
7637
|
}
|
|
6634
7638
|
function cleanupPidFile(path) {
|
|
@@ -6669,11 +7673,11 @@ async function startPipeline(configPath) {
|
|
|
6669
7673
|
console.log("[hivemind] Global context already exists in memory daemon");
|
|
6670
7674
|
}
|
|
6671
7675
|
}
|
|
6672
|
-
const requestLogger = new RequestLogger(
|
|
7676
|
+
const requestLogger = new RequestLogger(resolve20(dirname8(configPath), "data", "dashboard.db"));
|
|
6673
7677
|
startDashboardServer(requestLogger, config.memory);
|
|
6674
7678
|
const agent = new Agent(config);
|
|
6675
7679
|
agent.setRequestLogger(requestLogger);
|
|
6676
|
-
const hivemindHome = process.env.HIVEMIND_HOME ||
|
|
7680
|
+
const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
|
|
6677
7681
|
const toolRegistry = registerAllTools(hivemindHome, {
|
|
6678
7682
|
enabled: true,
|
|
6679
7683
|
workspace: config.agent.workspace || "workspace",
|
|
@@ -6681,7 +7685,7 @@ async function startPipeline(configPath) {
|
|
|
6681
7685
|
memoryDaemonUrl: config.memory.daemon_url,
|
|
6682
7686
|
configPath
|
|
6683
7687
|
});
|
|
6684
|
-
const workspaceDir =
|
|
7688
|
+
const workspaceDir = resolve20(hivemindHome, config.agent.workspace || "workspace");
|
|
6685
7689
|
const skillsEngine = new SkillsEngine(workspaceDir, toolRegistry);
|
|
6686
7690
|
await skillsEngine.loadAll();
|
|
6687
7691
|
registerSkillsTools(toolRegistry, skillsEngine, workspaceDir);
|
|
@@ -6689,7 +7693,50 @@ async function startPipeline(configPath) {
|
|
|
6689
7693
|
process.on("exit", () => skillsEngine.stopWatching());
|
|
6690
7694
|
agent.setToolRegistry(toolRegistry);
|
|
6691
7695
|
console.log(`[hivemind] Context manager initialized (active: ${agent.getActiveContext()})`);
|
|
6692
|
-
|
|
7696
|
+
if (config.auto_debug?.enabled) {
|
|
7697
|
+
const dataDir2 = resolve20(hivemindHome, "data");
|
|
7698
|
+
const autoDebugConfig = config.auto_debug;
|
|
7699
|
+
const logWatcher = new LogWatcher(
|
|
7700
|
+
{
|
|
7701
|
+
log_files: autoDebugConfig.log_files,
|
|
7702
|
+
severity_threshold: autoDebugConfig.severity_threshold,
|
|
7703
|
+
auto_pr: autoDebugConfig.auto_pr,
|
|
7704
|
+
repo: autoDebugConfig.repo,
|
|
7705
|
+
branch_prefix: autoDebugConfig.branch_prefix,
|
|
7706
|
+
max_concurrent_fixes: autoDebugConfig.max_concurrent_fixes,
|
|
7707
|
+
cooldown_minutes: autoDebugConfig.cooldown_minutes,
|
|
7708
|
+
notify_channel: autoDebugConfig.notify_channel
|
|
7709
|
+
},
|
|
7710
|
+
dataDir2
|
|
7711
|
+
);
|
|
7712
|
+
const autoDebugger = new AutoDebugger(
|
|
7713
|
+
{
|
|
7714
|
+
severity_threshold: autoDebugConfig.severity_threshold,
|
|
7715
|
+
auto_pr: autoDebugConfig.auto_pr,
|
|
7716
|
+
repo: autoDebugConfig.repo,
|
|
7717
|
+
branch_prefix: autoDebugConfig.branch_prefix,
|
|
7718
|
+
max_concurrent_fixes: autoDebugConfig.max_concurrent_fixes,
|
|
7719
|
+
cooldown_minutes: autoDebugConfig.cooldown_minutes
|
|
7720
|
+
},
|
|
7721
|
+
logWatcher.getRegistry(),
|
|
7722
|
+
config.llm,
|
|
7723
|
+
hivemindHome
|
|
7724
|
+
);
|
|
7725
|
+
const bgContext = {
|
|
7726
|
+
memoryClient: memory,
|
|
7727
|
+
workspacePath: hivemindHome,
|
|
7728
|
+
currentContext: "auto-debug",
|
|
7729
|
+
agentId: config.agent.name,
|
|
7730
|
+
sharedState: /* @__PURE__ */ new Map()
|
|
7731
|
+
};
|
|
7732
|
+
const processManager = new ProcessManager(bgContext);
|
|
7733
|
+
processManager.register(logWatcher);
|
|
7734
|
+
processManager.register(autoDebugger);
|
|
7735
|
+
processManager.start();
|
|
7736
|
+
process.on("exit", () => processManager.stop());
|
|
7737
|
+
console.log("[hivemind] Auto-debug processors started");
|
|
7738
|
+
}
|
|
7739
|
+
const dataDir = resolve20(hivemindHome, "data");
|
|
6693
7740
|
if (config.sesame.api_key) {
|
|
6694
7741
|
await startSesameLoop(config, agent, toolRegistry, dataDir);
|
|
6695
7742
|
} else {
|
|
@@ -6863,8 +7910,8 @@ ${response.content}
|
|
|
6863
7910
|
console.error("Error:", err.message);
|
|
6864
7911
|
}
|
|
6865
7912
|
});
|
|
6866
|
-
return new Promise((
|
|
6867
|
-
rl.on("close",
|
|
7913
|
+
return new Promise((resolve21) => {
|
|
7914
|
+
rl.on("close", resolve21);
|
|
6868
7915
|
});
|
|
6869
7916
|
}
|
|
6870
7917
|
async function runSpawnTask(config, configPath) {
|
|
@@ -6875,7 +7922,7 @@ async function runSpawnTask(config, configPath) {
|
|
|
6875
7922
|
const spawnDir = process.env.SPAWN_DIR;
|
|
6876
7923
|
console.log(`[spawn] Sub-agent starting (id: ${spawnId}, context: ${context})`);
|
|
6877
7924
|
const agent = new Agent(config, context);
|
|
6878
|
-
const hivemindHome = process.env.HIVEMIND_HOME ||
|
|
7925
|
+
const hivemindHome = process.env.HIVEMIND_HOME || resolve20(process.env.HOME || "/root", "hivemind");
|
|
6879
7926
|
const toolRegistry = registerAllTools(hivemindHome, {
|
|
6880
7927
|
enabled: true,
|
|
6881
7928
|
workspace: config.agent.workspace || "workspace",
|
|
@@ -6888,7 +7935,7 @@ async function runSpawnTask(config, configPath) {
|
|
|
6888
7935
|
const result = response.content;
|
|
6889
7936
|
console.log(`[spawn] Task completed (context: ${response.context})`);
|
|
6890
7937
|
if (spawnDir) {
|
|
6891
|
-
|
|
7938
|
+
writeFileSync8(resolve20(spawnDir, "result.txt"), result);
|
|
6892
7939
|
}
|
|
6893
7940
|
if (channelId && config.sesame.api_key) {
|
|
6894
7941
|
try {
|
|
@@ -6905,7 +7952,7 @@ async function runSpawnTask(config, configPath) {
|
|
|
6905
7952
|
const errorMsg = `[SPAWN ERROR] ${err.message}`;
|
|
6906
7953
|
console.error(`[spawn] ${errorMsg}`);
|
|
6907
7954
|
if (spawnDir) {
|
|
6908
|
-
|
|
7955
|
+
writeFileSync8(resolve20(spawnDir, "result.txt"), errorMsg);
|
|
6909
7956
|
}
|
|
6910
7957
|
process.exitCode = 1;
|
|
6911
7958
|
}
|
|
@@ -6936,20 +7983,20 @@ var WorkerServer = class {
|
|
|
6936
7983
|
}
|
|
6937
7984
|
/** Start listening. */
|
|
6938
7985
|
async start() {
|
|
6939
|
-
return new Promise((
|
|
7986
|
+
return new Promise((resolve21, reject) => {
|
|
6940
7987
|
this.server = createServer4((req, res) => this.handleRequest(req, res));
|
|
6941
7988
|
this.server.on("error", reject);
|
|
6942
|
-
this.server.listen(this.port, () =>
|
|
7989
|
+
this.server.listen(this.port, () => resolve21());
|
|
6943
7990
|
});
|
|
6944
7991
|
}
|
|
6945
7992
|
/** Stop the server. */
|
|
6946
7993
|
async stop() {
|
|
6947
|
-
return new Promise((
|
|
7994
|
+
return new Promise((resolve21) => {
|
|
6948
7995
|
if (!this.server) {
|
|
6949
|
-
|
|
7996
|
+
resolve21();
|
|
6950
7997
|
return;
|
|
6951
7998
|
}
|
|
6952
|
-
this.server.close(() =>
|
|
7999
|
+
this.server.close(() => resolve21());
|
|
6953
8000
|
});
|
|
6954
8001
|
}
|
|
6955
8002
|
getPort() {
|
|
@@ -7072,10 +8119,10 @@ var WorkerServer = class {
|
|
|
7072
8119
|
}
|
|
7073
8120
|
};
|
|
7074
8121
|
function readBody(req) {
|
|
7075
|
-
return new Promise((
|
|
8122
|
+
return new Promise((resolve21, reject) => {
|
|
7076
8123
|
const chunks = [];
|
|
7077
8124
|
req.on("data", (chunk) => chunks.push(chunk));
|
|
7078
|
-
req.on("end", () =>
|
|
8125
|
+
req.on("end", () => resolve21(Buffer.concat(chunks).toString("utf-8")));
|
|
7079
8126
|
req.on("error", reject);
|
|
7080
8127
|
});
|
|
7081
8128
|
}
|
|
@@ -7342,6 +8389,8 @@ async function startWorker(config) {
|
|
|
7342
8389
|
}
|
|
7343
8390
|
|
|
7344
8391
|
export {
|
|
8392
|
+
setLogLevel,
|
|
8393
|
+
createLogger,
|
|
7345
8394
|
getClaudeCodeOAuthToken,
|
|
7346
8395
|
LLMClient,
|
|
7347
8396
|
MemoryClient,
|
|
@@ -7362,6 +8411,10 @@ export {
|
|
|
7362
8411
|
SesameClient2,
|
|
7363
8412
|
HEALTH_PATH,
|
|
7364
8413
|
SkillsEngine,
|
|
8414
|
+
hashError,
|
|
8415
|
+
ErrorRegistry,
|
|
8416
|
+
LogWatcher,
|
|
8417
|
+
AutoDebugger,
|
|
7365
8418
|
startPipeline,
|
|
7366
8419
|
PRIMARY_ROUTES,
|
|
7367
8420
|
WORKER_ROUTES,
|
|
@@ -7411,4 +8464,4 @@ smol-toml/dist/index.js:
|
|
|
7411
8464
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
7412
8465
|
*)
|
|
7413
8466
|
*/
|
|
7414
|
-
//# sourceMappingURL=chunk-
|
|
8467
|
+
//# sourceMappingURL=chunk-ZM7RK5YV.js.map
|