@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.
- package/README.md +2 -1
- package/config/default.toml +18 -0
- package/dist/{chunk-NCJW2AOK.js → chunk-BHCDOHSK.js} +3 -3
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/{chunk-OVRYYS5I.js → chunk-DPLCEMEC.js} +2 -2
- package/dist/{chunk-XQZU3ULO.js → chunk-FBQBBAPZ.js} +2 -2
- package/dist/{chunk-XBJGLZ7V.js → chunk-FK6WYXRM.js} +79 -2
- package/dist/chunk-FK6WYXRM.js.map +1 -0
- package/dist/{chunk-LJHJGDKY.js → chunk-ICSJNKI6.js} +62 -2
- package/dist/chunk-ICSJNKI6.js.map +1 -0
- package/dist/{chunk-HDYEAKN5.js → chunk-IXBIAX76.js} +4 -34
- package/dist/chunk-IXBIAX76.js.map +1 -0
- package/dist/{chunk-OG6GPQDK.js → chunk-M3A2WRXM.js} +1765 -189
- package/dist/chunk-M3A2WRXM.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 +2 -1
- package/dist/commands/watchdog.js +4 -3
- package/dist/dashboard.html +873 -131
- package/dist/index.js +14 -5
- package/dist/main.js +376 -7
- package/dist/main.js.map +1 -1
- package/dist/start.js +2 -1
- package/dist/start.js.map +1 -1
- package/install.sh +162 -0
- package/package.json +24 -23
- package/packages/memory/Cargo.lock +6480 -0
- package/packages/memory/Cargo.toml +21 -0
- package/packages/memory/src/src/context.rs +179 -0
- package/packages/memory/src/src/embeddings.rs +51 -0
- package/packages/memory/src/src/main.rs +887 -0
- package/packages/memory/src/src/promotion.rs +808 -0
- package/packages/memory/src/src/scoring.rs +142 -0
- package/packages/memory/src/src/store.rs +460 -0
- package/packages/memory/src/src/tasks.rs +321 -0
- package/.pnpmrc.json +0 -1
- package/DASHBOARD-PLAN.md +0 -206
- package/MEMORY-ENHANCEMENT-PLAN.md +0 -211
- package/TOOL-USE-DESIGN.md +0 -173
- package/dist/chunk-HDYEAKN5.js.map +0 -1
- package/dist/chunk-LJHJGDKY.js.map +0 -1
- package/dist/chunk-OG6GPQDK.js.map +0 -1
- package/dist/chunk-XBJGLZ7V.js.map +0 -1
- package/docs/TOOL-PARITY-PLAN.md +0 -191
- package/src/memory/dashboard-integration.ts +0 -295
- package/src/memory/index.ts +0 -187
- package/src/memory/performance-test.ts +0 -208
- package/src/memory/processors/agent-sync.ts +0 -312
- package/src/memory/processors/command-learner.ts +0 -298
- package/src/memory/processors/memory-api-client.ts +0 -105
- package/src/memory/processors/message-flow-integration.ts +0 -168
- package/src/memory/processors/research-digester.ts +0 -204
- package/test-caitlin-access.md +0 -11
- /package/dist/{chunk-NCJW2AOK.js.map → chunk-BHCDOHSK.js.map} +0 -0
- /package/dist/{chunk-OVRYYS5I.js.map → chunk-DPLCEMEC.js.map} +0 -0
- /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((
|
|
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
|
-
|
|
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
|
|
3598
|
-
import { dirname as
|
|
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 =
|
|
3606
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
4697
|
+
if (!existsSync9(this.logPath)) return [];
|
|
3690
4698
|
try {
|
|
3691
|
-
const lines =
|
|
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
|
|
3709
|
-
import { resolve as
|
|
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 =
|
|
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,
|
|
4724
|
+
for (const dir of [__dirname, resolve7(__dirname, "../src")]) {
|
|
3717
4725
|
try {
|
|
3718
|
-
spaHtml =
|
|
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
|
-
|
|
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
|
|
3747
|
-
|
|
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(
|
|
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
|
|
3821
|
-
if (method === "
|
|
3822
|
-
const
|
|
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
|
|
3894
|
-
import { resolve as
|
|
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 ?
|
|
5022
|
+
const cwd = params.workdir ? resolve8(workspaceDir, params.workdir) : workspaceDir;
|
|
3922
5023
|
try {
|
|
3923
|
-
const output =
|
|
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
|
|
3949
|
-
import { resolve as
|
|
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 (!
|
|
5076
|
+
if (!existsSync10(filePath)) {
|
|
3976
5077
|
return `Error: File not found: ${filePath}`;
|
|
3977
5078
|
}
|
|
3978
5079
|
try {
|
|
3979
|
-
let content =
|
|
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 =
|
|
4017
|
-
if (!
|
|
4018
|
-
|
|
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 (!
|
|
5149
|
+
if (!existsSync10(filePath)) {
|
|
4049
5150
|
return `Error: File not found: ${filePath}`;
|
|
4050
5151
|
}
|
|
4051
5152
|
try {
|
|
4052
|
-
const content =
|
|
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
|
-
|
|
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:
|
|
5181
|
+
const { readdirSync: readdirSync5, statSync: statSync4 } = await import("fs");
|
|
4081
5182
|
const dirPath = params.path ? resolvePath(workspaceDir, params.path) : workspaceDir;
|
|
4082
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
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 (!
|
|
4367
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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
|
|
4519
|
-
import { join as join5, resolve as
|
|
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 (!
|
|
4525
|
-
|
|
5625
|
+
if (!existsSync12(spawnDir)) {
|
|
5626
|
+
mkdirSync8(spawnDir, { recursive: true });
|
|
4526
5627
|
}
|
|
4527
|
-
const hivemindBin =
|
|
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
|
-
|
|
4561
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
5772
|
+
if (existsSync12(resultPath)) {
|
|
4672
5773
|
try {
|
|
4673
|
-
const result =
|
|
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
|
-
|
|
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
|
|
4797
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
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
|
|
5034
|
-
import { mkdirSync as
|
|
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({
|
|
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 =
|
|
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
|
|
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
|
-
"
|
|
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: [
|
|
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
|
|
6281
|
+
description: "CSS selector for element-based actions."
|
|
5105
6282
|
},
|
|
5106
6283
|
text: {
|
|
5107
6284
|
type: "string",
|
|
5108
|
-
description: "Text to type
|
|
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
|
|
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
|
-
|
|
5142
|
-
|
|
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 (!
|
|
5162
|
-
|
|
6414
|
+
if (!existsSync13(screenshotDir)) {
|
|
6415
|
+
mkdirSync9(screenshotDir, { recursive: true });
|
|
5163
6416
|
}
|
|
5164
6417
|
const filename = `${randomUUID5()}.png`;
|
|
5165
|
-
const filepath =
|
|
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
|
-
|
|
5174
|
-
|
|
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}.
|
|
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
|
|
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
|
|
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
|
|
5475
|
-
import { join as join6, extname, resolve as
|
|
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 (!
|
|
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 (!
|
|
5498
|
-
const stat =
|
|
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 (!
|
|
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 ?
|
|
5555
|
-
if (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
|
|
5692
|
-
import { join as join7, resolve as
|
|
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 =
|
|
5726
|
-
if (!absolutePath.startsWith(
|
|
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 (!
|
|
7256
|
+
if (!existsSync15(absolutePath)) {
|
|
5730
7257
|
return `Error: Path not found: ${relPath}`;
|
|
5731
7258
|
}
|
|
5732
|
-
if (!
|
|
5733
|
-
|
|
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
|
-
|
|
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
|
|
5819
|
-
import { resolve as
|
|
5820
|
-
import { mkdirSync as
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
6006
|
-
if (!
|
|
6007
|
-
|
|
7532
|
+
const screenshotDir = resolve15(workspaceDir, "screenshots");
|
|
7533
|
+
if (!existsSync16(screenshotDir)) {
|
|
7534
|
+
mkdirSync11(screenshotDir, { recursive: true });
|
|
6008
7535
|
}
|
|
6009
|
-
savePath =
|
|
7536
|
+
savePath = resolve15(screenshotDir, `${randomUUID7()}.png`);
|
|
6010
7537
|
}
|
|
6011
|
-
const parentDir =
|
|
6012
|
-
if (!
|
|
6013
|
-
|
|
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 (!
|
|
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
|
|
6036
|
-
import { resolve as
|
|
6037
|
-
import { mkdirSync as
|
|
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
|
|
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 =
|
|
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 (!
|
|
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 (!
|
|
7678
|
+
if (!existsSync17(sourcePath)) {
|
|
6152
7679
|
return `Error: source not found: ${source}`;
|
|
6153
7680
|
}
|
|
6154
|
-
const outputParent =
|
|
6155
|
-
if (!
|
|
6156
|
-
|
|
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(
|
|
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 (!
|
|
7722
|
+
if (!existsSync17(archivePath)) {
|
|
6196
7723
|
return `Error: archive not found: ${archive}`;
|
|
6197
7724
|
}
|
|
6198
|
-
if (!
|
|
6199
|
-
|
|
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 (!
|
|
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
|
|
6282
|
-
import { resolve as
|
|
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 ?
|
|
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
|
-
|
|
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 =
|
|
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
|
|
6352
|
-
import { mkdirSync as
|
|
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 =
|
|
6359
|
-
if (!
|
|
6360
|
-
|
|
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 =
|
|
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
|
|
6417
|
-
import { resolve as
|
|
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 =
|
|
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 =
|
|
6487
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
6564
|
-
if (!
|
|
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
|
|
6584
|
-
import { resolve as
|
|
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 =
|
|
6589
|
-
const pkg = JSON.parse(
|
|
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
|
-
|
|
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(
|
|
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 ||
|
|
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 =
|
|
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
|
-
|
|
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((
|
|
6867
|
-
rl.on("close",
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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, () =>
|
|
8512
|
+
this.server.listen(this.port, () => resolve21());
|
|
6943
8513
|
});
|
|
6944
8514
|
}
|
|
6945
8515
|
/** Stop the server. */
|
|
6946
8516
|
async stop() {
|
|
6947
|
-
return new Promise((
|
|
8517
|
+
return new Promise((resolve21) => {
|
|
6948
8518
|
if (!this.server) {
|
|
6949
|
-
|
|
8519
|
+
resolve21();
|
|
6950
8520
|
return;
|
|
6951
8521
|
}
|
|
6952
|
-
this.server.close(() =>
|
|
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
|
|
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
|
|
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
|
|
7075
|
-
return new Promise((
|
|
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", () =>
|
|
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-
|
|
8990
|
+
//# sourceMappingURL=chunk-M3A2WRXM.js.map
|