///|
fn moon_allowed_workspace_files() -> Array[String] {
[
"AGENTS.md", "SOUL.md", "USER.md", "IDENTITY.md", "TOOLS.md", "MEMORY.md", "HEARTBEAT.md",
"BOOTSTRAP.md", "BOOT.md",
]
}
///|
fn moon_validate_workspace_filename(filename : String) -> Bool {
moon_allowed_workspace_files().contains(filename)
}
///|
fn moon_validate_daily_memory_filename(filename : String) -> Bool {
filename.length() == 13 &&
filename.has_suffix(".md") &&
moon_ascii_digit_at(filename, 0) &&
moon_ascii_digit_at(filename, 1) &&
moon_ascii_digit_at(filename, 2) &&
moon_ascii_digit_at(filename, 3) &&
filename[4] == '-' &&
moon_ascii_digit_at(filename, 5) &&
moon_ascii_digit_at(filename, 6) &&
filename[7] == '-' &&
moon_ascii_digit_at(filename, 8) &&
moon_ascii_digit_at(filename, 9) &&
filename[10] == '.'
}
///|
fn moon_daily_memory_list_filename(filename : String) -> Bool {
filename.has_suffix(".md")
}
///|
fn moon_daily_memory_date(filename : String) -> String {
if filename.has_suffix(".md") && filename.length() >= 3 {
filename[:filename.length() - 3].to_owned()
} else if filename.length() >= 10 {
filename[:10].to_owned()
} else {
filename
}
}
///|
fn moon_workspace_file_path(filename : String) -> String {
@pathx.join(moon_openclaw_workspace_dir(), filename)
}
///|
fn moon_daily_memory_file_path(filename : String) -> String {
@pathx.join(moon_openclaw_memory_dir(), filename)
}
///|
fn moon_file_mtime(path : String) -> Int64 {
let stat = @fsx.stat(path) catch { _ => return 0L }
stat.mtime()
}
///|
async fn moon_daily_memory_info_json(path : String, filename : String) -> Json {
let content = @fsx.read_file(path) catch { _ => "" }
let modified_at = moon_file_mtime(path)
{
"filename": Json::string(filename),
"date": Json::string(moon_daily_memory_date(filename)),
"sizeBytes": Json::number(content.length().to_double()),
"modifiedAt": Json::number(modified_at.to_double()),
"preview": Json::string(moon_preview(content, 200)),
}.to_json()
}
///|
async fn moon_list_daily_memory_files_json() -> Json {
let memory_dir = moon_openclaw_memory_dir()
if !@fsx.exists(memory_dir) {
return Json::array([])
}
let filenames : Array[String] = []
let rows_by_filename : Map[String, Json] = {}
let rows : Array[Json] = []
let entries = @fsx.list_directory(memory_dir) catch {
_ => return Json::array([])
}
for entry in entries {
if entry.kind is Regular && moon_daily_memory_list_filename(entry.name) {
filenames.push(entry.name)
rows_by_filename[entry.name] = moon_daily_memory_info_json(
entry.path,
entry.name,
)
}
}
filenames.sort()
let mut index = filenames.length() - 1
while index >= 0 {
guard rows_by_filename.get(filenames[index]) is Some(row) else {
index = index - 1
continue
}
rows.push(row)
index = index - 1
}
Json::array(rows)
}
///|
async fn moon_search_daily_memory_files_json(query : String) -> Json {
let trimmed = query.trim().to_owned()
let memory_dir = moon_openclaw_memory_dir()
if trimmed.is_empty() || !@fsx.exists(memory_dir) {
return Json::array([])
}
let filenames : Array[String] = []
let rows_by_filename : Map[String, Json] = {}
let rows : Array[Json] = []
let entries = @fsx.list_directory(memory_dir) catch {
_ => return Json::array([])
}
for entry in entries {
if !(entry.kind is Regular) || !moon_daily_memory_list_filename(entry.name) {
continue
}
let content = @fsx.read_file(entry.path) catch { _ => "" }
let date = moon_daily_memory_date(entry.name)
let (content_count, first_pos) = moon_count_case_insensitive_matches(
content, trimmed,
)
let (date_count, _) = moon_count_case_insensitive_matches(date, trimmed)
if content_count == 0 && date_count == 0 {
continue
}
let snippet = match first_pos {
Some(pos) => moon_search_snippet(content, pos)
None => moon_preview(content, 120)
}
let modified_at = moon_file_mtime(entry.path)
filenames.push(entry.name)
rows_by_filename[entry.name] = {
"filename": Json::string(entry.name),
"date": Json::string(date),
"sizeBytes": Json::number(content.length().to_double()),
"modifiedAt": Json::number(modified_at.to_double()),
"snippet": Json::string(snippet),
"matchCount": Json::number((content_count + date_count).to_double()),
}.to_json()
}
filenames.sort()
let mut index = filenames.length() - 1
while index >= 0 {
guard rows_by_filename.get(filenames[index]) is Some(row) else {
index = index - 1
continue
}
rows.push(row)
index = index - 1
}
Json::array(rows)
}
///|
async fn moon_env_conflicts_json_with_shell_files(
app : String,
shell_files : Array[String],
) -> Json {
let conflicts : Array[Json] = []
for key in moon_env_conflict_keys(moon_normalize_app_type(app)) {
match getenv_or_none(key) {
Some(value) if !value.trim().is_empty() =>
conflicts.push(
{
"varName": Json::string(key),
"varValue": Json::string(value),
"sourceType": Json::string("system"),
"sourcePath": Json::string("Process Environment"),
}.to_json(),
)
_ => ()
}
}
for file_path in shell_files {
moon_push_shell_env_conflicts(
conflicts,
file_path,
moon_env_conflict_keys(moon_normalize_app_type(app)),
)
}
Json::array(conflicts)
}
///|
fn moon_env_source_file_path(source_path : String) -> String {
match source_path.find(":") {
Some(index) => source_path[:index].to_owned()
None => source_path
}
}
///|
async fn moon_import_mcp_from_codex_file(
servers : Map[String, Json],
file_path : String,
) -> Int {
if !(@fsx.exists_as_file(file_path) catch { _ => false }) {
return 0
}
let content = @fsx.read_file(file_path) catch { _ => return 0 }
moon_import_mcp_from_codex_toml_text(servers, content)
}
///|
async fn moon_import_mcp_from_file(
servers : Map[String, Json],
app : String,
file_path : String,
) -> Int {
if !(@fsx.exists_as_file(file_path) catch { _ => false }) {
return 0
}
let content = @fsx.read_file(file_path) catch { _ => return 0 }
let root = @json.parse(content) catch { _ => return 0 }
moon_import_mcp_from_root_json(servers, app, root)
}
///|
fn moon_prompt_file_path(app : String) -> Result[String, String] {
let normalized = moon_normalize_app_type(app)
if normalized == "claude-desktop" {
return Err("Claude Desktop 暂不支持 Prompts")
}
let filename = match normalized {
"claude" => "CLAUDE.md"
"gemini" => "GEMINI.md"
"codex" | "opencode" | "openclaw" | "hermes" | "omo" | "omo-slim" =>
"AGENTS.md"
_ => "CLAUDE.md"
}
Ok(@pathx.join(moon_config_dir(normalized), filename))
}
///|
fn moon_prompt_file_path_from_request(
path : StringView,
body : Json?,
app : String,
) -> Result[String, String] {
match proxy_string_request(path, body, "filePath") {
Some(file_path) => Ok(file_path)
None => moon_prompt_file_path(app)
}
}
///|
async fn moon_import_prompt_from_file_json(
prompts : Map[String, Json],
app : String,
file_path : String,
) -> Result[Json, String] {
if !(@fsx.exists_as_file(file_path) catch { _ => false }) {
return Err("提示词文件不存在")
}
let content = @fsx.read_file(file_path) catch {
error => return Err("读取提示词文件失败: \{error}")
}
let timestamp = @async.now().to_int()
let timestamp_text = timestamp.to_string()
let id = "imported-\{timestamp_text}"
let prompt = {
"id": Json::string(id),
"name": Json::string(
"导入的提示词 \{@os.local_rfc3339(timestamp.to_int64())}",
),
"content": Json::string(content),
"description": Json::string("从现有配置文件导入"),
"enabled": Json::boolean(false),
"createdAt": Json::string(timestamp_text),
"updatedAt": Json::string(timestamp_text),
}.to_json()
prompts[moon_prompt_store_key(app, id)] = prompt
Ok(Json::string(id))
}
///|
async fn moon_current_prompt_file_content_json(
file_path : String,
) -> Result[Json, String] {
if !(@fsx.exists_as_file(file_path) catch { _ => false }) {
return Ok(Json::null())
}
let content = @fsx.read_file(file_path) catch {
error => return Err("读取提示词文件失败: \{error}")
}
Ok(Json::string(content))
}
///|
async fn WorkspaceService::handle_save_file_dialog(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
ignore(proxy_string_request(r.path, body, "defaultName"))
write_usage_json(w, moon_dialog_result_json())
}
///|
async fn WorkspaceService::handle_read_omo_local_file(
self : WorkspaceService,
w : @httpx.ResponseWriter,
slim~ : Bool,
) -> Unit {
ignore(self)
let (data, error) = moon_read_omo_local_file_json(slim)
match data {
Some(data) => write_usage_json(w, data)
None =>
write_usage_bad_request(w, error.unwrap_or("OMO 配置文件不存在"))
}
}
///|
async fn WorkspaceService::handle_open_file_dialog(
self : WorkspaceService,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
write_usage_json(w, moon_dialog_result_json())
}
///|
async fn WorkspaceService::handle_open_zip_file_dialog(
self : WorkspaceService,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
write_usage_json(w, moon_dialog_result_json())
}
///|
async fn WorkspaceService::handle_import_prompt_from_file(
self : WorkspaceService,
config : ConfigService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
let app = moon_config_app_from_request(r.path, body).unwrap_or("claude")
match moon_prompt_file_path_from_request(r.path, body, app) {
Ok(file_path) =>
match moon_import_prompt_from_file_json(config.prompts, app, file_path) {
Ok(result) => write_usage_json(w, result)
Err(error) => write_usage_bad_request(w, error)
}
Err(error) => write_usage_bad_request(w, error)
}
}
///|
async fn WorkspaceService::handle_get_current_prompt_file_content(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
let app = moon_config_app_from_request(r.path, body).unwrap_or("claude")
match moon_prompt_file_path_from_request(r.path, body, app) {
Ok(file_path) =>
match moon_current_prompt_file_content_json(file_path) {
Ok(result) => write_usage_json(w, result)
Err(error) => write_usage_bad_request(w, error)
}
Err(error) => write_usage_bad_request(w, error)
}
}
///|
async fn WorkspaceService::handle_list_daily_memory_files(
self : WorkspaceService,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
write_usage_json(w, moon_list_daily_memory_files_json())
}
///|
async fn WorkspaceService::handle_read_daily_memory_file(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
guard proxy_string_request(r.path, body, "filename") is Some(filename) &&
moon_validate_daily_memory_filename(filename) else {
write_usage_bad_request(w, "invalid_daily_memory_filename")
return
}
let path = moon_daily_memory_file_path(filename)
if !@fsx.exists_as_file(path) {
write_usage_json(w, Json::null())
return
}
let content = @fsx.read_file(path) catch {
_ => {
write_usage_bad_request(w, "daily_memory_read_failed")
return
}
}
write_usage_json(w, Json::string(content))
}
///|
async fn WorkspaceService::handle_write_daily_memory_file(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
guard proxy_string_request(r.path, body, "filename") is Some(filename) &&
moon_validate_daily_memory_filename(filename) else {
write_usage_bad_request(w, "invalid_daily_memory_filename")
return
}
let content = proxy_string_request(r.path, body, "content").unwrap_or("")
if !moon_ensure_directory(moon_openclaw_memory_dir()) {
write_usage_bad_request(w, "daily_memory_directory_failed")
return
}
@fsx.write_to_file(moon_daily_memory_file_path(filename), content) catch {
_ => {
write_usage_bad_request(w, "daily_memory_write_failed")
return
}
}
write_usage_json(w, Json::null())
}
///|
async fn WorkspaceService::handle_search_daily_memory_files(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
let query = proxy_string_request(r.path, body, "query").unwrap_or("")
write_usage_json(w, moon_search_daily_memory_files_json(query))
}
///|
async fn WorkspaceService::handle_delete_daily_memory_file(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
guard proxy_string_request(r.path, body, "filename") is Some(filename) &&
moon_validate_daily_memory_filename(filename) else {
write_usage_bad_request(w, "invalid_daily_memory_filename")
return
}
let path = moon_daily_memory_file_path(filename)
if @fsx.exists_as_file(path) {
@fsx.remove(path) catch {
_ => {
write_usage_bad_request(w, "daily_memory_delete_failed")
return
}
}
}
write_usage_json(w, Json::null())
}
///|
async fn WorkspaceService::handle_read_workspace_file(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
guard proxy_string_request(r.path, body, "filename") is Some(filename) &&
moon_validate_workspace_filename(filename) else {
write_usage_bad_request(w, "invalid_workspace_filename")
return
}
let path = moon_workspace_file_path(filename)
if !@fsx.exists_as_file(path) {
write_usage_json(w, Json::null())
return
}
let content = @fsx.read_file(path) catch {
_ => {
write_usage_bad_request(w, "workspace_file_read_failed")
return
}
}
write_usage_json(w, Json::string(content))
}
///|
async fn WorkspaceService::handle_write_workspace_file(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
guard proxy_string_request(r.path, body, "filename") is Some(filename) &&
moon_validate_workspace_filename(filename) else {
write_usage_bad_request(w, "invalid_workspace_filename")
return
}
let content = proxy_string_request(r.path, body, "content").unwrap_or("")
if !moon_ensure_directory(moon_openclaw_workspace_dir()) {
write_usage_bad_request(w, "workspace_directory_failed")
return
}
@fsx.write_to_file(moon_workspace_file_path(filename), content) catch {
_ => {
write_usage_bad_request(w, "workspace_file_write_failed")
return
}
}
write_usage_json(w, Json::null())
}
///|
async fn WorkspaceService::handle_open_workspace_directory(
self : WorkspaceService,
r : @httpx.RequestReader,
w : @httpx.ResponseWriter,
) -> Unit {
ignore(self)
let body = proxy_json_body(r)
let subdir = proxy_string_request(r.path, body, "subdir").unwrap_or(
"workspace",
)
let target = if subdir == "memory" {
moon_openclaw_memory_dir()
} else {
moon_openclaw_workspace_dir()
}
if !moon_ensure_directory(target) {
write_usage_bad_request(w, "workspace_directory_failed")
return
}
write_usage_json(w, Json::boolean(true))
}
///|
async fn WorkspaceService::write_static_file(
self : WorkspaceService,
logger : @pino.Logger,
w : @httpx.ResponseWriter,
path : String,
content_type : String,
) -> Unit {
ignore(self)
let result : Result[String, Error] = moon_async_result(() => {
@fsx.read_file(path)
})
match result {
Ok(content) => {
w.header().set("Content-Type", content_type)
w.write_header(@status.Ok)
w.write(content)
}
Err(error) => {
logger.error("Failed to serve static file \{path}: \{error}")
w.header().set("Content-Type", "text/plain; charset=utf-8")
w.write_header(@status.NotFound)
w.write("Not Found")
}
}
w.flush()
}
///|
async fn WorkspaceService::handle_register_page(
self : WorkspaceService,
logger : @pino.Logger,
w : @httpx.ResponseWriter,
) -> Unit {
self.write_static_file(
logger, w, "public/participant-register.html", "text/html; charset=utf-8",
)
}
///|
async fn WorkspaceService::handle_dashboard_page(
self : WorkspaceService,
w : @httpx.ResponseWriter,
host : String,
port : Int,
) -> Unit {
ignore(self)
w.header().set("Content-Type", "text/html; charset=utf-8")
w.write_header(@status.Ok)
w.write(moongate_rabbita_document_html(api_base=@suite.base_url(host, port)))
w.flush()
}