Files
HomeAgent/cmd/waiter/main.go
root 2d314b3e9c 重构: 插件自注册 + .so 动态加载 + 中断打断机制
- 所有内置插件 init() 自注册 (plugin.RegisterFactory), 移除 main.go 硬编码
- 新增 .so 动态加载器 (internal/plugin/dynamic.go), 插件可编译为 plugin.so
- 新增 plugin.json 元数据 (internal/plugin/manifest.go)
- 新增 interceptLoop 独立 goroutine:
  (a) cancelLLM() 取消进行中的 HTTP 请求
  (b) interceptCh → drainInterrupt() 注入 [打断消息] 到 LLM 上下文
  (c) InjectInput 空闲时触发新处理循环
- 新增 internal/plugins/all.go 空白导入触发所有内置插件 init()
- internal/sdk/ 作为 PluginSDK 正式 Go API
- internal/api/ → internal/plugins/webui/ 迁移
- 删除旧 cmd/cli/, 使用 cmd/waiter/ 替代
- 更新 PLAN.md / ARCHITECTURE.md / README.md 文档
2026-07-03 16:53:34 +08:00

181 lines
3.9 KiB
Go

package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"strings"
"sync"
"time"
)
func main() {
socket := flag.String("socket", "/var/lib/homeagent/cli.sock", "unix socket path")
remote := flag.String("remote", "", "remote webui URL (e.g. http://127.0.0.1:8080)")
say := flag.String("say", "", "send a message and print response (one-shot, no TUI)")
flag.Parse()
if *say != "" {
if *remote != "" {
sayRemote(*remote, *say)
} else {
sayLocal(*socket, *say)
}
return
}
if *remote != "" {
runRemote(*remote)
} else {
runLocal(*socket)
}
}
// sayLocal sends one message via Unix socket and prints the response
func sayLocal(socketPath, message string) {
conn, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatalf("connect to %s: %v", socketPath, err)
}
defer conn.Close()
fmt.Fprintf(conn, "%s\n", message)
scanner := bufio.NewScanner(conn)
scanner.Scan()
if err := scanner.Err(); err != nil {
log.Fatalf("read: %v", err)
}
var resp struct {
Type string `json:"type"`
Content string `json:"content"`
Error string `json:"error"`
}
if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
fmt.Println(scanner.Text())
return
}
switch resp.Type {
case "response":
fmt.Println(resp.Content)
case "error":
log.Fatalf("error: %s", resp.Error)
default:
fmt.Println(scanner.Text())
}
}
// sayRemote sends one message via HTTP and prints the response
func sayRemote(baseURL, message string) {
baseURL = strings.TrimRight(baseURL, "/")
body := fmt.Sprintf(`{"message":%q}`, message)
resp, err := http.Post(baseURL+"/api/v1/chat", "application/json", strings.NewReader(body))
if err != nil {
log.Fatalf("http post: %v", err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
if content, ok := result["response"].(string); ok {
fmt.Println(content)
}
}
// runLocal starts an interactive TUI via Unix socket
func runLocal(socketPath string) {
conn, err := net.Dial("unix", socketPath)
if err != nil {
log.Fatalf("connect to %s: %v", socketPath, err)
}
defer conn.Close()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
var resp struct {
Type string `json:"type"`
Content string `json:"content"`
Error string `json:"error"`
}
if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
fmt.Println(scanner.Text())
continue
}
switch resp.Type {
case "response":
fmt.Println(resp.Content)
case "error":
fmt.Fprintf(os.Stderr, "error: %s\n", resp.Error)
default:
fmt.Println(scanner.Text())
}
}
}()
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("> ")
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
fmt.Print("> ")
continue
}
if line == "/exit" || line == "/quit" {
break
}
fmt.Fprintf(conn, "%s\n", line)
}
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
select {
case <-done:
case <-time.After(60 * time.Second):
}
}
// runRemote starts an interactive TUI via HTTP
func runRemote(baseURL string) {
baseURL = strings.TrimRight(baseURL, "/")
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("> ")
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
fmt.Print("> ")
continue
}
if line == "/exit" || line == "/quit" {
break
}
body := fmt.Sprintf(`{"message":%q}`, line)
resp, err := http.Post(baseURL+"/api/v1/chat", "application/json", strings.NewReader(body))
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
fmt.Print("> ")
continue
}
var result map[string]interface{}
if resp.StatusCode == http.StatusOK {
json.NewDecoder(resp.Body).Decode(&result)
}
resp.Body.Close()
if content, ok := result["response"].(string); ok {
fmt.Println(content)
}
fmt.Print("> ")
}
}