godot ai 更新
This commit is contained in:
@@ -25,6 +25,8 @@ static var _cache: Dictionary = {} # exe_name -> resolved path (or "")
|
||||
static var _searched: Dictionary = {}
|
||||
|
||||
const _LOOKUP_TIMEOUT_MS := 3000
|
||||
const _TRACE_PATH_LIMIT := 16
|
||||
const _TRACE_PATH_MAX_CHARS := 512
|
||||
|
||||
|
||||
## Find any of the supplied exe names; returns the first hit.
|
||||
@@ -56,7 +58,15 @@ static func _find_one(exe_name: String, trace: Callable = Callable()) -> String:
|
||||
var cached: String = _cache.get(exe_name, "")
|
||||
_mutex.unlock()
|
||||
if already_searched:
|
||||
_trace_lookup(trace, "cache", "resolved" if not cached.is_empty() else "cached_miss", started, {}, true)
|
||||
_trace_lookup(
|
||||
trace,
|
||||
"cache",
|
||||
"resolved" if not cached.is_empty() else "cached_miss",
|
||||
started,
|
||||
{},
|
||||
true,
|
||||
cached,
|
||||
)
|
||||
return cached
|
||||
# `_resolve()` does FileAccess + bounded subprocess lookup (forks
|
||||
# `bash -lc` / `which`), which can take 100ms-1s. Holding the mutex across that
|
||||
@@ -76,12 +86,14 @@ static func _resolve(exe_name: String, trace: Callable = Callable()) -> String:
|
||||
var is_windows := OS.get_name() == "Windows"
|
||||
|
||||
# 1. Well-known locations
|
||||
var well_known_paths := PackedStringArray()
|
||||
for dir in _well_known_dirs():
|
||||
var full := dir.path_join(exe_name)
|
||||
well_known_paths.append(full)
|
||||
if FileAccess.file_exists(full):
|
||||
_trace_lookup(trace, "well_known", "resolved", started)
|
||||
_trace_lookup(trace, "well_known", "resolved", started, {}, false, full, well_known_paths)
|
||||
return full
|
||||
_trace_lookup(trace, "well_known", "no_match", started)
|
||||
_trace_lookup(trace, "well_known", "no_match", started, {}, false, "", well_known_paths)
|
||||
|
||||
# 2. Login shell lookup (Unix only)
|
||||
if not is_windows:
|
||||
@@ -97,22 +109,50 @@ static func _resolve(exe_name: String, trace: Callable = Callable()) -> String:
|
||||
if int(login_result.get("exit_code", -1)) == 0:
|
||||
var login_found: String = str(login_result.get("stdout", "")).strip_edges()
|
||||
if not login_found.is_empty() and FileAccess.file_exists(login_found):
|
||||
_trace_lookup(trace, "login_shell", "resolved", started, login_result)
|
||||
_trace_lookup(
|
||||
trace,
|
||||
"login_shell",
|
||||
"resolved",
|
||||
started,
|
||||
login_result,
|
||||
false,
|
||||
login_found,
|
||||
PackedStringArray([login_found]),
|
||||
)
|
||||
return login_found
|
||||
_trace_lookup(trace, "login_shell", _lookup_failure(login_result, false), started, login_result)
|
||||
|
||||
# 3. which / where with inherited PATH
|
||||
return _resolve_inherited_path(exe_name, is_windows, trace)
|
||||
|
||||
|
||||
## Resolve from the inherited PATH through a single lookup command. The
|
||||
## optional runner keeps the Windows/where failure modes deterministic in unit
|
||||
## tests without changing the production invocation.
|
||||
static func _resolve_inherited_path(
|
||||
exe_name: String,
|
||||
is_windows: bool,
|
||||
trace: Callable = Callable(),
|
||||
lookup_runner: Callable = Callable(),
|
||||
) -> String:
|
||||
var lookup := "where" if is_windows else "which"
|
||||
started = Time.get_ticks_msec()
|
||||
var result := McpCliExec.run(lookup, [exe_name], _LOOKUP_TIMEOUT_MS, false)
|
||||
var started := Time.get_ticks_msec()
|
||||
var result: Dictionary = {}
|
||||
if lookup_runner.is_valid():
|
||||
var injected: Variant = lookup_runner.call(lookup, exe_name)
|
||||
if injected is Dictionary:
|
||||
result = injected
|
||||
else:
|
||||
result = McpCliExec.run(lookup, [exe_name], _LOOKUP_TIMEOUT_MS, false)
|
||||
var lines := PackedStringArray(str(result.get("stdout", "")).split("\n"))
|
||||
if int(result.get("exit_code", -1)) == 0:
|
||||
var output := str(result.get("stdout", ""))
|
||||
var lines := PackedStringArray(output.split("\n"))
|
||||
var found := _pick_best_path(lines) if is_windows else lines[0].strip_edges()
|
||||
if not found.is_empty():
|
||||
_trace_lookup(trace, "inherited_path", "resolved", started, result)
|
||||
_trace_lookup(trace, "inherited_path", "resolved", started, result, false, found, lines)
|
||||
return found
|
||||
_trace_lookup(trace, "inherited_path", _lookup_failure(result, true), started, result)
|
||||
_trace_lookup(
|
||||
trace, "inherited_path", _lookup_failure(result, true), started, result, false, "", lines
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
@@ -128,7 +168,8 @@ static func _lookup_failure(result: Dictionary, absence_exit: bool) -> String:
|
||||
|
||||
static func _trace_lookup(
|
||||
trace: Callable, tier: String, status: String, started: int,
|
||||
result: Dictionary = {}, cache_hit := false,
|
||||
result: Dictionary = {}, cache_hit := false, resolved_path := "",
|
||||
candidate_paths: PackedStringArray = PackedStringArray(),
|
||||
) -> void:
|
||||
if not trace.is_valid():
|
||||
return
|
||||
@@ -141,9 +182,44 @@ static func _trace_lookup(
|
||||
"spawn_failed": bool(result.get("spawn_failed", false)),
|
||||
"cancelled": bool(result.get("cancelled", false)),
|
||||
"termination_failed": bool(result.get("termination_failed", false)),
|
||||
"resolved_path": _bounded_trace_path(resolved_path),
|
||||
"candidate_paths": _bounded_trace_paths(candidate_paths),
|
||||
})
|
||||
|
||||
|
||||
## Keep lookup diagnostics useful without retaining arbitrary command output or
|
||||
## environment values. Only absolute executable paths cross this boundary.
|
||||
static func _bounded_trace_paths(paths: PackedStringArray) -> Array[String]:
|
||||
var bounded: Array[String] = []
|
||||
for raw in paths:
|
||||
var path := _bounded_trace_path(raw)
|
||||
if path.is_empty() or bounded.has(path):
|
||||
continue
|
||||
bounded.append(path)
|
||||
if bounded.size() >= _TRACE_PATH_LIMIT:
|
||||
break
|
||||
return bounded
|
||||
|
||||
|
||||
static func _bounded_trace_path(raw: String) -> String:
|
||||
var path := raw.strip_edges()
|
||||
if not _is_traceable_path(path):
|
||||
return ""
|
||||
return path.left(_TRACE_PATH_MAX_CHARS)
|
||||
|
||||
|
||||
static func _is_traceable_path(path: String) -> bool:
|
||||
if path.is_empty() or path.contains("\n") or path.contains("\r"):
|
||||
return false
|
||||
if path.begins_with("/") or path.begins_with("\\\\"):
|
||||
return true
|
||||
return (
|
||||
path.length() >= 3
|
||||
and path.substr(1, 1) == ":"
|
||||
and (path.substr(2, 1) == "\\" or path.substr(2, 1) == "/")
|
||||
)
|
||||
|
||||
|
||||
## Executable extensions Windows' CreateProcessW can launch from a path
|
||||
## (after the cmd.exe wrap in `_cli_exec.gd`). Order is preference: `.exe`
|
||||
## is a native PE binary; `.cmd` / `.bat` go through the shell; `.com` is
|
||||
|
||||
Reference in New Issue
Block a user