sync: collector/ 同步自 inkstone/arcrun-rag@99ff817(桌面小幫手 0.18.56)

This commit is contained in:
2026-09-17 20:43:06 +08:00
parent d052c33d26
commit ba584002d0
8 changed files with 320 additions and 8 deletions
+36 -1
View File
@@ -56,6 +56,11 @@ type ManifestEntry struct {
// 真因(Cloudflare「當日免費額度用完」/「這份 PDF 沒有文字層」)**當場消失**
// ⇒ 使用者以為是我們壞掉。原因必須跟著 entry 存活到下次成功為止。
LastError string `json:"last_error,omitempty"`
// FailCloudVersion=最後一次失敗時,這個帳號的雲端版本(/health 的 bundle_version""=當時不知道)。
// 為什麼要記(inkstone/arcrun-rag#196):「單次呼叫子請求太多」在 1.4.64 之前是雲端引擎的病,
// 之後就是這張卡真的太大。只看錯誤原文分不出是哪一種——要知道**當時打的是哪一版雲端**,
// 才能做到「舊雲端造成的暫停,雲端更新後自己再試一次;新雲端上還撞牆就照舊暫停,不反覆燒額度」。
FailCloudVersion string `json:"fail_cloud_version,omitempty"`
// ── 雲端對帳(`inkstone/arcrun-rag#140`2026-08-26)──────────────────────
// 病:上面那個 IngestedHash 的章**永遠不會過期**。雲端 08-14 被重裝/清空之後,
@@ -91,6 +96,9 @@ const MaxFailBeforeSkip = 8
// Manifest 對應一個被勾選的資料夾。
type Manifest struct {
FolderID string `json:"folder_id"`
// CloudVersion=這一輪這個帳號的雲端版本(執行期由呼叫端填,不寫進檔案)。
// MarkFailed 把它記進病歷、ShouldRetry 拿它判斷「雲端是不是已經修好那面牆」(#196)。
CloudVersion string `json:"-"`
// FolderCardHashes=每個子資料夾索引卡的內容雜湊(鍵=相對監看根的目錄路徑)。
// 冪等用:內容沒變就不重送(同 InventoryHash 的角色,只是一層變多層)。
// 資料夾消失時由 syncFolderCards 清掉對應的鍵,不讓這張表無限長大。
@@ -234,6 +242,7 @@ func (m *Manifest) MarkIngestedBy(path, sourceHash string, at int64, extractor s
// 成功即清掉失敗狀態(t195):下次再壞會從第一階退避重新算起。
e.FailCount, e.LastFailAt, e.NextRetry = 0, 0, 0
e.LastError = ""
e.FailCloudVersion = ""
// #140:預設「這次有送卡上雲」;真的一張卡都沒送的那條路由呼叫端補打
// MarkNoCloudCard(見 direct.go 的 cards 為空分支)。預設值放這裡而不是
// 讓呼叫端每次都設,是因為漏設的方向要落在**安全的那一邊**:
@@ -275,6 +284,7 @@ func (m *Manifest) MarkFailed(path string, at int64, reason string) bool {
}
e.FailCount++
e.LastFailAt = at
e.FailCloudVersion = m.CloudVersion // #196:記下這次撞的是哪一版雲端
if strings.TrimSpace(reason) != "" {
e.LastError = reason // 存真因;退避訊息由呼叫端另外組,不覆蓋這裡
}
@@ -344,11 +354,36 @@ func (m *Manifest) ShouldRetry(path string, now int64, force bool) bool {
if e.FailCount >= MaxFailBeforeSkip {
// #201:「雲端當時還是舊版、沒有 AI」不是這個檔的病——雲端更新之後要自己再試。
// 退避窗口照舊(最長 6 小時一次),雲端還沒更新的話頂多每 6 小時白問一發。
return isOldCloudText(e.LastError) && now >= e.NextRetry
// #196:同理,「單次呼叫子請求太多」在雲端 1.4.64 之前是引擎的病(每個節點多打 3 發附帶呼叫)。
// 病歷是在舊雲端上記的、而現在的雲端已經修好 ⇒ 再試。
// 在已修好的雲端上又撞 ⇒ 這次 MarkFailed 會記下新版本號 ⇒ 不再自動重試(卡真的太大,重撞只是燒額度)。
return (isOldCloudText(e.LastError) || m.subrequestWallFixedSince(e)) && now >= e.NextRetry
}
return now >= e.NextRetry
}
// subrequestFixedRelease=雲端第一個帶「每次呼叫附帶子請求改成常數」修正的版本
// Arcrun 1eb26c9,首次出貨=1.4.64inkstone/arcrun-rag#196)。
const subrequestFixedRelease = "1.4.64"
// isSubrequestLimitText=病歷是 Cloudflare「單次 Worker 呼叫子請求太多」。
func isSubrequestLimitText(msg string) bool {
return strings.Contains(msg, "Too many subrequests")
}
// cloudHasSubrequestFix=這個雲端版本已經帶子請求修正(""、舊日期格式、比 1.4.64 舊 ⇒ false)。
func cloudHasSubrequestFix(version string) bool {
head := strings.TrimSpace(strings.SplitN(version, "+", 2)[0])
return isSemverLike(head) && compareSemver(head, subrequestFixedRelease) >= 0
}
// subrequestWallFixedSince=這筆暫停是舊雲端的子請求牆造成的,而這一輪的雲端已經修好。
func (m *Manifest) subrequestWallFixedSince(e *ManifestEntry) bool {
return isSubrequestLimitText(e.LastError) &&
cloudHasSubrequestFix(m.CloudVersion) &&
!cloudHasSubrequestFix(e.FailCloudVersion)
}
// isOldCloudText=病歷上寫的是「雲端還沒有這個功能」(舊版雲端),不是檔案本身的問題。
func isOldCloudText(msg string) bool {
return strings.Contains(msg, "沒有綁定 Workers AI") || strings.Contains(msg, "沒有雲端萃取功能")