sentoagent
Advanced tools
+1
-1
| { | ||
| "name": "sentoagent", | ||
| "version": "1.1.59", | ||
| "version": "1.1.61", | ||
| "description": "Agents sent to fight your battles. Self-improving AI agents powered by Claude Code.", | ||
@@ -5,0 +5,0 @@ "author": "Gabriel Gil", |
@@ -151,5 +151,48 @@ import fs from "fs"; | ||
| // ─── MCP bun-path self-heal ─── | ||
| // Newer Claude Code sanitizes PATH when spawning MCP servers, so any plugin | ||
| // whose .mcp.json uses bare "command": "bun" fails silently with | ||
| // "No such file or directory" and the channel bot appears offline. Patch | ||
| // every installed plugin's .mcp.json to the absolute bun path (idempotent). | ||
| function patchMcpBunPaths() { | ||
| const home = os.homedir(); | ||
| const root = path.join(home, ".claude/plugins/marketplaces"); | ||
| const bunAbs = path.join(home, ".bun/bin/bun"); | ||
| if (!fs.existsSync(root)) return 0; | ||
| if (!fs.existsSync(bunAbs)) { log.warn(`bun not found at ${bunAbs}, skipping .mcp.json patch`); return 0; } | ||
| let touched = 0; | ||
| for (const mp of fs.readdirSync(root)) { | ||
| const ext = path.join(root, mp, "external_plugins"); | ||
| if (!fs.existsSync(ext)) continue; | ||
| for (const pl of fs.readdirSync(ext)) { | ||
| const mcpPath = path.join(ext, pl, ".mcp.json"); | ||
| if (!fs.existsSync(mcpPath)) continue; | ||
| try { | ||
| const data = JSON.parse(fs.readFileSync(mcpPath, "utf-8")); | ||
| let changed = false; | ||
| for (const name of Object.keys(data.mcpServers || {})) { | ||
| const srv = data.mcpServers[name]; | ||
| if (srv && srv.command === "bun") { srv.command = bunAbs; changed = true; } | ||
| } | ||
| if (changed) { | ||
| fs.writeFileSync(mcpPath, JSON.stringify(data, null, 2)); | ||
| log.success(`MCP bun-path: patched ${mp}/${pl}`); | ||
| touched++; | ||
| } | ||
| } catch (e) { log.warn(`MCP bun-path: skip ${mcpPath} (${e.message})`); } | ||
| } | ||
| } | ||
| return touched; | ||
| } | ||
| // ─── Main export ─── | ||
| export async function patchChannels(config) { | ||
| // Defensive: ensure every plugin's .mcp.json has an absolute bun path. | ||
| // Independent of channel type — fixes ALL MCP-based plugins. | ||
| log.step("Ensuring absolute bun paths in plugin .mcp.json files..."); | ||
| const n = patchMcpBunPaths(); | ||
| if (n === 0) log.success("All plugin .mcp.json files already use absolute bun paths"); | ||
| // Discord patches | ||
@@ -156,0 +199,0 @@ if (config.channelType === "discord" || config.patchAll) { |
@@ -389,2 +389,42 @@ export function renderGuardian(config) { | ||
| // ─── MCP bun-path self-heal ─────────────────────────────────────────────── | ||
| // Newer Claude Code sanitizes PATH when spawning MCP servers, so any plugin | ||
| // whose .mcp.json uses bare "command": "bun" fails silently with | ||
| // "No such file or directory" — the MCP never starts and the channel bot | ||
| // appears offline. Patch every plugin's .mcp.json to use the absolute path | ||
| // to bun (idempotent). Runs alongside checkAndReapplyPatches so it self- | ||
| // heals on every restart, regardless of what Claude Code does upstream. | ||
| function patchMcpBunPaths() { | ||
| const root = HOME + '/.claude/plugins/marketplaces'; | ||
| if (!fs.existsSync(root)) return; | ||
| const bunAbs = HOME + '/.bun/bin/bun'; | ||
| if (!fs.existsSync(bunAbs)) { log('MCP bun-path: bun not found at ' + bunAbs + ', skipping'); return; } | ||
| let touched = 0; | ||
| try { | ||
| for (const mp of fs.readdirSync(root)) { | ||
| const ext = root + '/' + mp + '/external_plugins'; | ||
| if (!fs.existsSync(ext)) continue; | ||
| for (const pl of fs.readdirSync(ext)) { | ||
| const mcp = ext + '/' + pl + '/.mcp.json'; | ||
| if (!fs.existsSync(mcp)) continue; | ||
| try { | ||
| const raw = fs.readFileSync(mcp, 'utf-8'); | ||
| const data = JSON.parse(raw); | ||
| let changed = false; | ||
| for (const name of Object.keys(data.mcpServers || {})) { | ||
| const srv = data.mcpServers[name]; | ||
| if (srv && srv.command === 'bun') { srv.command = bunAbs; changed = true; } | ||
| } | ||
| if (changed) { | ||
| fs.writeFileSync(mcp, JSON.stringify(data, null, 2)); | ||
| log('MCP bun-path: patched ' + mp + '/' + pl); | ||
| touched++; | ||
| } | ||
| } catch (e) { log('MCP bun-path: skip ' + mcp + ' (' + e.message + ')'); } | ||
| } | ||
| } | ||
| } catch (e) { log('MCP bun-path scan error: ' + e.message); } | ||
| if (touched) log('MCP bun-path: ' + touched + ' file(s) updated'); | ||
| } | ||
| // ─── Restart ─── | ||
@@ -410,3 +450,3 @@ function restart() { | ||
| catch (e) { log('Failed: ' + e.message); } | ||
| setTimeout(() => { checkAndReapplyPatches(); }, 10000); | ||
| setTimeout(() => { checkAndReapplyPatches(); patchMcpBunPaths(); }, 10000); | ||
| }, 3000); | ||
@@ -514,2 +554,30 @@ }, 3000); | ||
| // AUTOCOMPACT v1: prevent agents from going unusable at high context. When | ||
| // tmux shows >= 80% context used, snapshot the recent buffer to memory/ | ||
| // (so the agent has a paper trail of what was happening), then fire /compact. | ||
| // Throttled to once per 15 min so slow-clearing compact does not re-trigger. | ||
| // Wrapped so any bug here cannot break the rest of the guardian loop. | ||
| try { | ||
| const ctxMatch = o.match(/(\\d+)% context used/); | ||
| if (ctxMatch) { | ||
| const ctxPct = parseInt(ctxMatch[1], 10); | ||
| const now = Date.now(); | ||
| const lastCompact = s.lastCompact || 0; | ||
| if (ctxPct >= 80 && (now - lastCompact) > 15 * 60 * 1000) { | ||
| log('AUTOCOMPACT: context at ' + ctxPct + '% — capturing snapshot'); | ||
| try { | ||
| const snap = execFileSync('tmux', ['capture-pane', '-t', SESSION, '-p', '-S', '-200'], { encoding: 'utf8', timeout: 5000 }); | ||
| const tsName = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); | ||
| const fname = process.env.HOME + '/workspace/memory/autocompact-' + tsName + '.log'; | ||
| fs.writeFileSync(fname, '# Pre-compact snapshot\\n# Session: ' + SESSION + '\\n# Context: ' + ctxPct + '%\\n# Time: ' + new Date().toISOString() + '\\n# Reason: auto-fired by guardian at >= 80% context\\n\\n' + snap); | ||
| log('AUTOCOMPACT: snapshot saved to ' + fname); | ||
| } catch (snapE) { log('AUTOCOMPACT snapshot error (non-fatal): ' + (snapE && snapE.message)); } | ||
| log('AUTOCOMPACT: firing /compact'); | ||
| execFileSync('tmux', ['send-keys', '-t', SESSION, '/compact', 'Enter'], { timeout: 5000 }); | ||
| s.lastCompact = now; | ||
| sv(s); | ||
| } | ||
| } | ||
| } catch (e) { log('AUTOCOMPACT error (non-fatal): ' + (e && e.message)); } | ||
| // Check for permission prompts — AUTO-ACCEPT immediately | ||
@@ -516,0 +584,0 @@ // Agents run with --dangerously-skip-permissions (user consented to full autonomy) |
@@ -12,5 +12,11 @@ export function renderWatchdog(config) { | ||
| # ─── Guardian health check ─── | ||
| # If Guardian is not running, relaunch it (may have exited after auto-update) | ||
| if ! pgrep -u "$(whoami)" -f "guardian.mjs" > /dev/null 2>&1; then | ||
| # ─── Guardian health check (DEDUP v1) ─── | ||
| # Count guardians. Zero = relaunch. One = healthy. >1 = kill extras (keep oldest PID). | ||
| GUARDIAN_PIDS=\$(pgrep -u "$(whoami)" -f "guardian.mjs" 2>/dev/null) | ||
| GUARDIAN_COUNT=\$(echo -n "\$GUARDIAN_PIDS" | grep -c . || true) | ||
| if [ "\$GUARDIAN_COUNT" -gt 1 ]; then | ||
| EXTRAS=\$(echo "\$GUARDIAN_PIDS" | sort -n | tail -n +2) | ||
| echo "$(date): \$GUARDIAN_COUNT guardians running. Killing extras: \$EXTRAS" >> "\$LOG" | ||
| echo "\$EXTRAS" | xargs -r kill -9 2>/dev/null | ||
| elif [ "\$GUARDIAN_COUNT" -eq 0 ]; then | ||
| echo "$(date): Guardian not running. Relaunching..." >> "\$LOG" | ||
@@ -17,0 +23,0 @@ cd "\$HOME/workspace" && nohup node guardian.mjs >> memory/guardian.log 2>&1 & |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 5 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 4 instances in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
195245
3.17%4110
2.8%49
2.08%