統帥 2026-04-20 指示「C 路線 + 甲 bot」— v1 獨立個人工具路線與 awoooi MASTER blueprint 全景割裂,違反 feedback_ai_autonomous_direction 北極星(純記錄非自主化)。v2 重新對齊: - DB:進主 PG,新 migration adr091 的 aider_events 表 - Telegram:走既有 telegram_gateway @tsenyangbot + Redis dedup - Incident:aider error 自動建 incident 走既有告警鏈 - AI 學習回路:symptom_pattern 抽取 + AI Router feedback hook - Mac client:薄殼 HTTP POST + 本機 JSONL fallback buffer v1 產物去向:events.py/redactor.py 搬進 awoooi;其他廢棄。 @NemoTronAwoooI_Bot 轉 sandbox 用,不刪。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
15 KiB
aider-watch 設計稿(v1 — ⛔ DEPRECATED 2026-04-20)
⛔ 已被 v2 取代 — 統帥於 2026-04-20 指出此設計未對齊 awoooi AI 自主化飛輪全景, 獨立 PG + 獨立 bot + 獨立 repo 路線與 MASTER blueprint 割裂。 新版 spec:2026-04-20-aider-watch-v2-design.md v1 保留僅供歷史對照;實作已停在 ~/aider-watch Task 5(8 commits)。
統帥本機 aider CLI 的全程監控系統
2026-04-19 Brainstorm 定稿,經統帥 §1-§7 逐段批准
背景與目標
統帥 2026-04-19 於本機安裝 aider 0.86.2(via pipx,Python 3.11.7),搭配 OpenRouter 免費 stealth 模型 openrouter/openrouter/elephant-alpha。aider 無原生 webhook/event stream,若不加監控,aider 自主工作時統帥無法掌握:改了什麼檔、花了多少 token、有沒有卡住、有沒有失敗。
目標:aider 每次執行都被完整記錄 + 重要事件即時推播到 Telegram DM + 每日/每週自動彙總。故障時 aider 本身不受影響(graceful degradation)。
非目標:
- 不整合 SignOz / awoooi telemetry(aider 是個人工具,避免綁死 awoooi infra)
- 不做 Web dashboard(本次先不碰,未來再議)
- 不監控其他人/其他機器(單機 single-user)
§1 架構總覽
統帥下指令
│
▼
aiderw (Python wrapper, 取代 `aider`)
│
├─ 啟 subprocess: aider [統帥的參數...]
│
├─ stdout tee ──────► [A] 即時解析 → 即時 events
│ • session_start (開場)
│ • silent_timeout (卡住 >2min)
│ • error (stderr / 異常 pattern)
│
└─ 退出時 ──────────► [B] 讀 .aider.chat.history.md + git diff
→ canonical events
• file_edit (每檔 +N -M + 3 行 diff)
• commit (若 auto-commit=true)
• session_end (duration, tokens, cost, exit_code)
[A] + [B] 合流 → Event Dispatcher
│
├─► Telegram Bot → DM chat_id 5619078117
├─► Postgres 192.168.0.188/aider_watch
│ └ 離線時 fallback → ~/aider-watch/buffer/*.jsonl
├─► ~/aider-watch/sessions/YYYY/MM/DD/<sid>.{jsonl,md} (source-of-truth)
└─► ~/aider-watch/live.log (append plain text, tail -F 用)
Launchd Jobs (獨立於 aiderw):
• com.awoooi.aider-watch.flush 每 5 min 補寫 PG (buffer → PG)
• com.awoooi.aider-watch.daily 每日 23:50 → 日報
• com.awoooi.aider-watch.weekly 週日 22:00 → 週報
關鍵設計決定:
- 雙層 event 來源:stdout (即時性) + chat_history.md (準確性) 互補,不怕 aider 改 TUI 格式
- PG 非強依賴:Mac 離線時 JSONL 做緩衝,連線恢復自動補寫 → aider 不因 PG 掛掉而失敗
- JSONL 是 source-of-truth:即使 PG 資料毀了,靠 JSONL 可 replay 重建
- 報表工作解耦:3 支 launchd job 獨立 Python script,彼此壞不影響
§2 元件分工
5 個獨立單元,每個有明確單一職責、介面、依賴:
| # | 檔案 | 職責 | 介面 | 依賴 |
|---|---|---|---|---|
| 1 | aider_watch/wrapper.py |
主入口:fork aider subprocess、tee stdout、寫 live.log、發 event 到 dispatcher、exit 時讀 chat_history.md 重建 canonical events | CLI: aiderw [args] → 透明轉發給 aider;退出碼同 aider |
subprocess, pexpect |
| 2 | aider_watch/events.py |
Event 資料契約:dataclass/pydantic 定義 7 種 event type、序列化/反序列化 | SessionStart, FileEdit, Error, Commit, SilentTimeout, SessionEnd, Raw |
pydantic |
| 3 | aider_watch/storage.py |
雙寫儲存:優先寫 PG;PG 不可達時 fallback buffer/*.jsonl;提供 flush_buffer() 清空 |
write_event(ev), write_session(s), flush_buffer() |
psycopg2, json |
| 4 | aider_watch/telegram.py |
Telegram 客戶端:格式化、rate-limit、429 指數退避、失敗不 raise(aider 不能卡) | send(event) -> bool |
requests |
| 5 | aider_watch/reporter.py |
報表:從 PG 撈資料 → 產日/週報 Telegram markdown | daily(), weekly() |
psycopg2 |
CLI subcommands:
aiderw [aider args...] # 包 aider 跑
aider-watch report daily # 立即產日報 (launchd 呼叫)
aider-watch report weekly # 立即產週報 (launchd 呼叫)
aider-watch flush # 手動 flush buffer (launchd 呼叫)
aider-watch doctor # 檢查 PG 連線、Telegram 通道、設定檔
aider-watch replay <session_id> # 從 JSONL 重建事件到 PG
單元資料流:
wrapper ──events──► events ──validated──► storage ──► PG / JSONL buffer
└──────────► telegram ──► Bot API
└──────────► live.log (tee via wrapper)
reporter ──query PG──► telegram
重要:wrapper 絕不直接碰 PG 或 Telegram,只發 event 給 dispatcher。dispatcher 可 mock 測。
§3 Event Schema
7 種 event type,統一外殼 + type-specific payload:
{
"ts": "2026-04-19T22:05:33+08:00",
"session_id": "01J7XZ...",
"type": "session_start",
"payload": { ... }
}
| type | 來源 | payload |
|---|---|---|
session_start |
stdout (aider banner) | {cwd, model, aider_args, aider_pid, cli_version} |
file_edit |
退出後 git diff + chat_history.md | {path, lines_added, lines_deleted, diff_head, is_new_file} |
error |
stderr + exit code ≠ 0 + "error" pattern | {kind: "api_rate_limit|api_auth|build_fail|unknown", message, context_50chars} |
commit |
退出後 git log 比對 | {sha, message, author, files[]} |
silent_timeout |
wrapper 監測:stdout >120s 無輸出 | {idle_sec, last_output_tail} |
session_end |
subprocess 結束 | {duration_sec, tokens_sent, tokens_received, cost_usd, files_changed, error_count, exit_code} |
raw |
debug 用,壓縮 stdout 片段 | {chunk: "..."} — 不推 Telegram,只寫 JSONL |
Telegram 訊息範例(對應事件 1-5, 8;事件 6/7 user/assistant message 刻意不推):
🚀 aider 啟動
repo: /Users/ogt/awoooi
model: elephant-alpha
pid: 12345
✏️ edit apps/api/src/jobs/foo.py (+12 -3)
@@ def scan_targets():
+ logger.info("starting")
- pass
❌ error: api_rate_limit — OpenRouter 429 on elephant-alpha
📌 commit abc123: "fix: handle null auth"
→ apps/api/src/auth.py, apps/api/tests/test_auth.py
⏸️ aider 靜默 2 分鐘 (last: "Tokens: 4.2k sent")
🏁 session 結束
⏱️ 5m32s | 🎯 exit=0
📊 12.4k↑ / 3.8k↓ tokens | $0 (Elephant Alpha)
📝 改 3 檔 | ❌ 0 error
時區:所有 timestamp 以台北時區 +08:00 儲存(feedback_timezone_taipei.md)。
§4 Postgres Schema
Database: aider_watch(獨立於 awoooi 其他 DB,同 PG instance 192.168.0.188:5432)
User: aider_watch(只在此 DB 有權限,不碰 K3s datastore 或其他 awoooi DB)
-- ===== Sessions:一場 aider 執行 =====
CREATE TABLE sessions (
id TEXT PRIMARY KEY, -- ULID
started_at TIMESTAMPTZ NOT NULL,
ended_at TIMESTAMPTZ,
cwd TEXT NOT NULL,
model TEXT NOT NULL,
aider_args TEXT[],
aider_pid INTEGER,
duration_sec INTEGER,
tokens_sent INTEGER DEFAULT 0,
tokens_recv INTEGER DEFAULT 0,
cost_usd NUMERIC(10,6) DEFAULT 0,
files_changed INTEGER DEFAULT 0,
error_count INTEGER DEFAULT 0,
exit_code INTEGER,
host TEXT DEFAULT 'ogt-mac'
);
CREATE INDEX sessions_started_idx ON sessions(started_at DESC);
CREATE INDEX sessions_cwd_idx ON sessions(cwd);
-- ===== Events:每個細粒度事件 =====
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
ts TIMESTAMPTZ NOT NULL,
type TEXT NOT NULL,
payload JSONB NOT NULL
);
CREATE INDEX events_session_ts_idx ON events(session_id, ts);
CREATE INDEX events_type_idx ON events(type);
CREATE INDEX events_ts_idx ON events(ts DESC);
-- ===== File touches:每次檔案變更展平,便於 top-N 查詢 =====
CREATE TABLE file_touches (
id BIGSERIAL PRIMARY KEY,
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
path TEXT NOT NULL,
touched_at TIMESTAMPTZ NOT NULL,
lines_added INTEGER DEFAULT 0,
lines_deleted INTEGER DEFAULT 0,
is_new_file BOOLEAN DEFAULT FALSE
);
CREATE INDEX ft_path_idx ON file_touches(path);
CREATE INDEX ft_session_idx ON file_touches(session_id);
CREATE INDEX ft_date_idx ON file_touches((touched_at::date));
為什麼不用純 event sourcing:查詢日報每次都要 aggregate events 太慢。sessions 表是派生摘要,退出時一次寫。events 表原樣保留,萬一 session 摘要錯可從 events 重建。
日/週報查詢範例(reporter.py 用):
-- 今日統計
SELECT count(*) AS sessions, sum(duration_sec) AS total_sec,
sum(tokens_sent) AS toks_sent, sum(files_changed) AS files,
sum(error_count) AS errors
FROM sessions WHERE started_at::date = CURRENT_DATE;
-- 今日 Top 3 最常改的檔
SELECT path, count(*) AS touches, sum(lines_added) AS adds, sum(lines_deleted) AS dels
FROM file_touches WHERE touched_at::date = CURRENT_DATE
GROUP BY path ORDER BY touches DESC LIMIT 3;
§5 檔案佈局
~/aider-watch/ # 主工作目錄
├── bin/aiderw # 入口 shim
├── aider_watch/ # Python 套件
│ ├── __init__.py
│ ├── wrapper.py
│ ├── events.py
│ ├── storage.py
│ ├── telegram.py
│ ├── reporter.py
│ └── cli.py
├── sessions/ # source-of-truth(永遠寫)
│ └── 2026/04/19/
│ ├── 01J7XZABC_awoooi_12345.jsonl
│ └── 01J7XZABC_awoooi_12345.md
├── buffer/ # PG 離線時 fallback
│ └── pending_01J7XZABC.jsonl
├── live.log # append-only 純文字流
├── state.json # 健康狀態
└── logs/ # launchd job stdout/stderr
├── flush.log
├── daily.log
└── weekly.log
~/.aider-watch.env # chmod 600
AIDER_WATCH_DATABASE_URL=postgresql://aider_watch:<PW-安裝時生成>@192.168.0.188:5432/aider_watch
AIDER_WATCH_TELEGRAM_TOKEN=<bot token - 已確認 @NemoTronAwoooI_Bot>
AIDER_WATCH_TELEGRAM_CHAT_ID=5619078117
AIDER_WATCH_HOSTNAME=ogt-mac
~/Library/LaunchAgents/
├── com.awoooi.aider-watch.flush.plist # 每 5 min
├── com.awoooi.aider-watch.daily.plist # 每日 23:50
└── com.awoooi.aider-watch.weekly.plist # 每週日 22:00
/opt/homebrew/bin/aiderw → ~/aider-watch/bin/aiderw # PATH 入口 symlink
PATH 策略:
aiderw→ symlink → Python 包 → subprocess aider- 原生
aider仍可直接呼叫(繞過監控,debug 用) - 可選:shell alias
alias aider=aiderw,不強制
§6 錯誤處理 / 降級策略
| 失敗情境 | 處理 | 結果 |
|---|---|---|
| PG 188 不可達 | 寫 buffer/pending_<sid>.jsonl;state.json 記 last_flush_fail_ts;launchd flush job 每 5min 重試 |
資料不丟 |
| Telegram 429 | 指數退避 3 次(1s→4s→16s),失敗→ live.log + 旗標進下次日報 |
aider 絕不卡住 |
| Telegram 網路 timeout | 同上,最多 5s | 同上 |
| aider subprocess 崩潰 / OOM | wrapper 收 SIGCHLD → 寫 session_end exit_code≠0 error_count≥1 + Telegram 推 error |
死得清楚 |
| wrapper 自己崩潰 | atexit handler 強寫 session_end 含 wrapper_crash=true |
半份紀錄仍有 |
| stdout 解析漏掉 event | 退出時 chat_history.md + git diff 補上 canonical events | 準確性不犧牲 |
| secrets 外洩 | ~/.aider-watch.env chmod 600;payload 偵測 sk-or-*、ghp_*、AIza* pattern → 遮罩 <redacted> |
符合 feedback_secrets_leak_incidents_2026-04-18.md |
| Launchd job 死 | KeepAlive=true + stderr 寫 logs/*.log;doctor 指令檢查狀態 |
aider-watch doctor 一眼看出 |
| PG DB 被手動刪 | aiderw 啟動時 ping PG + 必要表 → 缺表自動跑 migration(內建 schema.sql) |
自癒 |
| 磁碟爆滿 | sessions/ JSONL+MD 保留 90 天,更早壓縮成 YYYY-MM.tar.gz 移到 archive/;PG 不設 retention(資料量小,查詢歷史統計用);doctor 顯示本機磁碟用量 |
有上限 |
核心原則:aiderw 的任何故障絕不能讓 aider 跑不起來。wrapper 外層 try/except 全包,最壞情況只是「監控失效」,aider 本身 pass-through 照跑。
§7 測試策略
遵循 feedback_no_mock_testing.md(禁 mock),盡量用真服務。
| 層級 | 範圍 | 怎麼跑 | 通過標準 |
|---|---|---|---|
| Unit | events.py schema、telegram.py 格式化、secret redactor |
pytest,fixtures 不 mock | 欄位 100% 正確、redactor 6 種 token pattern 全中 |
| Integration: PG | storage.py 真寫獨立的 aider_watch_test DB(非 prod aider_watch 內的 schema) |
跑真 PG 188,測完 DROP | session/event/file_touches round-trip |
| Integration: Telegram | 真發訊息到 chat_id 5619078117 | 前綴 [TEST] |
Telegram API 回 ok=true、收到訊息 |
| E2E: aider session | aiderw --message "print hello" --exit 跑真 aider |
/tmp/aider-smoke |
TG 收 🚀+🏁;PG sessions 有 1 筆;JSONL 產生 |
| E2E: 斷線降級 | ifconfig en0 down/up;跑 aiderw;恢復網路;跑 flush |
手動 | buffer 產生 → flush 後 PG 補齊、buffer 清空 |
| E2E: 日/週報 | aider-watch report daily/weekly |
PG 有至少 1 筆 session | TG 收到日/週報,統計正確 |
| Doctor | aider-watch doctor |
正常 + 刻意破壞(停 PG / 刪 env / 刪 plist) | 全綠 / 破壞時精準指出問題 |
關鍵:第一次上線必跑 E2E 斷線降級測試。這是整個設計最容易壞的地方。
相關 Memory 依據
feedback_timezone_taipei.md— timestamp +08:00feedback_no_mock_testing.md— 測試不 mockfeedback_secrets_leak_incidents_2026-04-18.md— secret 遮罩 + chmod 600feedback_change_annotation_standard.md— 檔案變更註解標準(實作時加入)reference_aider_setup.md— aider 本機安裝現況reference_four_hosts.md— PG 位於 192.168.0.188:5432reference_secrets_architecture_v2.md— secret 4 層架構(本工具走 L4 本機 env file)
後續
本設計稿批准後,進入 writing-plans 階段產出逐步實作計畫(建立 DB/user、寫 Python 套件、裝 launchd、E2E 驗收 ...)。