|
|
4c962dfec1
|
docs(arch): ADR-019 影片/圖片/批次改走同機 localhost 直連(契約定稿)
新增 ADR-019:影片/圖片/批次上傳從經雲端 tunnel 改為同機瀏覽器直連
local-agent 的 localhost endpoint,修補 design-doc §5.3「大檔不走 tunnel」
原則未套用到 media 的邏輯漏洞。採混合路徑(上傳走 localhost、控制面 +
MJPEG 結果 + 推論 WS 仍走 tunnel)。
已完成 security pre-implementation review(1 Critical + 3 Major)+ architect
依審查修正契約 + security confirm-only 複審 APPROVED。契約含:
- C1: route 分離(新 route /api/local/media/upload/* 一律要 token、不看 Origin),
消除「無 Origin 免 token」後門
- M2: Host header 驗證 = loopback 升為必做(DNS rebinding 緩解)
- M3: CORS 雲端 origin 完整字串精確比對 + Allow-Credentials: false
- 議題1: serial 回 SHA-256(visiona-local-v1‖serial) 雜湊、移除 agentVersion
- M1: media size 上限(video≤500MB / batch 合計 80MB)+ temp 檔清理升為必做
ADR 維持 Proposed,待實作完成 + code-level security 複審後轉 Accepted。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-30 03:31:16 +08:00 |
|
|
|
c595bb8b91
|
fix(frontend): 影片上傳上限 500→90MB 對齊 nginx,解 HTTP 413
stage 推論工作區上傳影片撞 nginx client_max_body_size 100M 回 413,
但前端影片上限標 500MB(af44a9f 只改前端沒同步 nginx)。
過渡修復:MAX_VIDEO_BYTES 調回 90MB,留 10MB buffer 給 multipart
overhead(boundary/header/deviceId 欄位),可靠避開 100M 硬上限。
未來影片改走 localhost 直連(ADR-019)後可放寬此值。
- media.ts:94 MAX_VIDEO_BYTES 500→90MB + 註解說明過渡性質
- media.test.ts 常數斷言 + 邊界 89/90/91MB
- i18n zh-Hant/en 影片 hint 500→90MB
- 模型上傳(500MB)/圖片(20MB)/批次上限未動
Reviewer: Approve(0 Critical/Major/Minor)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-26 21:17:39 +08:00 |
|
|
|
9397a4d31f
|
docs: 新增三平台 build troubleshooting 參考
把 M10 這輪三平台 build 踩的雷整理成 troubleshooting 文件,供未來
build 安裝包時參考。
涵蓋 9 條逐項 troubleshooting(統一格式:症狀 → 根因 → 為什麼發生
→ 解法 → 如何根治)、快速症狀索引表、5 條橫向教訓、KneronPLUS
2.0.0 vs 3.1.2 版本相容附錄、build 驗收 checklist。
核心教訓:症狀常不指向根因(Error 12 是 input size、pip 秒退是 wheels
多版本、中文亂碼是 stdio code page),macOS 開發環境測不到大量
Windows-only 問題(編碼、DLL 路徑、SDK 版本差異)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-24 09:08:13 +08:00 |
|
|
|
f62141a925
|
fix(bridge): Windows 上中文標籤與 log 亂碼
使用者截圖標籤顯示「撣�」而非「布」。實證:布 的 UTF-8 位元組
e5 b8 83 用 cp950 解碼正好得到「撣�」。
根因是 bridge 的 stdio 綁在系統 ANSI code page(繁中 Windows = cp950),
而非 UTF-8。兩個方向的表現不同:
- Go → Python(stdin):Go 的 json.Marshal 不 escape 非 ASCII,送出的是
原始 UTF-8,Python 卻用 cp950 解碼 → 標籤壞掉。這是實際的損壞路徑。
- Python → Go(stdout):json.dumps 預設 ensure_ascii=True 會轉成 \uXXXX,
所以碰巧沒事 —— 但那是巧合不是設計。
同一根因也造成 log 的「(source: declared) �X SDK did not report」,那個
�X 是程式碼裡的 em dash 編碼失敗。
修正兩層,各自不可省:
1. kneron_bridge.py 在 module import 時強制 stdio 為 UTF-8(早於任何 I/O,
import 期間的 traceback 也涵蓋),並在 os.fdopen 明確指定 encoding
—— 那是 JSON-RPC 回應通道,reconfigure() 碰不到它,目前只靠
ensure_ascii 巧合存活。errors="replace" 是刻意的:bridge 崩潰會讓裝置
離線,比一個壞字元嚴重得多。
2. kl720_driver.go 加 PYTHONUTF8=1。實測 PYTHONIOENCODING 只影響 sys.stdin、
PYTHONUTF8 才管得到 os.fdopen 與 locale.getencoding(),單一層都不夠。
Go 端 stdout scanner 不需改(bufio.Scanner 是 byte-oriented,不做轉碼)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-23 02:00:16 +08:00 |
|
|
|
4f316bb3b8
|
fix: wheels 目錄累積多版本導致 pip 解析失敗,app 啟動失敗
Windows 啟動卡在階段 2,pip 在 2 秒內失敗:
ERROR: Cannot install certifi 2026.2.25 and certifi 2026.6.17
because these package versions have conflicting dependencies.
ERROR: ResolutionImpossible
根因:Makefile 的 vendor-wheels* 用 pip download --dest 但從不清空目錄,
payload-* 複製時也只疊上去不清。upstream 每發一次新版就多留一顆,於是
`pip install *.whl` 同時收到同一套件的多個版本,相依解析器立刻放棄。
vendor/wheels/darwin 目前 16 顆 = 9 個套件 + 7 顆重複舊版,與 log 的
「正在安裝 16 個 Python 套件」完全吻合。
三層修正:
1. ensurePythonRuntime 的 Auto 分支不再丟棄 bundled/system 的失敗原因。
這是使用者連續三輪拿不到線索的原因 —— 它不是根因,是放大器。
pip 的完整輸出本來就在 error 裡,只是被這裡擋掉。
2. selectLatestWheelPerPackage:送 pip 前每個套件只留最新版。這層才救得了
已經出貨的安裝包,只修 Makefile 對使用者手上那包無效。套件名依 PEP 503
正規化,版本用整數逐段比(避免 2.9 > 2.10 的字典序錯誤)。
3. Makefile 新增 clean_wheels_dir(沿用 copy_bundled_data 的路徑防護),
vendor-wheels* 先清再下載、payload-* 複製前先清,從源頭杜絕。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-23 00:55:42 +08:00 |
|
|
|
34bc361527
|
fix(app): Python 相依檢查不得阻斷啟動
上一個 commit 讓 Windows 完全開不了:階段 2 直接失敗,
「python runtime unavailable: no python runtime available」。
根因:健康檢查的 probe 環境與實際跑推論的環境不一致。
`import kp` 在 import 當下就要載入 6 個 native DLL(libkplus / libusb /
libwdi 等),這些不在 Windows 預設搜尋路徑。專案裡每一處真正用 kp 的地方
都自己補了路徑(kl720_driver.go:142 注入 PATH、platform_windows.go:68
用 add_dll_directory),只有新加的 probe 是裸跑 python -c "import kp"。
於是健康的 venv 被判成壞掉 → 重裝 wheels → 用同一個壞 probe 再驗一次 →
仍失敗 → 回 error → 連 app 都進不去。使用者前一天才手動重建好、推論正常
的環境,裝上新版就完全無法啟動。
修正:
- reuseExistingVenv 不回傳 error。venv 存在時的回傳值恆等於舊版
(永遠是 pythonBin, nil),要退化成阻斷版必須改簽章,不會不小心發生
- 首次安裝路徑區分致命與非致命:pip 真的失敗才算致命,probe 沒過就放行
並記 warning
- probe 在 import 前補上 DLL 搜尋路徑,對齊 kl720_driver.go 的實際做法
- 新增 GetPythonDepsWarning binding,讓提示出現在 app 內而非啟動阻斷點
原本要解的「wheels 半套安裝永久卡住」仍然有解,但改為非阻斷:修不好也讓
使用者進到 app 看見具體原因,而不是擋在啟動畫面外。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-23 00:18:13 +08:00 |
|
|
|
f9fbc778be
|
fix: input size 優先序錯誤導致 Windows 推論失敗 + venv 半套安裝永久卡住
## input size 優先序(Windows Error 12 的根因)
先前把使用者手填的 inputSize 排在檔名解析之前,Windows 上 SDK 讀不到
shape 時就用了隨手填的 640x640(模型實際 224x224)→ 推論回
KP_ERROR_INVALID_PARAM。改動前靠檔名 fallback 的 224 反而是對的。
新優先序(可信度由高到低):
SDK > 檔名明確解析 wNNNhNNN > 使用者宣告 > 已知 model id > 寫死預設
檔名排在宣告之前,因為它由編譯工具鏈產生、沒有人為亂填空間;宣告不降到
最底,是因為使用者若刻意填對,仍比無資訊時的預設值貼近現實。
配套:_size_from_name_or_none 讓「真的解析到」與「用了 default」可區分
(舊版兩者回傳型別相同,預設值會偽裝成檔名來源蓋掉宣告值)。
## KneronPLUS 3.1.2 相容
3.1.2 把 shape 搬進巢狀 union,TensorDescriptor 不再有 shape_onnx:
2.0.0 TensorDescriptor.shape_onnx
3.1.2 TensorDescriptor.tensor_shape_info.data → V1 .shape_onnx / V2 .shape
舊碼 getattr 失敗被 except 靜默吃掉,SDK 層在 Windows 永遠落空。現在
兩版都支援,不依賴 enum 版本判斷。
## Error 12 診斷
KP_ERROR_INVALID_PARAM 對使用者無法理解,現在附上當前 input size 與
來源,並針對 declared 來源提示「此尺寸來自手動填寫欄位,請優先確認」。
原始錯誤保留不吞。
## venv 半套安裝
app.go 原本只檢查 python.exe 存在就跳過安裝,導致 wheels 裝到一半中斷
後每次啟動都跳過、永遠卡住且無提示,使用者必須手動刪整個 runtime 目錄。
改為比對 wheel 清單指紋(快路徑不啟動 process),不符才實跑
import kp/numpy/cv2 驗證,失敗則只重跑 wheels 安裝。標記檔僅在 pip 成功
且 import 驗過後才寫入,不留「已就緒」假象。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 23:41:19 +08:00 |
|
|
|
a05d206c07
|
fix(build): copy_bundled_data 改為冪等,清掉前次 build 的殘留
Windows/Linux build 卡在「預期 2 個 .nef,實際 8 個」。
payload-macos 呼叫前有 rm -rf payload/darwin,但 payload-windows 與
payload-linux 刻意不清(build-server-* 已先把 binary 放進 bin/),
導致 data/ 留著白名單機制加入前那 8 個 .nef,與新複製的 2 個相加後
觸發後置檢查。
後置檢查本身是對的 —— installer/windows/visiona-local.iss:86 是
recursesubdirs 整包收,殘留什麼就出貨什麼。問題在 helper 不冪等。
改為複製前先清空目標 data 目錄。清整個目錄而非只清 nef/,因為該目錄
內容全由此 helper 產生,只清 nef/ 無法處理未來移除非 nef 檔案的情況。
rm -rf 加四層防護:拒絕空值、絕對路徑與 ~、含 .. 的跳脫、不符
payload/*/data 格式的目標,另檢查目標不是檔案。用 case 而非 [[ ]]
以相容 Git Bash。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 22:15:26 +08:00 |
|
|
|
61593f004d
|
fix(build): bootstrap-windows 讓 MSYS2 bash 找得到 Go / wails / node / pnpm
Windows build 卡在 `go: command not found`,但 PowerShell 端 go 明明可用。
原因是腳本用 `bash.exe -l` 呼叫 make,login shell 會 source /etc/profile
重建 PATH,而非繼承 Windows 的。`MSYS2_PATH_TYPE=inherit` 只對 MSYS2
自己的啟動器有效,直接呼叫 bash.exe 時不生效。
腳本原本就為 Inno Setup 與 Python 手動 export 路徑,只是漏了這四個工具。
現在比照辦理:
- 新增 Find-ToolDir,先用 Get-Command(可找到自訂磁碟的安裝),
找不到再退回已知安裝位置,都失敗則明確 Fail 並附安裝指引
- wails 路徑改問 `go env GOPATH` 而非假設 %USERPROFILE%\go,
且用 go.exe 絕對路徑呼叫(否則在本 bug 情境下自己也會找不到 go)
- pnpm 獨立偵測,npm i -g 的安裝位置與 node 不同
- 各目錄個別加雙引號後以 : 串接,避免 Program Files 的空白被斷詞
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 21:30:31 +08:00 |
|
|
|
a7bc67baba
|
chore: ignore .autoflow / .mcp.json / graphify-out
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:27:45 +08:00 |
|
|
|
744283bd28
|
feat(build): 安裝包只打包指定模型並改用中文名稱
安裝包從 8 個 .nef 縮減為 2 個,models.json 保留完整定義以便日後
加回。
- Makefile 加 BUNDLED_NEFS 白名單,三平台共用 copy_bundled_data
helper;用 POSIX find/cp 而非 rsync(Windows CI 的 Git Bash 沒有
rsync)
- 白名單檔案不存在時 build 直接失敗,並在複製後驗證 models.json
存在且 .nef 數量相符 —— 避免產出「安裝後 0 個模型」卻回報成功
- FCOS Detection (KL520) 改名為「物件辨識」
- Tiny YOLOv3 (KL520) 改名為「人型監測」
(只改 name/description,id 不動以免影響既有設定與紀錄)
- Repository 啟動時過濾 .nef 不存在的模型,否則使用者會看到未打包
的模型並在選取後拿到莫名錯誤
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:27:40 +08:00 |
|
|
|
5c1c37d151
|
feat(frontend): classification 結果呈現與推論期控制項
detection 畫框、classification 改在影像右上角疊標籤(不畫框),
並在推論面板提供即時切換解析方式與上傳 label 檔。
- 新增 InferenceOverlay 依 taskType 分派;ClassificationOverlay 用
DOM 而非 canvas,讓 CJK 排版與 aria-live 交給瀏覽器處理
- 標籤防閃爍:挑戰者需連續 3 幀居冠才切換,或信心度領先 15% 直接切;
低於門檻立即清空(顯示過期標籤比空白更糟)
- 身分比對優先用 classIndex,避免換 label 檔時被誤判為換類別
- 推論設定卡片:即時切 detection/classification、上傳 .txt label、
清除 label,皆不需重燒
- classification-result 原本無條件渲染,導致 detection 模式下側欄
永遠顯示「分類結果」與空圖表,改為依 taskType 切換
- 清掉 camera-overlay 每幀執行的 debug console.log
- 補 ResizeObserver stub,原本任何渲染 InferencePanel 的測試都會
在 jsdom 直接拋錯
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:27:28 +08:00 |
|
|
|
4583406efa
|
feat(api): 推論期切換解析方式與上傳 label 檔
新增 POST /api/devices/:id/inference/options,讓已燒錄的模型不重燒
即可切換 detection/classification 與更換 label。
KL520 一次只能載一個模型(換模型必須重燒),但解析方式與 label 都
只是後處理與顯示層,不碰 NPU,因此可即時生效。
- JSON: {taskType?, labels?};multipart: taskType + labelFile
- 欄位用指標型別以區分「省略 = 不動」與「labels:[] = 清空」
- label 解析器(internal/labelfile):格式 <index> <名稱>
略過空行與 # 註解、trim CRLF、名稱含空白只 split 第一個空白、
index 非整數/負數/重複則整檔拒絕並回報行號、稀疏 index 補空字串
- index 上限 4095、檔案上限 256KB,防記憶體耗盡
- 非法 taskType 一律拒絕(含舊別名 detection),因為 bridge 對無法
辨識的值是靜默 fallback,忽略會回 200 但用錯解析方式
- 走窄能力介面 InferenceOptionsDriver,不動 DeviceDriver 簽章
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:27:16 +08:00 |
|
|
|
ddd1aae5d1
|
feat(server): 把 model metadata 傳進推論鏈路
models.json 宣告的 taskType/labels/inputSize 原本在 flash 時被丟棄
(只傳 modelPath),導致 bridge 只能靠檔名猜測模型類型與尺寸。
- FlashOptions 帶 TaskType/Labels/InputWidth/InputHeight
- 抽出 buildLoadModelCommand,四處 load_model 呼叫點(初次 + 三條
retry 路徑)統一走它,並加測試釘住呼叫點數量與「不得有手寫 payload」
—— 讓漏改 retry 路徑在結構上不可能發生
- ClassResult 加 ClassIndex(不加 omitempty,index 0 是合法值)
用 FlashOptions struct 而非裸參數,未來擴充欄位不需再動 interface
與所有 test fake。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:27:02 +08:00 |
|
|
|
f14d24bd7b
|
feat(bridge): classification 自適應解析 + 推論期動態切換
新增 classification 推論支援,核心是「不預設模型長相」:
- output shape 自適應:支援 (1,C)/(C,)/(1,C,1,1)/(C,1,1)/(1,1,1,C)
及任何 squeeze 後為一維的張量;類別數從 shape 動態取得,
移除原本寫死的 num_classes=1000
- 無法解析時明確報錯(附實際 shape),不靜默回空結果
- logits vs 已 softmax 自動偵測:sum≈1.0 且全非負才跳過 softmax
(容差 1e-4,實測 float32 softmax 偏差最大僅 ~4e-7)
- label 為純顯示層:有注入用 label、沒有則輸出原始 enum class_N
- input size 三層來源:SDK > 宣告值 > 檔名猜測,log 標示來源
- 新增 handle_set_inference_options:不重載模型即可改解析方式與 label
修正:
- _detect_model_type 改為外部指定優先,解決未知檔名被誤判成
tiny_yolov3 而回傳空結果的根因
- handle_disconnect/reset 未清 label 狀態,導致換模型後沿用舊 label 表
- load_model 失敗路徑會污染全域 metadata,改為 defer-until-success
- taskType 值域統一為 object_detection(原本回 detection)
- 檔名解析原本只取 width 丟棄 height,非正方形模型會被壓成正方形
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-22 19:26:51 +08:00 |
|
|
|
af44a9f9ba
|
feat(frontend): flash 完成工作區入口醒目化 + 影片上限 200→500MB
驗收後兩個 UX 微調:
- flash 載入模型完成後「開啟工作區」入口不明顯、使用者要自己找 → Button
改 size=lg + ArrowRight 前往圖示 + ring-primary/40 highlight(設計系統
token、不裸色碼)。保留手動導航(點擊才去、無自動 router.push)、serial
空 disable + 離線 disable gate 維持
- 影片上傳上限 MAX_VIDEO_BYTES 200→500MB + i18n 兩語系 hint 文案。影片
upload timeout 本來就是 0(不限、後端決定)→ 500MB 不會撞 timeout
Reviewer 通過(0C/0M/0Mi/1Sug、timeout 獨立驗證、i18n 無殘留 200MB)。
tsc/eslint 0、vitest 39 passed(499/500/501MB 邊界 mock size 無 OOM +
醒目化樣式/行為)、next build 0。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-21 01:17:11 +08:00 |
|
|
|
67737334c8
|
fix(device): GET /api/devices/:id proxy 拿即時 driver status(方案 Y-2,解載入模型 disabled)
問題:連線後裝置詳情頁連線狀態顯示 unknown、載入模型按鈕永遠 disabled。
根因=雲端 GET /api/devices/:id 是純 DB 讀、沒 proxy 到 local agent → DB 只有
tunnel 層 status(online/offline/unknown)、沒有 driver 七態(detected/
connected/...)→ 前端 gate isDriverConnected 永遠 false。(上輪 C1/C5 查證
假設 status 拿得到、沒追到寫入點的漏洞)
修法(方案 Y-2、local agent 零改、gate 零改):
- backend device_driver_status.go(新):driverStatusFetcher 介面 +
forwarderDriverStatusFetcher(走既有 session.Forwarder proxy)+ envelope 解析
- devices.go devicesGetHandler:讀 DB metadata 後,device 有序號時額外 proxy
打 local agent GET /api/devices/{serial}(serial 路由對齊 WP-C)拿即時 driver
status 覆蓋 USBStatus;remoteStatus/tunnel_online 保留(offline banner 不壞)
- graceful fallback(全回 200 不掛 500):無序號/tunnel 離線/不可達/timeout/
非2xx/success:false/空status/Forwarder未配置 → 保留 DB status + Debug log
- 2s 短 timeout(不拖詳情頁)、serial path url.PathEscape 防禦
- frontend:DeviceHardwareStatus 加 unknown + coerceHardwareStatus(非七值→
unknown)+ normalizeDevice fallback disconnected→unknown(修誤顯未連接)+
i18n devices.status.unknown 兩語系(未確認/Unknown)
Reviewer 通過(0C/0M/3Mi/2Sug、Y-2 10/10、端到端追證 gate 放行 + 8 fallback
分支無一掛 500)。backend 8 測試 + frontend 49 passed、build/vet/test 綠、
gitleaks 0。端到端「即時 connected 覆蓋 unknown」需在線 agent+登入實測(單元
測試已覆蓋合併+fallback)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-17 09:25:26 +08:00 |
|
|
|
1da385f345
|
feat(frontend): 裝置詳情頁連線/斷線按鈕 + 兩步式 gate(解載入模型 device not connected)
問題:device 配對後停在 detected(rescan 偵測到硬體但沒 connect driver),
載入模型被 flash service IsConnected() 擋成 400。根因=前端漏了「連線」按鈕
(POC 有、移植時當 local-tool 專屬刪掉、詳情頁沒補回雲端版)。前端誤用
remoteStatus=online(tunnel) 當 flash 門檻,≠ device driver IsConnected()。
修法(方案 A、兩步式):
- device-detail-client.tsx:連線/斷線按鈕 + 三分支 gate(未連線→連線鈕/
連線中→loading disabled/已連線→斷線鈕+載入模型可用)+ driver status 呈現
- connect 走 serial(對齊 WP-C)、成功後 fetchDevice(UUID) 刷新(C4 混合模型,
WS 推送因 backend 501 stub 不通故用輪詢)
- 60s connect UX:loading hint「首次連線需載入韌體約1分鐘,請勿關閉頁面」
- FlashDialog gate 收緊 disabled !isOnline → !isOnline || !isDriverConnected
- device-store connect 的 api.post 加 timeoutMs:130_000(全域 30s < connect
最壞 65s,單點覆寫;> local agent 120s ctx)
- i18n devices.connect.* 13 key 兩語系
- TDD §10.5.1 過時型別修正 + 新增 §10.5.1.1 two-step connect 流程
推論線一併解決:能進 workspace 的裝置必已 connect(原無 IsConnected 檢查
會靜默空跑無框)。
Reviewer 通過(0C/0M/2Mi/3Sug、規格 12/12、gate 三分支全組合走查)。
tsc/eslint 0、vitest 44 passed、next build 0。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 15:30:33 +08:00 |
|
|
|
fac07c39d0
|
fix(local-agent): 配對前自動 rescan USB,解序號偵測時序問題
問題:配對時 exchange 撈 GET /api/devices(ListDevices=讀快取、不重新
偵測)。若 agent 啟動時 USB 尚未插入/未偵測到,快取為空 → 配對撈不到
序號 → payload 不帶 devices → 雲端裝置「尚未回報序號」,即便之後插上
USB 也不會自動重偵測。
修法(方案 A、最小侵入):
- NewLocalDeviceLister 改打 POST /api/devices/scan(ScanDevices →
Manager.Rescan() 重新偵測 USB),配對前強制重掃一次
- localDeviceListTimeout 2s → localDeviceScanTimeout 8s(真 SDK scan
kp.core.scan_devices 較慢、給餘裕,逾時走 fallback 不卡配對)
- app.go DeviceLister 注入註解同步更新(Reviewer Mi-1)
不變量保留(Reviewer 獨立驗證):
- rescan 失敗/逾時 → lister return nil(不 error)→ payload omit devices
→ 配對照常(序號是加值資訊,不中斷配對)
- Rescan 對序號身分未變的 session 保持連線(serialIdentity 相等 continue)、
只斷被拔除的 → 不誤斷在線 tunnel/inference session
- Pair 由 lifecycleMu 序列化、Rescan write lock 全量重建 → 重試冪等安全
Reviewer 通過(0C/0M/1Mi/2Sug)。兩 module build/vet/test/race 全綠、
8 個新 tunnel 行為測試 PASS(端到端快取空→rescan→序號進 payload +
逾時 fallback)、gitleaks 0。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 15:02:31 +08:00 |
|
|
|
fc7e1e0bc1
|
fix(local-agent): 修 vendor wheels 多版本累積導致 SDK 沒裝進 bundled runtime
根因:Makefile vendor-wheels 用 pip download --dest 只加不清空,跨多次
vendor-sync 累積出同套件多版本(certifi 三版/numpy 二版等共 17 wheel)→
agent bootstrap 的 ensureBundledPython 把目錄所有 .whl 當獨立參數丟 pip
install → 版本衝突 ResolutionImpossible → pip 失敗 → venv 建了但空 →
無 Kneron PLUS SDK → bridge scan 走 pyusb fallback 回假序號 0x00000000 →
裝置「尚未回報序號」+ 載入模型 device not connected。
修法(3 個 vendor-wheels target):
- 開頭 rm -f vendor/wheels/<plat>/*.whl 清空,確保每次 sync 單版本
- 結尾 dedup 驗證(sort|uniq -d 偵測多版本 → exit 1 fail-fast)
- linux 補「無專屬 KneronPLUS wheel 時退回複製 macos py3-none-any」
- .gitignore 補 local-agent/vendor|payload|dist 對稱規則(保留 ffmpeg
binary + .gitkeep 例外),防 vendor-sync 後 git add -A 撈進 build 產物
決定性 evidence(agent 實際 bundled runtime):清空 runtime → 起剛 build
的 .app → agent 自己 bootstrap 裝 9 個乾淨單版 wheel(修前 17 含重複必失敗)
→ venv 有 kp → GET /api/devices 回真序號 0xB906162C(非 0x00000000)。
Reviewer 通過(0C/0M/4Mi/3Sug)。win/linux 同邏輯已補、未在對應 runner
驗證(已知限制)。follow-up:ensureBundledPython 列舉 wheel 脆弱設計建議
改 find-links 自解析(backend 另案)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 14:21:08 +08:00 |
|
|
|
51dfd0f2d8
|
fix(frontend): normalizeDevice serialNumber 加 trim(WP-C Minor #1)
純空白字串(" ")原判 truthy → 會以空白 serial 組出必失敗的請求路由。
改為先 trim 再判定:serial !== "" ? serial : null。順帶消除字面 "0"
被 falsy 誤判為 null 的隱性行為。補純空白 → null 邊界測試。
Reviewer 通過(0C/0M/0Mi)。tsc/eslint 0、18 test 綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:35 +08:00 |
|
|
|
26b433eb10
|
fix(local-agent): review follow-up 小批(TLS Mi-3/4 + WP-0 S-3/4)
- Mi-3: insecure Transport 改用 DefaultTransport.Clone() 只覆寫 TLSConfig
(保留 proxy/timeout,消除「開 skip 順便改掉 proxy 行為」副作用)
- Mi-4: exchange 200 分支檢查 Success 欄位(避免 200+success:false 落到
誤導性的 missing session_token;既有回歸測試改用直接斷言防護不減反增)
- S-3: collectLocalDevices 全空 serial 濾掉不送 devices 陣列(payload 對稱)
- S-4: 假序號比對統一用 EqualFold(防未來 bridge 輸出 casing 變化)
Reviewer 通過(0C/0M/1Mi/3Sug)。兩 module build/vet/test + -race 綠、
gitleaks 0、TLS 行為級測試全 PASS。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:35 +08:00 |
|
|
|
59c57fa481
|
feat(device): WP-B repository 接 agents 模型 + exchange 重塑(A' 走向第二階段 Go 層)
- Device struct 加 4 欄(agent_id/agent_local_device_id/registered_at/
is_representative)+ deviceColumns 13→17 + scanDevice/SaveTx 讀寫新欄
- 新增 internal/agent package(domain + interface + in-memory + PG repo):
GetOrCreateAgentTx/GetByOwnerTx,advisory lock 序列化同 owner get-or-create
- exchange 重塑:建/復用 agent → representative device(綁 session_tokens、
serial=NULL)→ loop 建 N 顆真 USB device(R1 完整 N 顆非只第一顆)
- List filter is_representative=false + DeviceListItem 回傳 agent_id/registered_at
- 併入 WP-0/0005 follow-up Minor:Mi#2 lost-update 收斂(tx 內查詢+局部更新)
/ Mi#3 過時註解 / Mi#4 空 serial 回 ErrNotFound / Mi#5 serial 白名單
^0x[0-9A-Fa-f]{8}$ + 去重 / S-1 firmware forward-compat / S-2 device Name 衍生
守 ADR-018 A'(一 owner N agents、session_tokens FK 物理不動、不加 owner
unique 為多機器留路)。Reviewer 通過(0C/0M)。5 套件 dbtest 130 全綠
(db 19/device 37/agent 13/api 172/cmd 60)、build/vet/test 綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 11:40:17 +08:00 |
|
|
|
8369cab85c
|
feat(db): migration 0005 agents 模型(個人設備管理 A' 走向第二階段地基)
- 新增 agents 表 + devices 加 4 欄(agent_id/agent_local_device_id/
registered_at/is_representative)+ 2 index + 純 SQL data migration
- data migration 採 R-A:agent.id := device.id 決定性推導、冪等、
soft-deleted device 排除(WHERE deleted_at IS NULL)
- 守住 ADR-018 走向 A':不碰 session_tokens FK、不改現有 partial
unique index uq_devices_owner_serial_active、對 0001-0003 零破壞
- migrate_0005_db_test.go 6 個 dbtest case(apply/data migration/
rollback 對稱/re-apply 冪等/session_tokens 零影響/既有 device 讀寫回歸)
- 修 TestMigrate_UpDownUp 編號 gap 假設(0001/2/3/5 無 0004):
assert.Equal(topVer-1) → assert.Less(downVer, topVer)
- .gitignore 加 .logs/ + **/.logs/(本機執行 log per-branch 不進 git)
Reviewer 通過(0 Critical/0 Major/3 Minor/4 Sug)。全 db package 41
dbtest 130 綠、build/vet/test 綠、gitleaks 0。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-16 04:05:53 +08:00 |
|
|
|
72544d00ba
|
docs(adr): ADR-018 §8 addendum — C5 實作偏差回填(路由段維持 UUID)
- §5.5 加補註指向、原文一字未動(ADR 不可變原則)
- §8.1 記錄 C5 偏差證據鏈(fetchDevice 純 DB / API 無 GetBySerial /
帶 serial deep-link 必 404)與最終實作(路由段 UUID、serial 頁內派生、
入口 disable 全落實);決策本體 FE-A 與 C1–C4/C6–C8 不變
- 裁定來源:wp-c-frontend-serial-routing-review.md(reviewer 獨立驗證)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-11 08:39:32 +08:00 |
|
|
|
12f5bf6c47
|
feat(frontend): WP-C serial 路由落地(ADR-018 FE-A 混合模型)
- DeviceSummary 加 serialNumber(serial_number/serialNumber 雙 key 容錯、
缺欄/空字串 → null)
- 五個 proxy 操作識別值換 serial:connect/disconnect、flash POST+進度 WS
(單一派生點保 room key 一致)、inference WS、camera start/stop+WS、
media 三 upload
- serial 為空全面 disable + 提示(workspace banner/media 占位/flash-dialog/
device-card tooltip/device-detail 兩鈕+序號 InfoRow/選擇頁);i18n 兩語系
- UUID 組維持:fetchDevices/fetchDevice/unpair/詳情頁路由
- C5 偏差(路由段維持 UUID):與 FE-A 定案一致、reviewer 獨立驗證成立
(API 層無 GetBySerial、帶 serial deep-link 必 404);文件回填另派
- evidence:tsc 0 / eslint 0 / next build 15 route / 觸及 11 測試檔 99 passed
- review:通過 0C/0M/3Mi/2Sug(.autoflow/05-implementation/review/
wp-c-frontend-serial-routing-review.md)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:58:52 +08:00 |
|
|
|
12b1fe3bad
|
fix(local-agent): Rescan 序號身分比對替換 stale session + 鎖外 Disconnect
- serialIdentity() 三態比對(空/假序號 0x00000000 正規化為無身分、對齊
ADR-018 R2):Rescan 對既存合成 ID 比對身分——同一顆保留 live session、
換位/拔插以新 info 替換 session,serial 索引重建即正確
- stale drivers 改釋放鎖後 Disconnect(不再阻塞 GetDevice 路由)+
Disconnect 錯誤改 WARNING log
- 測試 seam:detectFn/newDriverFn 注入 + newSessionDriverLocked 統一建構
(Start 行為零變動);新 4 支行為級測試(拔除位移/換位 rebind/
同序號保 session/無身分回歸)
- evidence:build/vet/test 全綠 + -race ok;review 通過 0C/0M/0Mi/4Sug
(wp0-serial-routing-review.md「Minor #1 修復審查」章節)
- WP-C 阻擋項解除;觀察項:多裝置實測留意 changed identity log
(偵測順序抖動時解法在 detector 端排序)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:44:47 +08:00 |
|
|
|
b9ee184586
|
feat(device): WP-0 序號串通 + serial 路由(ADR-018 走向 A' 第一階段)
- local-agent/server:detector 保留 kn_number(parseScanDevices 可測化、
合成 ID 語意不動)+ DeviceInfo.SerialNumber + Manager serialToLocalID
反查表 + GetDevice 雙查(sessions 先、serial 後,純加法)
- visiona-agent:pairing exchange 帶本地裝置清單(DeviceLister 失敗不中斷
配對、timeout 2s、omitempty 舊版相容)
- visionA-backend:exchange 收 devices —— R1 取第一顆可用序號、R2 假序號
0x00000000 寫 NULL、R4 同 owner 同序號復用既有 device_id(防 23505)、
pg+mem 兩實作對齊
- 五個 proxy 操作(flash/inference/camera/connect/disconnect)收斂於
GetDevice 單一入口,serial 路由一處涵蓋
- docs:api-spec.md §2 增補 POST /api/pairing/exchange(schema + R1/R2/R4)
- 測試:行為級四環節鏈 + dbtest 130 實跑 6/6 + DBOn 回歸 4/4;
三 module build/vet/test 全綠
- review:通過 0C/0M/6Mi/5Sug(.autoflow/05-implementation/review/
wp0-serial-routing-review.md);Minor #1 Rescan stale session 掛 WP-C 前置
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:31:51 +08:00 |
|
|
|
3d30fdc580
|
feat(local-agent): TLS skip opt-in for self-signed stage + exchange envelope 對齊
- 新增 VISIONA_INSECURE_SKIP_TLS_VERIFY(DEV/TEST ONLY、須明確 opt-in "true"):
pairing exchange HTTP client、tunnel WSS dialer、設定頁 TestConnection 三路徑
共用 TLSConfigForDial(含 ALPN 釘 http/1.1);NewApp 唯一 env 讀取點注入欄位
- exchangeResponse 對齊雲端 /api/pairing/exchange success envelope
(account/relay_url 選填 fallback 保留、舊頂層格式回歸防護)
- .gitignore:.env.stage* + !.env.stage.example + *.pptx(堵 secrets 誤入)
- start-agent.sh(新增):public 模式 export skip env + 預檢 curl https 帶 -k
- 測試:自簽 TLS server 行為級(預設拒絕驗 x509 / 開啟通過)+ opt-in 規則
11+5 案例 + TestConnection 3 測試;go build/vet/test 3 packages 全綠
- review:2 輪通過(.autoflow/05-implementation/review/tls-skip-uncommitted-batch-review.md)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
2026-07-10 09:01:10 +08:00 |
|
|
|
276910854e
|
docs(adr): ADR-018 一 agent 多 device 模型 + serial 路由(走向 A')
個人設備管理資料模型決策,使用者裁決 Accepted。
- 走向 A':新增 agents 表 + devices 改實體 USB 掛 agent_id +
session_tokens.device_id 保留物理 schema 改綁代表 device(避開破壞性
FK migration)+ 路由統一用 serial(kn_number) + local agent serial 反查
雙查(純加法、保護剛做完的推論)
- 前端 FE-A 混合:DB 操作用 UUID、路由到 local agent 操作用 serial、
serial 空的 device 路由操作 disable
- 關鍵論證:推論路由不經 session_tokens.device_id(proxy pickActiveSessionToken
按 userID),單一 FK 非硬傷;真缺口是 UUID↔local key 未對齊
- migration 0004(序號、無 DDL 可先交付)+ 0005(agents 模型)
替代方案(mapping 版 A/B、FE-B)與否決理由、R-Route 查證結論已併入 ADR。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 08:39:27 +08:00 |
|
|
|
4f50ad7350
|
feat(ws): flash-progress WS tunnel proxy(後端,載入模型進度回顯)
flash「載入模型到裝置」後端缺口:flash-progress WS 從 501 stub 換真
tunnel proxy,複用 inference WS 的 path-agnostic handler。
- camera.go registerWebSocketRoutes:加 /ws/devices/:id/flash-progress
(複用 newWebSocketProxyHandler、掛 wsAuthGroup same-origin cookie)
- stubs.go:刪 flash-progress 501 stub(成對防 radix panic)+ 更新註解
- api.go:更新 wsAuthGroup 註解(現含 inference + flash-progress 兩條裝置級 WS)
- camera_ws_test.go:+3 test(NoForwarder/TunnelDisconnected/CoexistsWithInference)
- all_endpoints_require_auth_test.go:收窄白名單,flash-progress 從 skipped→
covered 納入「無 cookie 應 401」回歸檢查(守得住證明:移除 auth 即 FAIL)
契約:WS /ws/devices/:id/flash-progress、payload raw {percent,stage,message?,error?}
透明轉發。Reviewer 0C/0M/1Mi 通過(Mi 註解已修)。build/vet/全回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:30:36 +08:00 |
|
|
|
051994ed54
|
feat(workspace): 圖片/影片/批次推論 tab + 載入模型到裝置 FlashDialog(前端)
推論工作區前端塔3 + flash 前端(i18n/device-detail 交纏、一批 commit)。
塔3(圖片/影片/批次 tab、複用塊1/2 顯示管線 0 重造):
- media.ts 上傳 helper(XHR image/video/batch)+ media-uploader + media-tab
- workspace-client 3 tab 打開 + activeTab 互斥 camera/media WS
- video seek 用後端 durationSeconds 換算(脫離 fps 耦合、Reviewer M-1 修正)
- 補 tab 切換 WS reset 測試(S-1)
- Reviewer 2 輪通過
flash 前端(載入模型到裝置、補「進工作區前置」缺口,照 POC 移植):
- flash-dialog + flash-progress(選 model + 相容性檢查 + WS 進度)
- flash-store(throw-based ApiError)
- use-flash-progress(onOpen 才 POST 防 race + hasStartedRef 防重連重複)
- hardware-compat(targetChip 單值、寧鬆勿嚴、UX 警示非安全閘)
- device-detail-client 掛 FlashDialog(gate 未改、flash 完 fetchDevice 解鎖)
- Reviewer 0C/0M
契約:same-origin cookie、WS/stream/upload URL 相對路徑無 token、i18n 雙語對稱、
設計 token 不裸色值。全前端 tsc/eslint clean、塔3 30 test + flash 45 test 綠。
(全套 11 failed 全屬 conversion-store 既有 flaky、未觸碰)
follow-up:flash WS 逾時提示(Mi)、--success token(design)、conversion-store flaky 釐清。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:27:14 +08:00 |
|
|
|
81a32a5bf8
|
feat(stage): nginx 加 /ws/ location(推論 inference WS + 其餘 /ws/* stub)
兩個 server block(公網 stage-9527 + 內網 IP 直連 192.168.0.130)各加
一條 location /ws/ → proxy_pass api-server(:3721)。缺這條時 /ws/* 會落到
catch-all → Next.js → 404,擋住前端推論頁 WS 握手。
- WS upgrade 三要素(http/1.1 + Upgrade + Connection $connection_upgrade)
- 長 timeout 86400s + proxy_buffering off(比照 /tunnel/connect)
- 不影響既有 /tunnel/connect(不同 path 前綴、互不 shadow)
- 兩 block header 對齊(含 X-Forwarded-Host)
驗證:/ws/devices/test-id/inference 不帶 cookie → 401(過 backend auth)
不再 404;/tunnel/connect 回歸 401;preset 下載 200/206;demo 設定保留。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 06:00:59 +08:00 |
|
|
|
0010cc35c3
|
feat(workspace): canvas overlay 疊 bbox + WS 即時推論面板(前端塊2)
推論工作區前端塊2:canvas overlay 疊在 MJPEG <img> 上、接 WS
inference:<deviceId> 推的 raw InferenceResult、即時繪 bbox+label+信心度;
右側 Inference panel 從 Phase 1 佔位換成 WS 即時結果清單 + FPS/延遲。
照 edge-ai-platform POC 移植(唯讀參考)。
- camera-overlay.tsx:normalized bbox→canvas 像素換算、label 框
- inference-panel.tsx:指標 + 清單 + aria-live 無障礙
- inference-store.ts:fps/avgLatency、MAX_RESULTS=100 上限
- use-inference-stream.ts:用既有 useWebSocket(same-origin cookie、無 token URL)
- workspace-client:overlay 塞 CameraFeed slot、訂閱 WS(僅 isRunning && isOnline)
canvas 顏色讀設計 token(--chart-*/--background,getComputedStyle 快取、
主題切換才重讀)、跟隨深淺主題、不裸色值。
Reviewer 2 輪通過(0C/0M)。修正:
- M1 overlay 座標錯位:feedSize 初值 null、未拿真實顯示尺寸不繪(MJPEG 非
4:3 不錯位)
- M2 效能 jank:palette 快取 useState + MutationObserver 主題切換才重讀
(不再每 frame getComputedStyle)
- m2 label fallback 色改實際色(canvas 不認 currentColor)
- m3 isValidBBox guard:壞 payload 略過不 crash
+6 新測試(M1 640×360→height=360、M2 呼叫數不增、m3 NaN/undefined 略過)。
tsc/eslint clean、塊2 範圍 37 test 綠。(全套 11 failed 全屬 conversion-store
既有 time-based flaky、未觸碰)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 05:03:35 +08:00 |
|
|
|
dc6ca211ae
|
feat(ws): tunnel WS forward — 推論結果經 tunnel 推回雲端(後端塊2)
推論工作區後端塊2:實作 WS forward,讓 local agent 的推論結果 WS
(inference:<deviceId>)經 tunnel 推回雲端瀏覽器 canvas overlay。照
edge-ai-platform POC relay/server.go proxyWebSocket 移植(唯讀參考)。
- forwarder.go ForwardWebSocket:OpenStream→寫 upgrade→讀 101→WebSocketConn
(新增 WebSocketConn/wsUpgradeError/AsWSUpgradeError)
- proxy.go newWebSocketProxyHandler:hijack browser→回寫 101→雙向 io.Copy
cross-close(無 goroutine leak)+ copyWebSocketUpgradeHeaders(保留
Sec-WebSocket-*、剝 Authorization/Origin)
- camera.go registerWebSocketRoutes:GET /ws/devices/:id/inference
- api.go wsAuthGroup(/ws + AuthMiddleware):same-origin cookie 認證,
無 token-in-URL(security 定案)
- stubs.go:移除 WS inference 501 stub、更新過時 doc comment(Mi-1/2)
架構差異:POC 單 binary,visionA api-server(auth)+remote-proxy 雙 binary,
remote-proxy raw byte pipe 透明穿過 WS upgrade bytes。local agent 端未動。
認證/授權(security 定案 + S1/S2):same-origin cookie、剝 Auth/Origin、
WS 不套 300s timeout、走 pickActiveSessionToken(帶別人 deviceId 也只打到
自己 tunnel;多租戶嚴格綁定屬 Phase 1 M2 debt)。
Reviewer 0C/1M/3Mi 通過(修後)。Major-1 已修:收窄 all_endpoints_require_auth_test
的 /ws/ 白名單,讓 authed inference WS 納入「無 cookie 應 401」回歸檢查
(附守得住證明:移除 auth 測試即 FAIL)。+forwarder 5 test + camera_ws
端到端四層真連線雙向 pipe。build/vet/全回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:56:54 +08:00 |
|
|
|
ba8fd454ea
|
feat(workspace): Camera tab 接真 MJPEG stream + start/stop 狀態機(前端塊1)
推論工作區前端塊1:Camera tab 從 placeholder 換成真正接 MJPEG stream
的即時畫面。照 edge-ai-platform POC camera-feed.tsx 移植(唯讀參考、
重寫為 visionA 慣例)。
- camera-feed.tsx:MJPEG <img> + ResizeObserver + overlay slot(塊2 用)+
角落標籤
- camera.ts buildStreamUrl:cache-bust 走 _t= query(encode)、token 絕不
放 URL(same-origin cookie 認證、對齊 use-websocket §10)
- workspace-client Camera tab:接 CameraFeed + 串流狀態機、掉線清串流採
render 期衍生(非 effect setState)
- use-websocket.ts:修 same-origin WS URL bug——getWsBaseUrl 回 "" 時
new WebSocket() 需絕對 URL,補 window.location wss/ws(塊2 WS 用)
- types/camera.ts + types/inference.ts(後者鏡射 local-tool driver.InferenceResult)
- start/stop 契約改打 /api/camera/start|stop
- i18n 9 key 雙語齊全
Reviewer 0C/0M 通過。13 test(camera-feed 8 + camera 5)PASS、tsc/eslint clean。
(全套 11 failed 全在 conversion-store.test.ts 既有 flaky、本次未觸碰、經 grep 證實無關)
follow-up(交塊2/3):掉線→回線 stream 卡舊 URL,建議掉線一併 clearStream。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:42:41 +08:00 |
|
|
|
adab000987
|
feat(camera): camera/media 推論改走 tunnel proxy(塊1,取代 501 stub)
推論工作區後端塊1:把 camera/media 9 條路徑從 501 stub 換成走
newProxyHandler 的 tunnel proxy 宣告(比照 devices.go 慣例,新增
camera.go 而非塞 stubs.go)。
- GET /camera/stream → streaming:true(MJPEG multipart 長連線逐 chunk flush)
- 其餘 8 條(camera list/start/stop、media upload×3、batch-images/:index、
seek)→ request-response(upload 大檔的 request body 由 proxy 直接
streaming 送出,streaming flag 只控 response)
- 掛在已套 AuthMiddleware 的 apiGroup 下,沿用剝 Origin 修正
WS inference:<deviceId> 結果推播為塊2(ForwardWebSocket);MJPEG <img>
的 stream 認證分支(R-C4)待 security 定案短期 stream-token 方案,本塊未做。
Reviewer 0C/0M/1Mi/3Sug 通過。+camera_test.go 路由黑箱測試(路徑不再
501、無 session 回 502 TUNNEL_DISCONNECTED),全套 api 回歸綠。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:36:06 +08:00 |
|
|
|
c9f7eab682
|
fix(proxy): tunnel 轉發剝除 Origin header 修「開始推論」403
雲端瀏覽器點「開始推論」→ POST /api/devices/{id}/inference/start,
瀏覽器自動帶的 Origin(stage 網域)經 nginx→api-server proxy→tunnel
透傳到 local agent,被 local agent CORSMiddleware(只允許 loopback
Origin)擋回 403。local agent 的 CORS 是為「本機模式 UI 直連」設計,
不該套用在經 tunnel 中繼的 server-to-server 請求。
修法:api-server 的 copyProxyRequestHeaders 剝除 Origin header(比照
既有 Authorization 過濾、EqualFold case-insensitive),local agent 收到
無 Origin → 走 same-origin 放行路徑。
安全邊界:copyProxyRequestHeaders 全 codebase 只用於 tunnel 轉發
(proxy.go newProxyHandler),不影響 api-server 面向瀏覽器的
CORSMiddleware(另一套 gin-contrib/cors)。Reviewer 0C/0M 通過、
Security APPROVE(剝 Origin 不開 CSRF 面,真正閘門在 api-server
AuthMiddleware + SameSite=Lax cookie)。
+2 test(Origin/Authorization 剝除 + case-insensitive)6/6 PASS。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-09 04:27:49 +08:00 |
|
|
|
c0e2ac0a59
|
fix(devices): 前端讀 tunnel_online 修雲端裝置恆離線
normalizeDevice 原本只讀 remote_status(DB 靜態值、exchange 建 device
寫死 offline 永不更新),沒讀後端即時算出的 tunnel_online → tunnel 已通
但雲端裝置列表恆顯示離線。
改為:tunnel_online === true 時 remoteStatus 覆蓋為 online,否則 fallback
回原本 remote_status 判定,兩者皆缺維持 unknown。嚴格布林比對避免 truthy
誤判。多裝置誤判限制(後端 resolveTunnelStatus 寬鬆比對、正解 R3/Phase 1)
已在 code 註解標明,不在本次前端修法範圍。
新增 4 test(true→online / false→沿用 / 缺欄位→沿用 / 皆缺→unknown),
共 16 test 全綠、tsc/eslint 乾淨。Reviewer 0C/0M。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-04 06:46:44 +08:00 |
|
|
|
dbe5d6a78e
|
docs(architecture): WS tunnel 網路/防火牆需求文件
整理 WS tunnel 要通需要的網路設定(正式環境交接用):
- 核心:邊界反代開 WebSocket upgrade 轉發(或 L4 TCP passthrough),
現在剝 WS upgrade header → tunnel 400
- 配套:idle timeout≥90s、wss TLS、DNS、上線前移除 demo 繞法
- Port 清單:唯一對公網開 :9527,:3800/:3801 絕不對外
- 含 HAProxy/ALB/Cloudflare/F5 等效設定 + 驗證方法 + 回傳碼對照
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-03 03:27:45 +08:00 |
|
|
|
58313169c2
|
feat(stage): nginx 加內網 IP 直連 server block(demo 用)
agent 本機 demo 需直連內網 192.168.0.130:9527 建 tunnel(繞過公網邊界
proxy — 那台剝 WS upgrade header、碰不到)。但直連 IP 時 Host=192.168.0.130
不命中 stage-9527 白名單被 444 擋。
加一個 server_name 192.168.0.130 的 server block(複製 stage-9527 block、
XFP 改 http),8 個 location 全對齊含 /tunnel/connect WS upgrade。現有兩
block(default 白名單 + stage-9527)一字未動。
安全:公網經 proxy 進來 Host 固定 stage-9527、偽造 Host 打不進;IP block
只內網可達;default 444 白名單未削弱。⚠️ demo 專用、正式上線前應移除或
加 allow/deny(註解已標)。
搭配 .env.stage VISIONA_RELAY_PUBLIC_URL 改內網 ws(部署時設、缺一不可)。
Reviewer 通過 0C/0M/0Mi。stage 純 demo 無正式使用者。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 20:59:13 +08:00 |
|
|
|
ab50691f8a
|
fix(conversion): 轉檔完成頁「已加入」按鈕不再重複 promote
SuccessView「加到模型庫」按鈕文字依 importedModelId 切換(已加入/加到模型庫),
但 onClick 永遠是 handleImportClick(開 PromoteDialog)→ 已 promote 後再點又
開 dialog 又 promote。
- 按鈕 onClick 改 handleImportButtonClick 分流:importedModelId 有值→
router.push(/models/{id}) 導航(不再 promote);空→開 PromoteDialog(現狀)
- aria-label 同步切換(import.aria.done)對齊實際行為
- 後端 PromoteToModels 本有冪等(同 jobID 不重複建),前端 onClick 為防禦層
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:30:18 +08:00 |
|
|
|
7d8ad4857e
|
fix(dashboard): 修首頁白屏(React #185 無限 re-render)
connected-devices-list 的 selector 內 .filter() 每次回新陣列,zustand v5
移除淺比較後 Object.is 永 false → 無限 re-render → #185 整頁白屏(配對後
有 device 資料才觸發)。
- selector 改回只取 s.devices(穩定 ref),filter 移到 component body
- activity-timeline 未知 type 加 fallback(?? Circle / muted)防 F7/F8 WS 事件 crash
- 測試 render not.toThrow + act flush 驗不再迴圈
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:38 +08:00 |
|
|
|
6de7c1b4a3
|
feat(devices): 裝置詳細頁加移除(unpair)功能
- device-store 加 unpairDevice action(接 POST /api/devices/:id/unpair)+ unpairingId
- 詳細頁加移除按鈕 + AlertDialog 二次確認(說明解除配對需重新配對、不可逆)
- 成功 toast + 導回 /devices;403/404/unknown 分流 toast、失敗不導頁
- i18n devices.remove.* 雙語
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:37 +08:00 |
|
|
|
0901ffafda
|
feat(models): 模型庫頁按來源分三區(B2)+ 來源標籤 chart token 上色
- /models 改三區渲染:預設模型/我轉檔的/我上傳的(groupModelsBySource)
- 移除 source 篩選器(三區已分來源)、保留 targetChip 篩選跨三區
- 空區顯示標題+空狀態;新 model-section 分區元件
- 來源標籤三色:preset=chart-1/converted=chart-2/uploaded=chart-3
(設計系統 token、badge + 區標題色點呼應、dark mode 自動跟隨)
- i18n models.section.* + .empty.* 雙語
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-07-01 00:17:19 +08:00 |
|
|
|
838d10b084
|
fix(stage): nginx 加 /preset-models/ 反代到 api-server(B8 stage 修正)
preset .nef 公用下載端點 GET /preset-models/{id}.nef 由 api-server :3721
串流。nginx.stage.conf 原本無此 location → 落 catch-all 反代到 frontend
:3000 → 404。
加 /preset-models/ location(在 /storage 後、catch-all 前),比照 /storage
pattern:proxy_buffering off 串流大檔、X-Forwarded-Proto https、安全 header
re-add、cache public max-age=86400(preset 公用內容固定)。nginx -t 通過。
配套(同一交付):Dockerfile.stage preset COPY(1d4977a)+ .env.stage 設
VISIONA_PRESET_BASE_URL(git-ignored 不進)。stage e2e 驗證:
GET /preset-models/kl520-yolov5-detection.nef → 200 + 7506224 bytes(與原檔一致)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:52:11 +08:00 |
|
|
|
1d4977a974
|
fix(stage): Dockerfile.stage 補 preset 模型 COPY(B8 stage 修正)
B8 的 preset COPY 原本只加到 dev 的 Dockerfile.api-server,漏了 stage
實際用的 docker/Dockerfile.stage(multi-stage nginx runtime)→ stage image
無 preset 檔、/preset-models/* 下載 404。
runtime stage 補:
- COPY --from=backend-builder /src/assets/preset-models → /app/assets/preset-models
- ENV VISIONA_PRESET_MODELS_DIR=/app/assets/preset-models(baked 資產、隨 image 走)
docker build --check 通過。Reviewer 複審通過。
注意:VISIONA_PRESET_BASE_URL + nginx /preset-models/ location 由 devops 在
stage 部署時補(Reviewer 指出的兩個 DevOps 配套缺口)。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:46:43 +08:00 |
|
|
|
9ab1a11ea4
|
feat(models): 補 7 個預設模型(B8)+ 詳細頁下載按鈕(B5)
# B8 預設模型(簡單版:寫死常數 + 打包 + 不簽 token)
模型庫「預設模型」原本實際是空的(舊 seed 是 demo 假資料、production 不啟用)。
補 7 個公用預設模型(kl520×4 + kl720×3,來源 local-tool models.json):
- metadata 寫死成 Go 常數(presets.go),不進 DB → 公用、不可刪、無 seed 重複
- 7 個 .nef 打包進 image(assets/preset-models/,~61MB;Dockerfile COPY)
- download 走 visionA 自己(簡單版不簽 token,preset 公用本不需授權):
download handler 三分支 preset→visionA URL / converted→FAA / uploaded→501
- 新 GET /preset-models/*filepath 靜態 serve(無 auth、Content-Disposition
attachment 用 mime.FormatMediaType、path-traversal 防禦)
- list/get 含 preset(對所有人可見)
# B5 詳細頁下載按鈕 + preset 可下載
- model-detail-client.tsx 補下載按鈕(行為對齊列表卡片)
- isModelDownloadable 加 preset(source==="preset" → true)
- normalizeModelSummary 對 target_chip toLowerCase(修 preset 大寫 KL520
在小寫晶片篩選下被隱藏的 bug;收斂所有來源大小寫)
# 測試
backend:presets/preset 下載三分支/靜態 serve/path-traversal/條件對稱 全綠
frontend:詳細頁下載鈕 + preset 可下載 + target_chip 大小寫篩選回歸 38 PASS
Reviewer 兩輪通過(Major-1 + Minor-1/2 修畢、複審 0 問題)
# backlog(未做的優化)
preset 下載改 HMAC presigned / preset .nef 改放 FAA 減 image / metadata 後台可管理
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-28 07:38:25 +08:00 |
|
|
|
3e45532f55
|
fix(model-download): 改 redirect/query-string token 下載,根除 CORS preflight 405
模型庫下載原本前端用 fetch + Authorization: Bearer 跨 origin 直連 FAA,
觸發 CORS preflight(OPTIONS);FAA 未設 CORS、OPTIONS 回 405 → 下載失敗。
FAA 設計本就支援「token 放 query string(access_token) + redirect 導航下載」,
故不需 FAA 設 CORS。改採 ADR-017 §11 v1.3 乙案:
- backend: download_url 組成含 ?access_token={url.QueryEscape(token)} 完整 FAA URL;
ModelDownloadResponse.Token 標 deprecated 保留(向下相容);token 不進 log
- frontend: 移除 downloadModelFile/deriveDownloadFilename(fetch+blob),
改 triggerNavDownload 用 <a download href> 導航;ModelDownloadGrant 移除 token 欄
- 整條鏈無 CORS:前端→backend 同 origin、前端→FAA 導航無 preflight
docs: ADR-017 增補 §11(v1.3) + TDD.md §9.5
tests: backend 8 PASS(含 token escape 邊界)、frontend 35 PASS;Reviewer 通過(0C/0M)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-27 06:18:05 +08:00 |
|
|
|
c2f0b1549e
|
feat: OIDC 登入修復(email fallback / prompt=login / logout 連動)+ 真轉檔鏈路 e2e
接 DB 後真人 OIDC 登入暴露 MC OIDC provider 實作不完整,visionA 端逐項繞過,
讓登入/換帳號可用;另補真轉檔服務的整合 e2e。
OIDC 登入修復(MC 端根因另有交接檔,visionA 先繞過):
- email fallback:MC id_token 不發 email claim(ASP.NET Identity 預設 factory 只發
sub/name)→ A7 email 必填擋住登入。callback email 空時用 <sub>@noemail.visiona.local
placeholder,不污染 schema,MC 修好發真 email 後 ON CONFLICT 自動覆寫
- prompt=login:authorize 帶 prompt=login(config VISIONA_OIDC_PROMPT_LOGIN,預設關)
- logout 連動 MC:logout 回 idp_logout(MC Web :7880 /account/logout,GET),前端用
隱藏 iframe 觸發清 MC session(Web/Api 共享 DataProtection)→ 能換帳號。
config VISIONA_OIDC_LOGOUT_URL、向下相容(未設則只清本地)
真轉檔鏈路 e2e(//go:build realconv,按需對 stage 跑、不污染主測試集):
- real_converter_e2e:give 真轉檔服務 contract(init→poll→completed/promote/result)
- real_chain_e2e:真轉檔→PromoteToModels→model 進 PG→冪等 全鏈路(對 stage 跑 PASS)
交接檔(給對應團隊根治):
- mc-email-claim-handoff:MC 加 email claim(自訂 UserClaimsPrincipalFactory)
- converter-promote-oauth-handoff:轉檔服務 OAuth 用 form body 非 Basic Auth
全程 Reviewer 審查 + 對 stage 真環境驗證。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
2026-06-27 03:18:59 +08:00 |
|