Import 0.9.0 development baseline

This commit is contained in:
2026-03-04 22:21:18 +01:00
parent 576ec1e298
commit 81025922eb
20 changed files with 4683 additions and 726 deletions

298
code_executor.lua Normal file
View File

@@ -0,0 +1,298 @@
-- code_executor.lua
-- Secure Lua code execution for LLM-Connect / Smart Lua IDE
-- Privileges:
-- llm_dev → Sandbox + Whitelist, no persistent registrations
-- llm_root → Unrestricted execution + persistent registrations possible
local core = core
local M = {}
M.execution_history = {} -- per player: {timestamp, code_snippet, success, output/error}
local STARTUP_FILE = core.get_worldpath() .. DIR_DELIM .. "llm_startup.lua"
-- =============================================================
-- Helper functions
-- =============================================================
local function player_has_priv(name, priv)
local privs = core.get_player_privs(name) or {}
return privs[priv] == true
end
-- llm_root ist Superrolle: impliziert llm_dev und alle anderen
local function has_llm_priv(name, priv)
if player_has_priv(name, "llm_root") then return true end
return player_has_priv(name, priv)
end
local function is_llm_root(name)
return player_has_priv(name, "llm_root")
end
-- =============================================================
-- Sandbox environment (for normal llm_dev / llm users)
-- =============================================================
local function create_sandbox_env(player_name)
local safe_core = {
-- Logging & Chat
log = core.log,
chat_send_player = core.chat_send_player,
-- Secure read access
get_node = core.get_node,
get_node_or_nil = core.get_node_or_nil,
find_node_near = core.find_node_near,
find_nodes_in_area = core.find_nodes_in_area,
get_meta = core.get_meta,
get_player_by_name = core.get_player_by_name,
get_connected_players = core.get_connected_players,
}
-- Block registration functions (require restart)
local function blocked_registration(name)
return function(...)
core.log("warning", ("[code_executor] Blocked registration call: %s by %s"):format(name, player_name))
core.chat_send_player(player_name, "Registrations are forbidden in sandbox mode.\nOnly llm_root may execute these persistently.")
return nil
end
end
safe_core.register_node = blocked_registration("register_node")
safe_core.register_craftitem = blocked_registration("register_craftitem")
safe_core.register_tool = blocked_registration("register_tool")
safe_core.register_craft = blocked_registration("register_craft")
safe_core.register_entity = blocked_registration("register_entity")
-- Allowed dynamic registrations (very restricted)
safe_core.register_chatcommand = core.register_chatcommand
safe_core.register_on_chat_message = core.register_on_chat_message
-- Safe standard libraries (without dangerous functions)
local env = {
-- Lua basics
assert = assert,
error = error,
pairs = pairs,
ipairs = ipairs,
next = next,
select = select,
type = type,
tostring = tostring,
tonumber = tonumber,
unpack = table.unpack or unpack,
-- Safe string/table/math functions
string = { byte=string.byte, char=string.char, find=string.find, format=string.format,
gmatch=string.gmatch, gsub=string.gsub, len=string.len, lower=string.lower,
match=string.match, rep=string.rep, reverse=string.reverse, sub=string.sub,
upper=string.upper },
table = { concat=table.concat, insert=table.insert, remove=table.remove, sort=table.sort },
math = math,
-- Minetest-safe API
core = safe_core,
-- Redirect print
print = function(...) end, -- will be overwritten later
}
-- Output buffer with limit
local output_buffer = {}
local output_size = 0
local MAX_OUTPUT = 100000 -- ~100 KB
env.print = function(...)
local parts = {}
for i = 1, select("#", ...) do
parts[i] = tostring(select(i, ...))
end
local line = table.concat(parts, "\t")
if output_size + #line > MAX_OUTPUT then
table.insert(output_buffer, "\n[OUTPUT TRUNCATED 100 KB limit reached]")
return
end
table.insert(output_buffer, line)
output_size = output_size + #line
end
return env, output_buffer
end
-- =============================================================
-- Append persistent startup code (llm_root only)
-- =============================================================
local function append_to_startup(code, player_name)
local f, err = io.open(STARTUP_FILE, "a")
if not f then
core.log("error", ("[code_executor] Cannot open startup file: %s"):format(tostring(err)))
return false, err
end
f:write(("\n-- Added by %s at %s\n"):format(player_name, os.date("%Y-%m-%d %H:%M:%S")))
f:write(code)
f:write("\n\n")
f:close()
core.log("action", ("[code_executor] Appended code to %s by %s"):format(STARTUP_FILE, player_name))
return true
end
-- =============================================================
-- Main execution function
-- =============================================================
function M.execute(player_name, code, options)
options = options or {}
local result = { success = false }
if type(code) ~= "string" or code:trim() == "" then
result.error = "No or empty code provided"
return result
end
local is_root = is_llm_root(player_name)
local use_sandbox = options.sandbox ~= false
local allow_persist = options.allow_persist or is_root
-- Prüfen ob der Player überhaupt Ausführungsrechte hat
if not has_llm_priv(player_name, "llm_dev") then
result.error = "Missing privilege: llm_dev (or llm_root)"
return result
end
-- =============================================
-- 1. Compile
-- =============================================
local func, compile_err = loadstring(code, "=(llm_ide)")
if not func then
result.error = "Compile error: " .. tostring(compile_err)
core.log("warning", ("[code_executor] Compile failed for %s: %s"):format(player_name, result.error))
return result
end
-- =============================================
-- 2. Prepare environment & print redirection
-- =============================================
local output_buffer = {}
local env
if use_sandbox then
env, output_buffer = create_sandbox_env(player_name)
setfenv(func, env) -- Lua 5.1 compatibility (Luanti mostly uses LuaJIT)
else
-- Unrestricted mode → Careful!
if not is_root then
result.error = "Unrestricted execution only allowed for llm_root"
return result
end
-- Redirect print (without overwriting _G)
local old_print = print
print = function(...)
local parts = {}
for i = 1, select("#", ...) do parts[#parts+1] = tostring(select(i, ...)) end
local line = table.concat(parts, "\t")
table.insert(output_buffer, line)
end
end
-- =============================================
-- 3. Execute (with instruction limit)
-- =============================================
local ok, exec_res = pcall(function()
-- Instruction limit could be added here later (currently dummy)
return func()
end)
-- Reset print (if unrestricted)
if not use_sandbox then
print = old_print
end
-- =============================================
-- 4. Process result
-- =============================================
result.output = table.concat(output_buffer, "\n")
if ok then
result.success = true
result.return_value = exec_res
core.log("action", ("[code_executor] Success by %s (sandbox=%s)"):format(player_name, tostring(use_sandbox)))
else
result.error = "Runtime error: " .. tostring(exec_res)
core.log("warning", ("[code_executor] Execution failed for %s: %s"):format(player_name, result.error))
end
-- =============================================
-- 5. Check for registrations → Persistence?
-- =============================================
local has_registration = code:match("register_node%s*%(") or
code:match("register_tool%s*%(") or
code:match("register_craftitem%s*%(") or
code:match("register_entity%s*%(") or
code:match("register_craft%s*%(")
if has_registration then
if allow_persist and is_root then
local saved, save_err = append_to_startup(code, player_name)
if saved then
local msg = "Code with registrations saved to llm_startup.lua.\nWill be active after server restart."
core.chat_send_player(player_name, msg)
result.output = (result.output or "") .. "\n\n" .. msg
result.persisted = true
else
result.error = (result.error or "") .. "\nPersistence failed: " .. tostring(save_err)
end
else
local msg = "Code contains registrations (node/tool/...). \nOnly llm_root can execute these persistently (restart required)."
core.chat_send_player(player_name, msg)
result.error = (result.error or "") .. "\n" .. msg
result.success = false -- even if execution was ok
end
end
-- Save history
M.execution_history[player_name] = M.execution_history[player_name] or {}
table.insert(M.execution_history[player_name], {
timestamp = os.time(),
code = code:sub(1, 200) .. (code:len() > 200 and "..." or ""),
success = result.success,
output = result.output,
error = result.error,
})
return result
end
-- =============================================================
-- History functions
-- =============================================================
function M.get_history(player_name, limit)
limit = limit or 10
local hist = M.execution_history[player_name] or {}
local res = {}
local start = math.max(1, #hist - limit + 1)
for i = start, #hist do
res[#res+1] = hist[i]
end
return res
end
function M.clear_history(player_name)
M.execution_history[player_name] = nil
end
-- Cleanup
core.register_on_leaveplayer(function(player)
local name = player:get_player_name()
M.execution_history[name] = nil
end)
return M