diff --git a/cmd/arcrun-app/.version-source.json b/cmd/arcrun-app/.version-source.json index d36416e..b0fa1f8 100644 --- a/cmd/arcrun-app/.version-source.json +++ b/cmd/arcrun-app/.version-source.json @@ -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" } diff --git a/cmd/arcrun-app/app.go b/cmd/arcrun-app/app.go index 4bfe120..041c048 100644 --- a/cmd/arcrun-app/app.go +++ b/cmd/arcrun-app/app.go @@ -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) } diff --git a/cmd/arcrun-app/firstrun_test.go b/cmd/arcrun-app/firstrun_test.go new file mode 100644 index 0000000..ea70481 --- /dev/null +++ b/cmd/arcrun-app/firstrun_test.go @@ -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 撞到的那個)") + } +} diff --git a/cmd/arcrun-app/supervise.go b/cmd/arcrun-app/supervise.go index e28fbd8..c4c6228 100644 --- a/cmd/arcrun-app/supervise.go +++ b/cmd/arcrun-app/supervise.go @@ -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() diff --git a/direct.go b/direct.go index 6b8adf5..a22d618 100644 --- a/direct.go +++ b/direct.go @@ -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)