sync: collector/ 同步自 inkstone/arcrun-rag@10eb2cb(桌面小幫手 0.18.53)

This commit is contained in:
2026-09-14 07:05:34 +08:00
parent bce422f6fe
commit e4327ecd37
13 changed files with 820 additions and 22 deletions
+57 -1
View File
@@ -669,6 +669,11 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
if cfg.Manifest != "" {
prevStatus, _ = LoadSyncStatus(StatusFilePath(cfg.Manifest)) // 讀不到=零值,等同「沒有上一輪」
}
// 🔴 `inkstone/arcrun-rag#200`:一輪開始就寫一筆「正在跑」,之後每件事發生的當下更新
// (見 live_status.go)。放在載入 prevStatus **之後**:途中寫入不該影響這一輪讀到的上一輪。
if !dryRun && cfg.Manifest != "" {
cfg.guard.beginRound(StatusFilePath(cfg.Manifest), now)
}
// t92-②:預檢 extractor(機器層級),有 fallback 路徑時就地更新 cfg.ClaudeBin。
extractorOK := true
@@ -792,6 +797,7 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
}
accCfg := cfg.makeAccountSubConfig(acc)
accHost := instanceHostOf(acc.CypherURL)
cfg.guard.enterFolder(accHost, "") // #200:帳號層的事(探測雲端 AI、查版本)也要講得出在誰身上
// t103per-account 雲端版本偵測
cloudVer, cloudOK := cloudVersionThrottled(accCfg.CypherURL, cfg.ForceSync) // #121:一分鐘問一次,見 cloudcheck.go
@@ -859,6 +865,7 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
multi := len(accCfg.Folders()) > 1
for _, root := range accCfg.Folders() {
cfg.guard.enterFolder(accHost, root) // #200
r, e, p, rp := runDirectOnceRoot(accCfg, root, dryRun, qs, now)
totalProgress = totalProgress.Add(rp.Progress)
stuckReasons = append(stuckReasons, rp.StuckReasons...)
@@ -926,6 +933,7 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
// arcrun-rag#46:把「使用者按了移除並收回」的資料夾撤乾淨。
// 放在看守資料夾之後:正在用的資料夾優先,收回是善後。
for _, root := range accCfg.RetiringRoots() {
cfg.guard.enterFolder(accHost, root) // #200
r, e, remaining, done := retireRootOnce(accCfg, root, dryRun)
for i := range r {
r[i].Root = root
@@ -979,6 +987,8 @@ func RunDirectOnce(cfg *DirectConfig, dryRun bool) ([]DirectResult, int, *Trigge
// t91:每輪寫狀態檔(含 per-account 雲端版本與萃取計數)。
if !dryRun && cfg.Manifest != "" {
// #200:先封口再寫——晚到的「還在等」不准把收工這份蓋回「同步中」(見 finishRound)。
cfg.guard.finishRound()
st := SyncStatus{
LastSync: time.Now().Format(time.RFC3339),
ExtractorOK: extractorOK,
@@ -1618,6 +1628,45 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
PublishFolderTreeNow(cfg.Manifest, root, tree, runNow)
}
// 🔴 `inkstone/arcrun-rag#200`:上面那棵樹是**開始送之前**算的,分子必然停在開工前。
// 以前它就是本機唯一的一棵——收工合併時用的也是它 ⇒ 2026-09-13 ISEP 送上去 11 份,
// 畫面每一層照樣 0 / N。
//
// ⇒ 每處理完一份檔,就用**同一個函式、同一組分母**、manifest 此刻的分子重算一棵,
// 立刻落地本機,並把這個資料夾的進度寫進 status.jsonlive_status.go)。
// 分母沿用這一輪掃描的結果:一輪途中使用者新丟的檔,本來就要下一輪才算得到。
//
// 「removed 暫時放回」的那幾筆不算進樹:它們只是下架重試的記帳,檔案已經不在了
//(同開工那棵的判準——那棵算的時候它們還沒被放回去)。
//
// 🔴 送上 portal 的那棵(syncFolderTree)**不在這裡重送**:每份檔多打一發雲端,
// 在一發 24〜57 秒的實例上等於把一輪拉長一倍。portal 下一輪掃描就會拿到新的。
latestTree := tree
removedNow := map[string]bool{}
for _, ev := range payload.Events {
if ev.Type == "removed" {
removedNow[ev.Path] = true
}
}
liveReport := func(outcome string) {
if dryRun {
return
}
entries := m.Entries
if len(removedNow) > 0 {
entries = make(map[string]*ManifestEntry, len(m.Entries))
for k, v := range m.Entries {
if !removedNow[k] {
entries[k] = v
}
}
}
latestTree = BuildFolderTree(absRoot, cfg.libraryFor(absRoot), payload.DirStats, entries,
payload.AllExcludedDirs, plan, runNow).StampMachine(cfg.machineIdentity())
PublishFolderTreeNow(cfg.Manifest, root, latestTree, runNow)
cfg.guard.fileFinished(root, outcome, m.Progress())
}
// 結構先行(InkStoneCo#432026-08-15):掃描一結束(純本機、免費、秒級)就先把
// 「這個資料夾有哪些檔案/最近改了什麼」送上知識庫,**不等 LLM 萃取、不受額度影響**
// ——走 rag_ingest_card(零 LLM 的機械收口),所以刻意放在:
@@ -1792,6 +1841,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
res.Status, res.Error = "failed", "讀檔失敗:"+rerr.Error()
m.MarkFailed(ev.Path, now, res.Error) // t195:讀不到的檔也退避(權限/被鎖/壞掉的外接碟)
saveManifest()
liveReport("failed") // #200
results = append(results, res)
exit = 1
continue
@@ -1866,6 +1916,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
// 漏記的話(連不上知識庫、金鑰壞、模型錯)照樣每輪重撞。
m.MarkFailed(ev.Path, now, res.Error)
saveManifest()
liveReport("failed") // #200
results = append(results, res)
exit = 1
continue
@@ -1959,6 +2010,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
exit = 1
}
saveManifest()
liveReport(res.Status) // #200:送上去的那一刻,畫面上的分子就要跟著動
results = append(results, res)
continue
}
@@ -1994,6 +2046,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
qs.DailyCount++
}
saveManifest()
liveReport(res.Status) // #200
results = append(results, res)
case "removed":
@@ -2037,6 +2090,7 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
// (不是 Scan() rebuild 時就拿掉——那時只是「偵測到不見了」,不是「已下架」)。
delete(m.Entries, ev.Path)
saveManifest()
liveReport("removed") // #200
// t15extractor 模式雲端下架成功後,同步清掉本地萃出的卡,保持本地與雲端一致。
// arcrun-rag#60:清除路徑必須跟落卡路徑**同一個函式**算出來(cardRelFor)——
// 目錄或檔名任一邊不同步就清不到卡、留下孤兒檔。第一輪對齊了目錄,
@@ -2098,7 +2152,9 @@ func runDirectOnceRoot(cfg *DirectConfig, root string, dryRun bool, qs *quotaSta
// t210manifest 走到這裡已經是本輪最終狀態(每個事件處理完就地更新),
// 原地數一次就是對的(同 SkippedDocCount 那套「現況快照」邏輯,不必另外維護計數器)。
rp := rootProgress{Progress: m.Progress(), Tree: tree}
// #200Tree 用**最後重算的那一棵**(沒處理任何檔時就是開工那棵)。
// 以前這裡是開工前那棵 ⇒ 收工合併進 folder-trees.json 的分子永遠是開工前的數字。
rp := rootProgress{Progress: m.Progress(), Tree: latestTree}
for _, e := range m.Entries {
if e != nil && e.FailCount >= MaxFailBeforeSkip {
// LastError 原文交給呼叫端彙總後過 ClassifyFailure——分類判斷只住那一個接縫,