feat(tui): infer workspace agent when launching TUI (#39591)

Merged via squash.

Prepared head SHA: 23533e24c4
Co-authored-by: arceus77-7 <261276524+arceus77-7@users.noreply.github.com>
Co-authored-by: altaywtf <9790196+altaywtf@users.noreply.github.com>
Reviewed-by: @altaywtf
This commit is contained in:
arceus77-7
2026-03-08 06:31:11 -04:00
committed by GitHub
parent f4c4856254
commit 492fe679a7
6 changed files with 231 additions and 3 deletions

View File

@@ -1,3 +1,4 @@
import fs from "node:fs";
import path from "node:path";
import type { OpenClawConfig } from "../config/config.js";
import { resolveAgentModelFallbackValues } from "../config/model-input.js";
@@ -270,6 +271,62 @@ export function resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) {
return stripNullBytes(path.join(stateDir, `workspace-${id}`));
}
function normalizePathForComparison(input: string): string {
const resolved = path.resolve(stripNullBytes(resolveUserPath(input)));
let normalized = resolved;
// Prefer realpath when available to normalize aliases/symlinks (for example /tmp -> /private/tmp)
// and canonical path case without forcing case-folding on case-sensitive macOS volumes.
try {
normalized = fs.realpathSync.native(resolved);
} catch {
// Keep lexical path for non-existent directories.
}
if (process.platform === "win32") {
return normalized.toLowerCase();
}
return normalized;
}
function isPathWithinRoot(candidatePath: string, rootPath: string): boolean {
const relative = path.relative(rootPath, candidatePath);
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
}
export function resolveAgentIdsByWorkspacePath(
cfg: OpenClawConfig,
workspacePath: string,
): string[] {
const normalizedWorkspacePath = normalizePathForComparison(workspacePath);
const ids = listAgentIds(cfg);
const matches: Array<{ id: string; workspaceDir: string; order: number }> = [];
for (let index = 0; index < ids.length; index += 1) {
const id = ids[index];
const workspaceDir = normalizePathForComparison(resolveAgentWorkspaceDir(cfg, id));
if (!isPathWithinRoot(normalizedWorkspacePath, workspaceDir)) {
continue;
}
matches.push({ id, workspaceDir, order: index });
}
matches.sort((left, right) => {
const workspaceLengthDelta = right.workspaceDir.length - left.workspaceDir.length;
if (workspaceLengthDelta !== 0) {
return workspaceLengthDelta;
}
return left.order - right.order;
});
return matches.map((entry) => entry.id);
}
export function resolveAgentIdByWorkspacePath(
cfg: OpenClawConfig,
workspacePath: string,
): string | undefined {
return resolveAgentIdsByWorkspacePath(cfg, workspacePath)[0];
}
export function resolveAgentDir(cfg: OpenClawConfig, agentId: string) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim();