v0.18.14/15:全新使用者連上知識庫後會自己開始+log 不再印不存在的網址

leo 08-06 用共用資料夾實測,兩張圖把最後一個病逼出來:
「第一次直接跑可以,**清空再來一次,就不跑了**」。

## 真兇:restartWatch 第一行就早退
全新使用者的順序是:
  ① 打開程式(**還沒有任何設定**)⇒ startSupervisor 早退,`sup` 是 nil
  ② 連上知識庫、加資料夾 ⇒ restartWatch
  ③ 舊版:`if sup == nil { return }` ⇒ **什麼都沒發生**
  ⇒ 引擎永遠不啟動,畫面叫人「請結束 Arcrun 再重新開啟」
「不算錯」(重開真的會好),但等於要求剛裝好的人自己想到去重開程式。
**每一個第一次用的人都會撞到**;開發機永遠有設定,所以永遠測不到。

修:`sup == nil` 時**現場建立**(設定剛出現,正是該啟動的時機)。
新測試 `TestFirstRunStartsEngineAfterConnect` 從「沒有設定」開始走完整順序;
反向驗證拿掉即紅。

## 順帶把那句沒用的話拿掉
「請結束 Arcrun 再重新開啟」=把系統的無能推給使用者。
改成「正在啟動同步引擎,請稍候…」;還沒連知識庫時說
「按『新增知識庫帳號』就會開始」。

## log 不再說謊(今天第三次同一個主題)
啟動訊息印 `cfg.triggerURL(...)`=用**根層**的 cypher_url/namespace 組的,
而多帳號設定根層是空的 ⇒ 印出 `→ /webhooks/named//rag_ingest/trigger`
(沒網域、雙斜線)**看起來像設定壞了,其實功能正常**。
改印每個帳號真正會用到的網址。

## 實測證據(共用資料夾自動收回來的 log,非截圖轉述)
  19:13:54  設定檔缺必填欄位,已自動補上 manifest=C:\Users\leo21\.arcrun-rag\manifest.json
  19:13:55  collector direct daemon 啟動:監看 …\win_on_mac_test_lib  {"phase":"start"}
