项目文件夹

文件
wehub-resource-sync 9f2e1cd2af
Ruby / Ruby 4.0.5 (push) Failing after 5m13s
Ruby / Ruby 3.3.4 (push) Failing after 5m43s
Ruby / Ruby 2.6.8 (push) Failing after 16m2s
chore: import upstream snapshot with attribution
2026-07-13 12:33:07 +08:00

167 行
5.3 KiB
Ruby

# frozen_string_literal: true
require_relative "ui_interface"
module Clacky
# PlainUIController implements UIInterface for non-interactive (--message) mode.
# Writes human-readable plain text directly to stdout so the caller can capture
# or pipe the output. No spinners, no TUI — just clean lines.
class PlainUIController
include Clacky::UIInterface
def initialize(output: $stdout)
@output = output
@mutex = Mutex.new
end
# === Output display ===
def show_assistant_message(content, files:)
puts_line(content) unless content.nil? || content.strip.empty?
files.each { |f| puts_line("📄 File: #{f[:path]}") }
end
def show_tool_call(name, args)
args_data = args.is_a?(String) ? (JSON.parse(args) rescue args) : args
# Special handling for request_user_feedback — display as a readable prompt
if name.to_s == "request_user_feedback"
question = args_data.is_a?(Hash) ? (args_data[:question] || args_data["question"]).to_s : ""
context = args_data.is_a?(Hash) ? (args_data[:context] || args_data["context"]).to_s : ""
options = args_data.is_a?(Hash) ? (args_data[:options] || args_data["options"]) : nil
options = Array(options) if options && !options.is_a?(Array)
parts = []
parts << "**Context:** #{context.strip}" if context && !context.strip.empty?
parts << "**Question:** #{question.strip}"
if options && !options.empty?
parts << "**Options:**"
options.each_with_index { |opt, i| parts << " #{i + 1}. #{opt}" }
end
puts_line(parts.join("\n"))
return
end
display = case name
when "terminal"
cmd = args_data.is_a?(Hash) ? (args_data[:command] || args_data["command"]) : args_data
sid = args_data.is_a?(Hash) ? (args_data[:session_id] || args_data["session_id"]) : nil
if cmd
"$ #{cmd}"
elsif sid
"$ (session ##{sid})"
else
"$ terminal"
end
when "write"
path = args_data.is_a?(Hash) ? (args_data[:path] || args_data["path"]) : args_data
"Write → #{path}"
when "edit"
path = args_data.is_a?(Hash) ? (args_data[:path] || args_data["path"]) : args_data
"Edit → #{path}"
else
label = args_data.is_a?(Hash) ? args_data.map { |k, v| "#{k}=#{v.to_s[0..40]}" }.join(", ") : args_data.to_s[0..80]
"#{name}(#{label})"
end
puts_line("[tool] #{display}")
end
def show_tool_result(result)
text = result.to_s.strip
return if text.empty?
# Indent multi-line results for readability
indented = text.lines.map { |l| " #{l}" }.join
puts_line(indented)
end
def show_tool_error(error)
msg = error.is_a?(Exception) ? error.message : error.to_s
puts_line("[error] #{msg}")
end
def show_file_write_preview(path, is_new_file:)
action = is_new_file ? "create" : "overwrite"
puts_line("[file] #{action}: #{path}")
end
def show_file_edit_preview(path)
puts_line("[file] edit: #{path}")
end
def show_file_error(error_message)
puts_line("[file error] #{error_message}")
end
def show_shell_preview(command)
puts_line("[shell] #{command}")
end
def show_complete(iterations:, cost:, duration: nil, cache_stats: nil, awaiting_user_feedback: false, cost_source: nil)
parts = ["[done] iterations=#{iterations}", "cost=$#{cost.round(4)}"]
parts << "duration=#{duration.round(1)}s" if duration
puts_line(parts.join(" "))
end
def append_output(content)
puts_line(content)
end
# === Status messages ===
def show_info(message, prefix_newline: true)
puts_line("[info] #{message}")
end
def show_warning(message)
puts_line("[warn] #{message}")
end
def show_error(message, code: nil, top_up_url: nil, raw_message: nil)
puts_line("[error] #{message}")
end
def show_success(message)
puts_line("[ok] #{message}")
end
def log(message, level: :info)
# Only surface errors/warnings; suppress debug noise in plain mode
puts_line("[#{level}] #{message}") if %i[error warn].include?(level.to_sym)
end
# === Progress (no-ops — no spinner in plain mode) ===
def show_progress(message = nil, prefix_newline: true, progress_type: "thinking", phase: "active", metadata: {}); end
# === State updates (no-ops) ===
def update_sessionbar(tasks: nil, cost: nil, cost_source: nil, status: nil, latency: nil); end
def update_todos(todos); end
def set_working_status; end
def set_idle_status; end
# === Blocking interaction (auto-approve in non-interactive mode) ===
def request_confirmation(message, default: true)
# Should not be reached because permission_mode is forced to auto_approve,
# but return true as a safety net.
true
end
# === Input control / Lifecycle (no-ops) ===
def clear_input; end
def set_input_tips(message, type: :info); end
def stop; end
def puts_line(text)
@mutex.synchronize do
@output.puts(text)
@output.flush
end
end
end
end