FR-192 · 設計文件 v1.0 · 2026-11-09(D1~D8 定案;13 張開發卡 4 批,收口首腦做)
在 Agent 主控台新增、瀏覽、修改、刪除自己的回饋;與主系統的回饋是同一份資料,兩邊都能改,以較新的為準
本文件把 討論稿 v4 的八項定案落成可派工的設計:Agent 新開 feedback 模組照既有「本機先存、連上再補送」機制長;主系統多一支收單端點把 Agent 來的回饋建成 jedi-issue 的正常回饋並標來源;附件留在 Agent、主系統只記參照;修改與刪除兩邊都能做,Agent 到主系統走同一條待回傳佇列,主系統到 Agent 走既有設定通道回拉,衝突以較新者整筆為準。附件區趁這次抽成共用元件,主控台與主系統回饋表單一起換。原廠回覆(helpdesk)不在本期。
狀態:v1.0 定案、已開卡|日期:2026-11-09|母卡 CM-3781、子卡 CM-3782~CM-3797|前作 FR-099(回饋整組進 jedi-issue)、FR-166(Agent 主控台)、FR-181(長輪詢與設定通道)、FR-166.1(主控台下載能力點) 開發 branch:四個 repo(main、fe、agent、jedi-packages)目前皆在
feature/FR-172;runner 開工git branch --show-current再確認,不對就停下問,不切
🔴 這份是設計決策,不是現況。 上線後的畫面與 API 現況看 SPEC 頁。定稿後若設計有異動,開
design-v1.1.md,本檔不改。
本檔每版一行;設計異動開新版檔,不改舊版。
handoff/cards.py 兩個斷言過、零撞檔。同日開卡:母卡 CM-3781,子卡 CM-3782~CM-3797。enrich;remote-agent migration 號開工時算;Agent 設定類存 JSON 檔不是 kv;所有卡以 FR-190/FR-188 合回後為基底;E-1、P-1 重做。七張卡已 append 修正段。客戶的人多半在 Agent 主控台上操作,碰到問題沒地方講:回饋只能在主系統網頁送,Agent 斷線時連主系統都開不了。主系統的回饋功能已完整(新增、瀏覽、編輯、刪除、我的回饋、檢視全部),整組在 jedi-issue 套件;Agent 主控台什麼都沒有。背景細節、現況數字、既有機制的來龍去脈在討論稿第一節,這裡只放做完之後的樣子。
Agent 主控台多一頁「意見回饋」,掛「作業」群組最下方,登入即可用。按「新增回饋」開對話框,欄位與主系統回饋表單完全相同(標題、類別、功能、內文、檔案上傳),功能固定「Agent 主控台」;儲存後清單立刻一列「待回傳」,連上主系統幾秒內變「已送達」。點任一列開「瀏覽回饋」對話框,可編輯、可刪除。離線一樣能用,連回自動補送。
主系統「系統回饋」與「我的回饋」兩頁多一欄「來源」,Agent 來的顯示「Agent · 名稱」,可篩選。送件人登入主系統在「我的回饋」看得到自己在 Agent 送的那筆,可以改、可以刪,改了 Agent 那邊跟著變。附件存在 Agent,主系統明細可預覽可下載(由主系統向 Agent 取檔)。本機管理員送的,建立人員顯示「本機管理員」,由持有「檢視全部回饋」的人代管。
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
participant U as 使用者(主控台)
participant A as Agent
participant M as 主系統
participant I as jedi-issue
U->>A: 新增回饋(multipart:json+files)
A->>A: 附件 blob_ops.save_upload;feedbacks 落一筆;outbox 入列 op=create
A-->>U: 清單「待回傳」
Note over A,M: 心跳 observer 觸發 outbox.drain
A->>M: POST /agents/feedbacks {items:[…]}(控制面通行證)
M->>M: 驗 created_by;每個 Agent 檔 register_agent_blob()
M->>I: add_feedback(labels=[類別, Agent 主控台], attached_file_uids, source=agent)
I-->>M: feedback uid
M->>M: agent_feedbacks 對照表 (agent_id, local_uid) 唯一
M-->>A: {accepted, inserted, rejected}
A->>A: feedbacks.synced_at、cloud_uid
A-->>U: 清單「已送達」
圖外兩件事:主系統 rejected 時 Agent 把原因寫進該筆、不再重送;同一筆的 create→update→delete 在 outbox 保序,前一筆沒送成後面不送。
七批,每批決策者怎麼檢查:
.worktrees/;建帳號叫 local-admin 被擋。feedback_issues 多兩欄。決策者功課:批 3 與批 4 驗收要在一台真 Agent 上斷線試;Z-1 發版前看一眼四個版號。
八項的定案、理由、被排除方案在討論稿第四節,每項獨立一段,這裡不重抄。只列定案一句與落到哪張卡:
local-admin 不改名、直接當建立人員存、列保留字、由持「檢視全部回饋」者代管 → P-2、I-1(歸屬例外)、M-1(暱稱)、M-3(放行)。open 狀態 → 無卡。直接用的:Agent files/app/blob_ops.save_upload(附件與證據檔同一套);主系統 RemoteAgentAdapter 從 Agent 取檔與簽票下載;jedi-issue SchemaExtensions.response_enricher(空殼掛點第一次有人接);「Agent 名稱雙向以較新者為準」的衝突規則;jedi-issue 既有 issues.state=open。
加參數或加一段的:jedi-issue add_feedback/update_feedback(attached_file_uids、source、source_ref)、feedback_issues 表(兩欄)、_assert_owner(local-admin 例外);主系統 RemoteAgentAdapter._add_reference 抽成公開 register_agent_blob();主系統 _agent_config_sections.py(兩類)與 _agent_config_notify._WATCHED(四張表);Agent sync/app/outbox.py(一種 kind、保序、stale);jedi-iam 建帳號驗證(保留字);主系統回饋兩頁與表單。
新開但照既有形狀:Agent guidant_agent/feedback/ 照 scan/app/local_runs.py 與 console/api/runs_routes.py;主系統 agent_feedback_route.py/agent_feedback_intake_service.py 照 agent_manual_scan_route.py 與 jedi-remote-agent local_run_service.ingest;主控台 Feedback.vue 照 Runs.vue;AttachmentDropList.vue 拖放照 SourceFilePicker.vue。
feedback 模組(A-1、A-3)依賴方向 console → feedback → platform;透過 sync 的 outbox port 入列,不 import sync 的 adapters;SQL 只在 adapters/feedback_repo.py。
表 feedbacks(新版號一步,照 console/migrations.py 寫法,列舉值不下 CHECK):
| 欄 | 型別 | 說明 |
|---|---|---|
uid |
VARCHAR(64) PK | Agent 端編號,送主系統當 local_uid |
title |
VARCHAR(255) | |
description |
TEXT | TipTap 產的 HTML,與主系統同 |
type_label_uid、type_label_name |
VARCHAR(50)、VARCHAR(100) | 名稱一併存,離線拉不到標籤時清單仍顯示 |
created_by |
VARCHAR(128) | session sub;本機管理員為 local-admin |
created_at、updated_at、deleted_at |
DATETIME | 軟刪 |
files |
TEXT JSON | [{uid, name, size, mimetype, sha256, where: agent|cloud}] |
cloud_uid、cloud_updated_at |
VARCHAR(50)、DATETIME | 主系統回饋編號與主系統端更新時間(回拉覆寫的比較基準) |
synced_at、sync_error |
DATETIME、TEXT | 同 local_runs |
app 層:create/update/delete/remove_file(uid, …, actor) 寫表並 outbox.enqueue(KIND_FEEDBACK, uid, op, payload);list_mine(sub)、get(uid, sub) 非本人一律 404;record_sync(conn, uids, error) 簽名照 local_runs;apply_cloud(items) 是設定類 feedbacks 的套用者,cloud_updated_at 較新者覆寫本機、deleted 標 deleted_at,被覆寫記事件碼。
outbox:KIND_FEEDBACK 進 _NEVER_DROP、_ORDERED、SENDABLE;_post_feedbacks 照 _post_local_runs 批次送 FEEDBACKS_PATH;回應 accepted settle、rejected 寫 sync_error、stale 寫「主系統已有較新版本」並 config_pull.request()。
主控台 API(掛 console blueprint,登入即可、不綁 scope,body 上限沿用主控台既有限制)八條:清單只回本人的並附同步狀態;單筆非本人一律 404;新增與修改收 multipart(一個 JSON 欄位帶標題、內文、類別編號,加多個檔案欄位),修改另收要拿掉的附件編號清單;刪除是軟刪並入列;刪單一附件會同時刪本機 blob 並入列一筆修改;立即重試照執行紀錄頁那支,離線回 409;類別選項讀設定類快取。路徑一律 /console/api/feedbacks…,與主系統 /feedback 端點同形狀,runner 開工照 console/api/runs_routes.py 的寫法。
tasks.py:FEEDBACKS_PATH = "/api/1.0/agents/feedbacks"、FEEDBACK_OPS = ("create","update","delete")、FEEDBACK_ITEM_KEYS、FEEDBACK_FILE_KEYS、FEEDBACKS_MAX_BATCH = 50。config.py:KIND_FEEDBACK_LABELS、KIND_FEEDBACKS。每筆 payload:
{"local_uid": "…", "op": "create|update|delete",
"title": "…", "description": "<p>…</p>", "type_label_uid": "…",
"created_by": "wang.auditor", "created_at": "…", "updated_at": "…",
"base_cloud_updated_at": "…(update 時帶;主系統比它新回 stale)",
"files": [{"agent_file_uid": "…", "file_name": "…", "size": 0, "mimetype": "…", "sha256": "…"}],
"remove_file_uids": ["…(update 時要拿掉的主系統附件 uid)"]}feedback_issues 加 source VARCHAR(20) NOT NULL DEFAULT 'web'、source_ref VARCHAR(50),索引 (source, source_ref)。add_feedback/update_feedback 加 attached_file_uids,有值時不走 upload_attachment、直接 add_issue_file_mapping;建回饋另收 source/source_ref。_assert_owner 加例外:source == "agent" and created_user == "local-admin" 且呼叫者持 feedback_read_all_capability → 視為擁有者。新 list_by_source(source, source_ref)。FeedbackResponse 吐 source/source_ref;FeedbackQueryRequest 收 source。清單與單筆回應改先過 schema_extensions.enrich()——這個掛點是空殼、從沒被呼叫,不接的話主系統的 enricher 不會生效。刪除語意不改(硬刪),「已不存在」由主系統對照表告訴 Agent。外部整合(GitLab/GitHub)附件傳 None。
jedi-iam:保留字檢查抽成一支,建帳號、改帳號名、LDAP/AD 同步建帳號、租戶佈建四個寫入點都呼叫;只擋建帳號不夠。另附唯讀查詢確認既有帳號無撞名(DEV 開卡時 0 筆)。
收單端點 POST /agents/feedbacks,@agent_pass_required,body {items:[…]} 1~50。收單服務在 agent_tenant_context 內逐筆:
created_by(body 帶來的,不可信):只准 local-admin,或這台 Agent 有效離線操作者名單內的 login_name(名單由主系統下發給這台,等於用通行證錨定「誰能在這台主控台登入」);同租戶但未被授權這台的人也拒收。不用「是這租戶的啟用使用者」當判準,那會讓一台 Agent 替任何同租戶的人送件。op:目標一律由對照表以 (agent_id, local_uid) 反查,不收 body 的回饋編號,查不到 rejected not_found;create 撞唯一鍵即 accepted 不重建;update 比 base_cloud_updated_at 與主系統 updated_at,主系統較新回 stale;delete 走 delete_feedback,身分就是送件人(本機管理員那筆建立人員本來就是 local-admin,同名身分過既有歸屬檢查,不另開繞過);成功後對照表寫 deleted_at。files 每項 register_agent_blob() 建 upload_files 參照(storage_type=remote_agent、save_file_name=agent_file_uid、path=agent.base_url、sha256 用 Agent 報的、created_user=created_by、storage_scope=customer);remove_file_uids 走既有刪附件。labels=[type_label_uid, AGENT_CONSOLE_FUNCTION_LABEL_UID]、source="agent"、source_ref=agent.uid;對照表 agent_feedbacks 寫一列。對照表 compliance.agent_feedbacks(id, uid, tenant_id, org_unit_id, agent_id, local_uid, feedback_uid, deleted_at, created_at, updated_at),(agent_id, local_uid) 唯一,RLS 與 GRANT 照 agent_local_runs;jedi-remote-agent migration 號=開工時套件目錄最大號加一(FR-188 合回後至少 022),主專案 scripts/sql/packages/jedi_remote_agent/ 鏡像同號。
來源欄顯示:core/plugins/issue.py 傳 SchemaExtensions(response_enricher=…);enricher 對 source == "agent" 的列補 source_name(remote_agents.name),對 created_user == "local-admin" 補 created_user_name="本機管理員"。
設定通道:feedback_labels provider 回 FEEDBACK_TYPE 標籤 [{uid, name}];feedbacks provider 回該 Agent list_by_source 的結果加對照表 deleted 列,每項 {local_uid, feedback_uid, title, description, type_label_uid, files:[{uid,name,size,where}], updated_at, deleted};版本皆內容雜湊。_agent_config_notify._WATCHED 加 feedback_issues/issue_upload_files/issue_label_mapping → KIND_FEEDBACKS(只通知 source_ref 那一台,照 remote_agents.name 單台通知形狀,不可全租戶)、labels → KIND_FEEDBACK_LABELS。
seed:主線 phase=seed migration 補 labels(scope=FUNCTION, name='Agent 主控台'),uid 固定寫在 common/code/feedback_source.py。
AttachmentDropList.vue(src/components/common/):props existing[]、pending[]、accept、maxSize、canDownload、canDelete、readonly;emits add、remove-pending、remove-existing、preview、download。一個拖放區,兩類檔同清單,每列 hover 出動作,where=cloud 顯示「存於主系統」。套全站 dark-card 與字級,不自訂。
主控台 Feedback.vue:表格與回傳狀態照 Runs.vue;新增/編輯同一個對話框,欄位照 SystemFeedbackForm.vue,內文用 @/views/project-summary-report/TipTapEditor.vue;瀏覽對話框底部「刪除」「關閉」「編輯」,回傳失敗頂部原因與「立即重試」。文案依 i18n 既有用語重寫,不從雛形複製;驗收 grep -rn "本案新增\|審稿\|D[0-9]\|場景" 為 0。
主系統兩頁加「來源」欄與 SegmentButtons(全部/網頁/Agent);表單來源為 agent 時頂部 hint-bar--info 一列。
收單端點只認通行證,body 身分欄位一律不採信;created_by 必須是該租戶的人;附件 SHA-256 記下不當驗過,下載時既有完整性檢查重算;標題 150、內文上限沿用主系統,超長拒收;Agent 端走主控台 session 與 CIDR,附件解析沿用 files 模組檔頭檢查與大小上限。回拉只通知該 Agent,不會把別台的回饋內容送出去。
批(Stage)=工作坊同時開幾個小弟一起跑的那一組:同一批不改同一個既有檔、不依賴對方產出。底表在 handoff/cards.py,兩個斷言已過、零撞檔。批 1=E-1、P-1、P-2;批 2=A-1、I-1、F-1;批 3=M-1、M-3、A-2、M-2;批 4=A-3、M-4、A-4;批 5=V-1;批 6=Z-1;批 7=Z-2。
登記型檔只讓一張卡碰:scripts/sql/manifest.tsv 由 M-3 一次登記 014 鏡像與 M-1 的 seed(M-1 不碰);sync/app/outbox.py 由 A-1 加 kind、A-3 只加 stale 分支(不同批);console router.js 只有 A-2 碰;i18n 各卡各自新開檔。
套件版號:jedi-agent-protocol 0.6.0(P-1)、jedi-iam patch(P-2)、jedi-issue 1.6.0(I-1)、jedi-remote-agent 新 migration 014 隨 M-3 bump minor;Z-1 統一發版。
| 批 | 階段 | 卡 | repo | reuse/extend/new+理由 | 改哪些既有檔 | 新開哪些檔 | 依賴 | 驗收 | model/effort:理由 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 前置 | E-1 環境卡:主系統開發期改走四套件本機路徑(jedi-agent-protocol/jedi-issue/jedi-remote-agent/jedi-iam);FR-190/FR-188 合回後重做,BE 工作區共用 .venv 要先處理 | BE | reuse:既有 poetry path dependency 形式(pyproject.toml 第 132~135 行註解掉的那組);指向各套件工作坊工作區 .worktrees/<母卡號>/<套件> |
pyproject.toml(不 commit,git update-index --assume-unchanged) |
(無) | — | python main.py 起得來;python -c 'import jedi_issue,jedi_agent_protocol,jedi_remote_agent,jedi_iam;print(*(m.__file__ for m in (jedi_issue,jedi_agent_protocol,jedi_remote_agent,jedi_iam)),sep=chr(10))' 四行都在 .worktrees/ 底下;git status 不見 pyproject.toml |
sonnet/medium:機械性:改四行、poetry update 指定套件名、驗 import 路徑 |
| 1 | 前置 | P-1 jedi-agent-protocol:回饋端點路徑、動作列舉、鍵名清單、兩個設定類;0.6.0 → 0.7.0(0.6.0 已被 FR-190 發版用掉) | jedi 套件 | extend:照 tasks.py 既有 LOCAL_RUNS_PATH 與 config.py 既有 KIND_* 常數形狀加項,不另開檔 |
jedi-agent-protocol/jedi_agent_protocol/tasks.py(FEEDBACKS_PATH、FEEDBACK_OPS、FEEDBACK_ITEM_KEYS、FEEDBACK_FILE_KEYS、FEEDBACKS_MAX_BATCH)、jedi-agent-protocol/jedi_agent_protocol/config.py(KIND_FEEDBACK_LABELS、KIND_FEEDBACKS 進 ALL_KINDS)、jedi-agent-protocol/jedi_agent_protocol/__init__.py(export)、jedi-agent-protocol/pyproject.toml |
(無) | — | 以 FR-190 合回後的 0.6.0(含 console_scopes)為底;套件 tests 綠;python -c 'import jedi_agent_protocol as p; assert p.FEEDBACKS_PATH and p.CONSOLE_SCOPES' 過;tests/test_mirror.py 三邊綠;版號 0.7.0 |
sonnet/medium:純契約常數;一次定完兩端都照它寫 |
| 1 | 前置 | P-2 jedi-iam:保留字 local-admin 在每一條寫入帳號名的路徑都擋(建、改名、匯入、批次匯入、LDAP/AD 同步、佈建);1.11.2 → 1.11.3 |
jedi 套件 | extend:保留字檢查抽成 domain 一支 assert_login_name_not_reserved(),建帳號、改帳號名、LDAP/AD 同步建帳號、租戶佈建四個寫入點都呼叫它(不是只在 verify_login_name_duplicate 旁加一行);錯誤碼走既有 login_name 不合法那顆 |
jedi-iam/jedi_iam/app/service/user_service.py 158(建)、273~323(update_user)、jedi-iam/jedi_iam/app/service/import_user_service.py 172、jedi-iam/jedi_iam/app/service/user_batch_import_service.py、jedi-iam/jedi_iam/app/service/tenant_provisioning_service.py、jedi-iam LDAP/AD 同步建帳號處(FR-188 合回後再 grep 一次)、jedi-iam/pyproject.toml(1.11.2 → 1.11.3,在 FR-188 之上) |
jedi-iam/jedi_iam/domain/.../reserved_login_names.py(集合+檢查函式) |
— | 套件 tests:建、改名、LDAP 同步、佈建四條路徑給 local-admin(含大小寫變體)都拋既有不合法錯誤;其他名字不受影響;突變(把檢查拿掉任一路徑要紅);回寫附 DEV 唯讀查詢 select count(*) from public.users where lower(login_name)='local-admin'(開卡時為 0) |
sonnet/low:四個寫入點一起擋才算擋;漏一條就是資安審查指的碰撞洞 |
| 2 | 看得見的 | A-1 Agent 新模組 feedback:表、新增/清單/單筆、主控台 API、outbox 類型與保序、類別選項套用者 | evidence-agent | new(新模組,照型):表欄位與 app 函式簽名照 scan/app/local_runs.py(_insert/record_sync/list_page/get);console API 照 console/api/runs_routes.py(清單、404 不分查無與非本人、resend);附件 reuse files/app/blob_ops.save_upload;extend:outbox 加 KIND_FEEDBACK 進 _NEVER_DROP/_ORDERED/SENDABLE,批次送 FEEDBACKS_PATH 照 _post_local_runs |
guidant_agent/sync/ports/result_outbox.py(KIND_FEEDBACK)、guidant_agent/sync/app/outbox.py(三個集合+_post_feedbacks)、guidant_agent/platform/registry.py(MODULES 加 _module("feedback", migrations=True, config=True);模組 init.py申報config_appliers() 照 identity)、guidant_agent/shared/event_codes.py(AGENT_FEEDBACK_CREATED/SYNCED/SYNC_FAILED)、common/code/(新 ConsoleFeedbackErrorCode 檔) |
guidant_agent/feedback/__init__.py、guidant_agent/feedback/migrations.py(新版號:feedbacks 表)、guidant_agent/feedback/app/feedbacks.py(create/list_mine/get/record_sync)、guidant_agent/feedback/app/labels.py(KIND_FEEDBACK_LABELS 套用者,存 data_dir JSON 檔照 identity/app/offline_login_window.py;Agent 沒有 kv 表)、guidant_agent/feedback/adapters/feedback_repo.py(SQL 只在這)、guidant_agent/feedback/api/feedback_routes.py(GET list/GET one/POST/POST resend/GET labels)、test/feedback/test_feedbacks.py |
P-1 | 守衛六支綠(含 test_security_rules_cover_sources.py 新檔都被命中);主控台 POST /console/api/feedbacks multipart 建一筆,GET /console/api/feedbacks 只回本人的,換另一個 session 打單筆 404;outbox 表多一列 kind=feedback;同一 uid 入列 create 再 update,drain 時 create 沒送成 update 不送(test);離線時 resend 回 409;設定類 feedback_labels 套用後 GET /console/api/feedback-labels 回三個 |
opus/high:基底=FR-190(console 權限名)與 FR-188(outbox 立即送)合回後;新模組要過六支架構守衛(依賴方向、SQL 只在 adapters、背景元件、security rules paths);outbox 保序改動碰核心 |
| 2 | 看得見的 | I-1 jedi-issue:回饋表加來源兩欄、建改回饋收既有附件參照、歸屬例外、來源查詢、清單吐來源與篩選;1.5.0 → 1.6.0 | jedi 套件 | extend:FeedbackService.add_feedback/update_feedback 加 attached_file_uids/source/source_ref(有參照時走 IssueUploadFilesRepoImpl.add_issue_file_mapping 不走 upload_attachment);_assert_owner 加一條例外;entity/DTO/serializer 各加兩欄;query entity 加 source 篩選;新 list_by_source |
jedi-issue/jedi_issue/app/feedback/service/feedback_service.py 175(_assert_owner)、243(add)、294(update)、jedi-issue/jedi_issue/app/feedback/service/feedback_issue_service.py(create_issue 傳參照)、jedi-issue/jedi_issue/app/feedback/dto/feedback_issue_dto.py、jedi-issue/jedi_issue/domain/feedback/entities/feedback_issue_entity.py、jedi-issue/jedi_issue/domain/feedback/entities/feedback_issue_query_entity.py、jedi-issue/jedi_issue/domain/feedback/service/feedback_issue_domain_service.py、jedi-issue/jedi_issue/infra/feedback/models/feedback_issue.py、jedi-issue/jedi_issue/infra/feedback/repository/(list_by_source)、jedi-issue/jedi_issue/api/serializers/feedback.py(source/source_ref 進 FeedbackResponse;FeedbackQueryRequest 加 source)、jedi-issue/jedi_issue/api/routes/feedback_route.py 65、76(清單與單筆回應先過 rt.schema_extensions.enrich()——掛點是空殼從沒人呼叫,不接 M-1 整張白做;post/put 簽名不動)、jedi-issue/pyproject.toml |
jedi-issue/jedi_issue/migrations/006-feedback-issues-source.sql |
— | 套件 tests 綠;新 tests:add_feedback(attached_file_uids=[x]) 不呼叫 upload 直接掛 mapping;source 預設 web;_assert_owner 對 source=agent, created_user=local-admin 持 read_all 者放行、不持者 403(突變:把例外拿掉要紅);list_by_source('agent', uid) 只回該 Agent 的;DEV 套 006 後 \d public.feedback_issues 有 source/source_ref 與索引;既有回饋 source 全為 web |
opus/high:套件公開 API 加參數、表加欄、歸屬檢查改動;要保既有 consumer 零影響 |
| 2 | 看得見的 | F-1 FE 共用附件元件 AttachmentDropList:一個拖放區、既有檔與待上傳檔同清單、hover 動作、存放位置標籤;先換進主系統回饋表單 | FE 主站/console | new(照型):拖放與 accept 照 src/views/source-scan/SourceFilePicker.vue(ss-drop、onDrop、useAcceptedUploadTypes),多檔清單與既有檔列是新寫;extend:SystemFeedbackForm.vue 附件區整段換成這個元件,FileUpload 兩區四樣式刪掉 |
src/views/feedback/system/SystemFeedbackForm.vue 302~420(附件區)、src/config/locales/i18n/zh-tw/feedback.json、en/feedback.json(附件區文案改寫;舊鍵 drag_drop_hint/ready_to_upload/uploaded_files 若無他處用則刪) |
src/components/common/AttachmentDropList.vue(props:existing[]、pending[]、accept、maxSize、canDownload、canDelete、readonly;emits:add、remove-pending、remove-existing、preview、download) |
— | npm run build 過;主系統新增回饋:空的只有一行提示、拖兩個檔進區內兩列、hover 出移除、超 20 MB 顯示既有錯誤訊息;編輯既有回饋:既有檔與新加檔同一區、既有檔 hover 出預覽/下載/刪除且 v-can 照舊;grep -n 'border-left|font-size|max-width' src/components/common/AttachmentDropList.vue 為 0;文案全部來自 i18n 檔,回寫列出新增與沿用的鍵 |
opus/medium:元件 API 要讓主站與主控台都能用;版面照雛形結構不抄樣式;套全站 dark-card 與字級 |
| 3 | 接起來 | M-1 主系統來源欄顯示:接 jedi-issue 回應擴充掛點補 Agent 名與「本機管理員」暱稱;功能標籤「Agent 主控台」seed | BE | reuse:jedi-issue SchemaExtensions.response_enricher(空殼掛點第一次有人接);extend:core/plugins/issue.py mount() 傳 schema_extensions;seed 照 2026-05-22-seed-oscal-ssp-roles.sql 形狀 ON CONFLICT DO NOTHING |
core/plugins/issue.py 184(mount 加 schema_extensions) |
core/plugins/_issue_source_enrich.py(source=agent 補 source_name=remote_agents.name;created_user=local-admin 且 source=agent 補 created_user_name=本機管理員)、scripts/sql/2026-11-XX-fr192-seed-agent-console-function-label.sql(labels:scope=FUNCTION, name=Agent 主控台,uid 固定)、common/code/feedback_source.py(AGENT_CONSOLE_FUNCTION_LABEL_UID、SOURCE_WEB/AGENT、LOCAL_ADMIN_LOGIN_NAME) |
I-1 | DEV 套 seed 後 select uid,name from labels where scope='FUNCTION' and name='Agent 主控台' 一列且 uid 等於常數;手動 insert 一筆 source=agent 的回饋後 POST /feedbacks 回應該列有 source_name;created_user=local-admin 那列 created_user_name 為「本機管理員」;既有 web 回饋回應不變;BE 重啟實打;回寫明講「出貨基線待重產」 |
sonnet/medium:接線與 seed,形狀都有先例。🔴 登記型共用檔 manifest.tsv 由同批 M-3 一次登記兩支(本卡 seed+M-3 的 014 鏡像),本卡只寫 seed 檔、不碰 manifest |
| 3 | 接起來 | M-3 主系統收單端點 POST /agents/feedbacks:新增/修改/刪除、過期判定、Agent 附件建參照、對照表、類別選項設定類 | BE | new(照型):route 照 api/source_scan/routes/agent_manual_scan_route.py、service 照 jedi-remote-agent local_run_service.ingest(通行證身分、批次、accepted/rejected);extend:RemoteAgentAdapter._add_reference 抽成公開 register_agent_blob(),收單與既有上傳共用;設定類 provider 照 _agent_config_sections.py 既有 provider 形狀;對照表 migration 照 011-remote-agent-offline-operators.sql 的 RLS/GRANT |
infra/upload_file/remote_agent_adapter.py 158(_add_reference → register_agent_blob 公開)、core/plugins/_agent_config_sections.py(feedback_labels provider)、core/plugins/remote_agent.py(route 登記一行)、di_containers/remote_agent_container.py(intake service wiring)、common/enum/event_code.py(AGENT_FEEDBACK_RECEIVED)、scripts/sql/manifest.tsv(登記兩支:本卡 014 鏡像+M-1 的 seed,M-1 不碰此檔)、scripts/sql/packages/jedi_remote_agent/(鏡像同號 0NN) |
api/remote_agent/routes/agent_feedback_route.py、app/remote_agent/service/agent_feedback_intake_service.py、common/code/agent_feedback_error_code.py、jedi-remote-agent/jedi_remote_agent/migrations/0NN-remote-agent-feedbacks.sql(NN=開工時套件目錄最大號+1,FR-188 合回後至少 022;014 早被占用)、test/test_agent_feedback_intake.py |
A-1、I-1 | DEV 套 0NN 後 \d compliance.agent_feedbacks 有 RLS 四指令與 (agent_id, local_uid) 唯一;用 123 Agent 的通行證打 50 筆 create 回 accepted 50、第 51 筆 400;重送同 local_uid 回 accepted 不重建;created_by 不在這台 Agent 的有效離線操作者名單(同租戶但沒被授權這台的人也算)回 rejected unknown_user;local-admin 放行且 feedback_issues.created_user=local-admin;update/delete 帶別台 Agent 或不存在的 local_uid 回 rejected not_found(不收 body 的 feedback_uid);帶 files 的那筆 upload_files 多一列 storage_type=remote_agent、save_file_name=Agent 檔 uid、path=該 Agent base_url;update 帶舊的 base_cloud_updated_at 回 stale;delete 後 feedback 不在、對照表 deleted_at 有值;GET /agents/config 回 feedback_labels 三個;BE 重啟實打;守衛測試綠;回寫明講「出貨基線待重產」 |
opus/high:橋接兩個套件、身分與租戶邊界、附件參照、對照表唯一鍵;資安規則 endpoints/data 兩支都命中 |
| 3 | 接起來 | A-2 主控台前端 Feedback.vue:清單、新增回饋對話框、瀏覽回饋對話框、路由與 i18n;用 F-1 元件與主站 TipTapEditor | FE 主站/console | new(照型):表格與回傳狀態欄照 console/views/Runs.vue(狀態字沿用 agent_console_runs 的 sync 鍵);新增表單欄位照 src/views/feedback/system/SystemFeedbackForm.vue(標題整行、類別+功能並排、內文 @/views/project-summary-report/TipTapEditor.vue、檔案上傳 @/components/common/AttachmentDropList.vue);對話框照 console/views/Logs.vue 的 Dialog;extend:router.js 加一項 |
console/router.js(feedback 項:group work、scope null)、console/main.js(若 TipTap 需全域註冊才加) |
console/views/Feedback.vue、console/feedbackApi.js、console/feedback.js(狀態對照、錯誤鍵)、src/config/locales/i18n/zh-tw/agent-console-feedback.json、en/agent-console-feedback.json |
F-1、A-1 | npm run build:CONSOLE 過;側欄「作業」群組最下多「意見回饋」;按「新增回饋」開對話框,欄位與主系統表單同、功能反灰「Agent 主控台」、沒拉到類別時新增鈕反灰;儲存後清單立刻一列「待回傳」;點列開「瀏覽回饋」對話框,回傳失敗的頂部有原因與立即重試;grep -rn '本案新增|審稿|D[0-9]|場景' console/views/Feedback.vue src/config/locales/i18n/*/agent-console-feedback.json 為 0;grep border-left console/ 為 0;回寫列出文案來源(沿用哪些鍵、新增哪些鍵) |
opus/medium:畫面照雛形結構;文案全部重寫;要同時接主站兩個元件進 console build |
| 3 | 接起來 | M-2 主系統前端:系統回饋與我的回饋兩頁加「來源」欄與分段篩選;編輯回饋表單來源提示列 | FE 主站/console | extend:兩頁 <Column> 各加一欄、工具列加 SegmentButtons(全部/網頁/Agent,照 feedback_list_page_filter_baseline_my_tasks 規則)、filters 加 source;SystemFeedbackForm.vue 讀 source 顯示 hint-bar--info 一列 |
src/views/feedback/system/SystemFeedbackManage.vue 249~318(欄)、toolbar、src/views/feedback/system/SystemFeedbackView.vue、src/views/feedback/system/SystemFeedbackForm.vue(頂部提示列;附件區 F-1 已換)、src/config/locales/i18n/zh-tw/feedback.json、en/feedback.json(source_web/source_agent/source_hint) |
(無) | F-1、I-1 | npm run build 過;兩頁多「來源」欄,web 顯示「網頁」、agent 顯示「Agent · 名稱」;分段鈕切 Agent 只剩 Agent 來的;編輯 Agent 來的那筆頂部一列提示含 Agent 名與送出時間;grep -n 'border-left' src/views/feedback/system/*.vue 為 0 |
sonnet/medium:既有頁加欄加篩選 |
| 4 | 改刪與回拉 | A-3 Agent feedback 模組:修改、刪除、刪單一附件、回饋現況設定類套用者(較新者覆寫、已刪標記、stale 處理) | evidence-agent | extend:feedbacks.py 加 update/delete/remove_file/apply_cloud;feedback_routes.py 加 PUT/DELETE 兩條與刪附件;outbox 的 stale 回應處理(標 sync_error 並 config_pull.request());套用者照 labels.py 形狀 |
guidant_agent/feedback/app/feedbacks.py、guidant_agent/feedback/api/feedback_routes.py、guidant_agent/feedback/adapters/feedback_repo.py、guidant_agent/sync/app/outbox.py(stale 分支)、guidant_agent/shared/event_codes.py(UPDATED/DELETED/OVERWRITTEN_BY_CLOUD)、test/feedback/test_feedbacks.py |
guidant_agent/feedback/app/cloud_state.py(KIND_FEEDBACKS 套用者) |
A-1、M-3 | 主控台 PUT 改標題 → outbox 一列 op=update;DELETE → op=delete、清單不列;收到設定類 feedbacks 內某筆 cloud_updated_at 比本機新 → 本機覆寫、稽核 OVERWRITTEN_BY_CLOUD;比本機舊 → 不動;deleted=true → 本機標 deleted_at;主系統回 stale → 該筆 sync_error 為「主系統已有較新版本」且觸發一次 config_pull;守衛六支綠 | opus/high:雙向覆寫邏輯與保序;要突變測試(把較新者判斷拿掉要紅) |
| 4 | 改刪與回拉 | M-4 主系統回拉:回饋現況設定類 provider、四張表改動監看、對照表刪除時間回寫 | BE | extend:_agent_config_sections.py 加 feedbacks provider(用 jedi-issue list_by_source +對照表 deleted 列);_agent_config_notify._WATCHED 加 feedback_issues/issue_upload_files/issue_label_mapping → KIND_FEEDBACKS、labels → KIND_FEEDBACK_LABELS(只通知該 source_ref 那台,照 remote_agents name 的單台通知形狀);主系統端刪回饋時對照表寫 deleted_at(掛 jedi-issue delete 後,走 ORM after_delete 事件或 intake service 共用函式) |
core/plugins/_agent_config_sections.py、core/plugins/_agent_config_notify.py 27(_WATCHED)、app/remote_agent/service/agent_feedback_intake_service.py(刪除路徑共用的 mark_deleted)、test/test_agent_feedback_intake.py |
core/plugins/_agent_feedback_state.py(provider 本體,照 _agent_vulndb.py 形狀) |
M-3、I-1 | 主系統改 Agent 來的回饋標題 → agent_requests 多一列 config_changed 只對那台 → Agent 拉 GET /agents/config?versions=feedbacks:舊 回新內容;改 web 來的回饋不通知任何 Agent;主系統刪該筆 → provider 回該 local_uid deleted=true;改 labels → 通知 feedback_labels;BE 重啟實打 |
opus/medium:表改動監看要只通知那一台(不是全租戶);provider 內容雜湊版本 |
| 4 | 改刪與回拉 | A-4 主控台前端:瀏覽回饋對話框加編輯/刪除、存於主系統附件標示、被主系統覆寫與 stale 提示、最後更新列 | FE 主站/console | extend:Feedback.vue 瀏覽對話框底部加兩鈕(編輯開同一個表單對話框帶現值;刪除走既有 ConfirmDialog 形狀);附件列 where=cloud 顯示「存於主系統」標籤且無預覽;sync_error 為 stale 時頂部提示 |
console/views/Feedback.vue、console/feedbackApi.js(PUT/DELETE/刪附件)、console/feedback.js、src/config/locales/i18n/zh-tw/agent-console-feedback.json、en/agent-console-feedback.json |
(無) | A-2 | npm run build:CONSOLE 過;瀏覽對話框有「編輯」「刪除」,編輯帶入現值、儲存後清單那筆變「待回傳」;刪除確認後清單消失;存於主系統的附件標籤正確且無預覽鈕;stale 那筆頂部有提示;grep border-left console/ 為 0 |
sonnet/medium:既有頁加兩鈕與兩種狀態顯示 |
決策者 2026-11-11 裁:驗收、套件發版、主專案改回釘版不開卡、不進工作坊,因為三件都要先把四個 repo 合回主線才能跑,排在尾批一定卡。批 4 最後一張開發卡驗收通過、工作坊 finish 合回之後,由首腦依序做:
feature/FR-172(工作坊 finish 自動做,衝突由首腦解)。docs/claude/jedi-packages.md 末段推 Nexus;publish 不可逆。poetry update 指定套件名、起 BE 實打一筆回饋;Agent pyproject.toml 釘 protocol 0.7.0。端到端十二步(真 Agent 123 或 190,每步附截圖或指令輸出):
agent_feedbacks.deleted_at 有值。local-admin 被擋。基底是 FR-190 與 FR-188 合回之後:FR-190 發了 protocol 0.6.0 與 remote-agent 1.18.0、改了主控台權限名與 require_session;FR-188 改了 Agent outbox、主系統 remote_agent_adapter.py、jedi-iam(1.11.2)、remote-agent(1.18.1)、protocol(0.5.5,要對齊)。本案每張卡開工先 git merge feature/FR-172 跟上,版號在它們之上 bump:protocol 0.7.0、iam 1.11.3、remote-agent 1.18.2 以上。E-1 與 P-1 第一輪在舊基底做,要重做。
BE 工作區共用一份 .venv:E-1 改 path 會把 FR-190 與決策者自己的 BE 一起切到 FR-192 的半成品套件,FR-190 收尾又切回來。要嘛 FR-192 工作區建獨立 .venv,要嘛等前兩案完全收口再開(決策者裁)。
local-admin 不改名:主系統端兩處拿這個字串撈歷史稽核,Agent 端 SQLite 以它當主鍵;任何想「順手改名」的念頭停下。保留字要擋在每一條寫帳號名的路徑(建、改名、LDAP 同步、佈建),P-2 開工先 grep 寫入點。
送件人判準是「這台的離線操作者」不是「這租戶的使用者」:資安審查點名;用租戶判準等於讓 Agent 替任何同事送件。
回拉只通知那一台:_WATCHED 加表時若照預設通知全租戶,每次有人改任何回饋所有 Agent 都來拉,且 provider 會算全租戶內容。照 remote_agents.name 的單台路徑。
outbox 保序與既有 _ORDERED:KIND_EVENT/KIND_REPLY 的保序是「各 kind 各排各的」,feedback 要的是「同一 uid 內保序」,實作時確認 _ordered_heads_waiting 的 key 是否要加 uid。
TipTap 進 console build:主站元件帶 @tiptap/* 相依,console 包體積會變大;A-2 回寫 build 體積前後。
manifest.tsv 只讓 M-3 碰:M-1 的 seed 檔寫好但不登記,M-3 登記時要把兩支都列;V-1 驗 schema_migrations 兩支都在。
衝突規則是整筆覆蓋:使用者在 Agent 改了附件、主系統同時改了標題,較新者整筆蓋掉另一邊的改動;本期接受,文件與提示要講清楚。
主系統補上傳的附件 Agent 不可預覽:檔在租戶儲存,Agent 拿不到;清單標「存於主系統」即可,不要嘗試從主系統拉檔到 Agent。
設計決策文件,v1.0 凍結。實作期間設計異動開 design-v1.1.md;上線後畫面與 API 現況以 SPEC 頁為準;每棒做了什麼、推翻了什麼進 handoff/fr192-LOG.md 與 commit message。