⇒ v0.18.13 的自我修復在真機生效,config 已補上 manifest,collector 真的跑起來。
This commit is contained in:
2026-08-06 19:33:36 +08:00
parent 8dc9a2ee85
commit 458ff3fd88
5 changed files with 89 additions and 3 deletions
+3 -1
View File
@@ -3,5 +3,7 @@
"v0.18.10": "a440f044a849b7f2",
"v0.18.11": "1538689e181444f6",
"v0.18.12": "ce37982f05d8cb5b",
"v0.18.13": "fbf7342d0d0e27b4"
"v0.18.13": "fbf7342d0d0e27b4",
"v0.18.14": "bfcb4f2701fc23b4",
"v0.18.15": "447b59ea3ade430e"
}
+8 -1
View File
@@ -432,7 +432,14 @@ func describeStatus(s syncStatus) (syncing bool, big, sub string) {
}
if !collectorAlive() {
msg, n, _ := collectorFailure()
sub := "資料夾不會被自動整理 ⇒ 請結束 Arcrun 再重新開啟"
// 🔴 2026-08-06:以前這裡寫「請結束 Arcrun 再重新開啟」——
// 那是**把系統的無能推給使用者**(真正該做的是自己啟動,已在 restartWatch 修)。
// 留這句當保底文案時也不要叫人重開,而是說「正在啟動」——
// 因為修完之後,唯一還會短暫看到這個狀態的時機就是剛啟動那幾秒。
sub := "正在啟動同步引擎,請稍候…"
if c, err := loadCfg(); err != nil || len(c.Accounts) == 0 {
sub = "還沒連上知識庫 ⇒ 按「新增知識庫帳號」就會開始"
}
if msg != "" {
sub = fmt.Sprintf("原因:%s(已重試 %d 次)", msg, n)
}
+55
View File
@@ -0,0 +1,55 @@
package main
import (
"os"
"testing"
)
// TestFirstRunStartsEngineAfterConnect 釘住 leo 2026-08-06 實測的最後一個病:
//
// 「第一次直接跑可以,**清空再來一次,就不跑了**」
//
// 全新使用者的真實順序是:
//
// ① 打開程式(**這時還沒有任何設定**)⇒ startSupervisor 早退,sup 仍是 nil
// ② 連上知識庫、加資料夾 ⇒ 呼叫 restartWatch
// ③ 舊版的 restartWatch 第一行是 `if sup == nil { return }` ⇒ **什麼都沒發生**
// ⇒ 引擎永遠不啟動,畫面叫人「請結束 Arcrun 再重新開啟」
//
// 這條路只有「**先沒有設定、之後才有**」才會走到——
// 開發機永遠有設定,所以永遠測不到。這支測試刻意從「沒有設定」開始。
func TestFirstRunStartsEngineAfterConnect(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
sup = nil
t.Cleanup(func() { stopSupervisor(); sup = nil })
// ① 打開程式:還沒有設定
startSupervisor()
if sup != nil {
t.Fatal("沒有設定時不該啟動引擎(會一直失敗刷 log)")
}
// ② 使用者連上知識庫、加資料夾(=寫出設定)
cfg := &directConfig{
Accounts: []accountCfg{{
CypherURL: "https://example.workers.dev",
Namespace: "abc123",
APIKey: "abc123",
}},
Extractor: "workers-ai",
}
if err := saveCfg(cfg); err != nil {
t.Fatalf("存檔失敗:%v", err)
}
if _, err := os.Stat(configPath()); err != nil {
t.Fatalf("設定沒寫出來:%v", err)
}
// ③ 這一步以前是死巷
restartWatch()
if sup == nil {
t.Fatal("設定出現後仍沒建立引擎 ⇒ 使用者得自己想到重開程式(leo 撞到的那個)")
}
}
+8
View File
@@ -99,7 +99,15 @@ func appLog(format string, args ...any) {
// restartWatch 在設定變更後重起看守,讓新設定立刻生效。
func restartWatch() {
// 🔴 2026-08-06 leo 實測(「第一次直接跑可以,清空再來一次就不跑了」):
// 這裡以前第一行是 `if sup == nil { return }` ⇒ **每一個全新使用者都會撞到**——
// ① 開程式時還沒有設定 ⇒ startSupervisor 早退,`sup` 是 nil
// ② 使用者接著連知識庫、加資料夾 ⇒ 呼叫這支 ⇒ **nil 就直接 return,什麼都沒發生**
// ③ 引擎永遠不啟動,畫面說「請結束 Arcrun 再重新開啟」
// ——它「不算錯」(重開真的會好),但等於要求剛裝好的人自己想到去重開程式。
// ⇒ 還沒建立就**現在建立**:設定剛剛才出現,正是該啟動的時機。
if sup == nil {
startSupervisor()
return
}
sup.Stop()
+15 -1
View File
@@ -1025,8 +1025,22 @@ func runDirect(args []string) int {
// t98:每 1 秒檢查一次訊號檔(SyncNowSignalPath),命中即立刻跑一輪並刪檔;
// 否則依 PollSec 間隔照舊定時跑。這讓 tray「立刻同步」按鈕能即時觸發,
// 不需引入 IPC/socket 等平台依賴。
// 🔴 2026-08-06:這裡以前印 `cfg.triggerURL(...)`,但那是用**根層**的
// cypher_url/namespace 組出來的——多帳號設定(現在的常態)根層是空的
// ⇒ log 印出 `→ /webhooks/named//rag_ingest/trigger`(沒有網域、雙斜線),
// 看起來像設定壞了,其實實際工作走的是**每個帳號各自的網址**,功能是好的。
// 誤導性的 log 比沒有 log 更貴——會害人往錯的方向查(今天就繞了一輪)。
// ⇒ 改印真正會用到的:有幾個帳號、各自的目的地。
dests := make([]string, 0, len(cfg.Accounts))
for _, a := range cfg.Accounts {
dests = append(dests, fmt.Sprintf("%s/webhooks/named/%s/%s/trigger",
a.CypherURL, a.Namespace, cfg.IngestWF))
}
if len(dests) == 0 {
dests = append(dests, cfg.triggerURL(cfg.IngestWF))
}
fmt.Fprintf(os.Stderr, "collector direct daemon 啟動:監看 %s → %s(每 %ds 掃一輪)\n",
strings.Join(cfg.Folders(), "、"), cfg.triggerURL(cfg.IngestWF), cfg.PollSec)
strings.Join(cfg.Folders(), "、"), strings.Join(dests, "、"), cfg.PollSec)
runOne()
signalPath := SyncNowSignalPath(cfg.Manifest)