# Continuity Plugin:Generic / Manual Integration Runbook > 目標讀者:**沒有 `force-recall` hook 的宿主 / orchestrator / planner 實作者** > > 這份文件專門說明:當你的 host 沒有 `hooks/force-recall/handler.ts` 時,如何仍然安裝 continuity plugin、提供 payload、執行 manual/generic preflight,並驗證 continuity gate block 真的有生效。 > 英文版:`GENERIC_INTEGRATION.md` --- ## 0. 這份 runbook 解決什麼問題 如果你的宿主屬於以下任一情境,請優先看這份: - 你沒有 `force-recall` hook。 - 你有自己的 planner / preflight / orchestrator。 - 你想手動把 approved-plan 狀態餵給 continuity plugin。 - 你需要一份「最少要給哪些欄位」的 payload 規格。 - 你需要知道如何驗證 continuity block 有真的擋住錯誤結案,而不是只是在文件裡說可以。 一句話總結: **沒有 `force-recall` 也能裝 continuity plugin,但你必須自己提供 generic continuity input,並把回傳的 block 接進 agent prompt。** --- ## 1. 最小安裝目標 安裝完成後,至少要達到以下結果: - 你的 host 能 import `plugins/continuity/src/index.mjs` - 你的 host 能呼叫: - `runGenericPreflightContinuityAdapter(...)`,或 - `runManualContinuityPreflight(...)` - 你的 host 能把回傳的 `out.block` prepend 到 agent 會看到的 prompt/body - 當任務完成、但還有下一步且沒有真實 dispatch receipt 時,continuity gate 會擋住「直接結案」 - `cd plugins/continuity && npm test` 可通過 --- ## 2. 安裝位置與檔案 建議放在: ```text /plugins/continuity ``` generic/manual 路徑至少需要這些檔案: ```text plugins/continuity/ package.json src/index.mjs src/adapters/generic-preflight.mjs src/config/defaults.mjs src/config/schema.mjs src/continuity/engine.mjs src/continuity/evaluator.mjs src/continuity/receipt-validator.mjs src/continuity/receipt-store.mjs src/continuity/types.md examples/openclaw.continuity.example.json examples/approved-plan-receipt.example.json README.zh-TW.md README.md GENERIC_INTEGRATION.zh-TW.md ``` 可先用這個命令確認: ```bash cd find plugins/continuity -maxdepth 3 -type f | sort ``` --- ## 3. generic/manual 路徑會用到哪些 API `src/index.mjs` 已經 re-export 這幾個你最需要的 API: - `defaultConfig` - `normalizeContinuityConfig()` - `validateContinuityConfig()` - `buildGenericContinuityInput()` - `createGenericPreflightContinuityAdapter()` - `runGenericPreflightContinuityAdapter()` - `runManualContinuityPreflight()` - `validateReceipt()` - `isValidReceipt()` - `writeReceipt()` 如果你只想最短路徑完成整合,通常會直接用: - `runGenericPreflightContinuityAdapter(...)` - `runManualContinuityPreflight(...)` - `writeReceipt(...)` --- ## 4. continuity input 最小 contract generic adapter 吃的是 **host-agnostic continuity input**。 依 `src/continuity/types.md`,實務上最少要準備: - `planId`: string - `currentTask`: string - `taskState`: string | null - `nextDerivedAction`: object | null - `replyClosureState`: string | null - `dispatchReceipt`: object | null - `nextTaskKnown`: boolean - `sameApprovedPlan`: boolean - `taskBoundaryStop`: boolean - `highRiskStop`: boolean 可選欄位: - `nextTaskId`: string | null - `nextTaskKey`: string | null - `derivedAction`: object | null - `metadata`: object ### 最小可運作 payload 範例 ```js const source = { planId: 'approved-plan-1', currentTask: 'task-3', taskState: 'complete', nextTaskKnown: true, sameApprovedPlan: true, taskBoundaryStop: true, highRiskStop: false, nextTaskId: 'task-4', nextDerivedAction: { type: 'message_subagent', task: 'continue with task-4', }, replyClosureState: 'completed', dispatchReceipt: null, metadata: { host: 'custom-orchestrator', }, }; ``` ### 什麼情況下這個 payload 會觸發 gate? 常見高風險情境: - `taskState = 'complete'` - `nextTaskKnown = true` - `sameApprovedPlan = true` - `nextDerivedAction != null` - `dispatchReceipt = null` - `replyClosureState = 'completed'` 這類情況通常代表:**目前 task 做完了、下一步也知道了,但你還沒有真實派發 receipt,卻準備把整件事收掉。** 這正是 continuity gate 要攔的核心情境。 --- ## 5. generic adapter 接法 ### 最小接法 ```js import plugin from './plugins/continuity/src/index.mjs'; const out = plugin.runGenericPreflightContinuityAdapter({ config: plugin.defaultConfig, source, }); if (out?.block) { agentPromptBody = `${out.block}\n${agentPromptBody}`; } ``` ### 回傳值你至少要看哪些欄位 `out` 常用欄位: - `out.input` - `out.result` - `out.evaluation` - `out.block` - `out.meta.adapterName` - `out.meta.hostAgnostic` ### 什麼時候不該忽略 `out.block` 只要 `out.block` 非空,就表示 plugin 產生了應注入 prompt 的 continuity gate block。 **不要只把 `evaluation` 印 log,就算整合完成。** 真正有完成整合的最低標準,是: - 有拿到 `out.block` - 有把它接到 agent prompt/body - agent 後續真的會看到該 block --- ## 6. manual runner 接法 如果你沒有完整 generic source builder,或者只想先做一次單點 preflight 驗證,可用 manual runner: ```js import plugin from './plugins/continuity/src/index.mjs'; const out = plugin.runManualContinuityPreflight({ config: plugin.defaultConfig, planId: 'approved-plan-1', currentTask: 'task-3', taskState: 'complete', nextDerivedAction: { type: 'message_subagent', task: 'continue with task-4', }, replyClosureState: 'completed', nextTaskKnown: true, sameApprovedPlan: true, taskBoundaryStop: true, dispatchReceipt: null, }); if (out?.block) { agentPromptBody = `${out.block}\n${agentPromptBody}`; } ``` ### manual runner 適合什麼情況 - 你還沒有完整 planner,但想先驗證 gate 行為 - 你要做 integration smoke test - 你在 migration 階段,先用手動欄位對接舊宿主 ### manual runner 不代表可以跳過正式整合 manual runner 比較像 **preflight harness**,不是完整 host integration 的替代品。 等你確認行為正確後,仍建議回到 generic adapter,把 host 真實資料映射進 source。 --- ## 7. config 怎麼準備 建議先從: ```text plugins/continuity/examples/openclaw.continuity.example.json ``` 開始,內容大致如下: ```json { "enabled": true, "planMatchers": ["approved-plan"], "legalTerminalStates": [ "waiting_user", "blocked", "pending_verification" ], "receiptDir": "state/approved-plan-continuity", "requireRealDispatchReceipt": true, "allowReplyClosureWithoutDispatch": false, "debug": false, "adapter": { "forceRecall": { "enabled": true, "injectBlockLabel": "APPROVED_PLAN_CONTINUITY_GATE" }, "genericPreflight": { "enabled": true, "injectBlockLabel": "APPROVED_PLAN_CONTINUITY_GATE" } } } ``` ### generic/manual 路徑先檢查這些欄位 - [ ] `enabled = true` - [ ] `planMatchers` 符合你的 approved plan 名稱 - [ ] `legalTerminalStates` 符合你的 closure policy - [ ] `receiptDir` 可寫 - [ ] `requireRealDispatchReceipt = true`(通常建議保持) - [ ] `allowReplyClosureWithoutDispatch = false`(通常建議保持) - [ ] `adapter.genericPreflight.enabled = true` - [ ] `adapter.genericPreflight.injectBlockLabel` 有你想要的 label --- ## 8. receipt 怎麼提供 如果你的宿主真的有派發下一步,不要只在 metadata 或 log 裡寫「已派發」,請提供真實 `dispatchReceipt`。 ### 最小 receipt shape 依目前 validator,至少要有: - `planId` - `currentTask` - `nextDerivedAction` - `dispatchedAt` - `dispatchRunId` - `childSessionKey` - `replyClosureState` 對照範例: ```text plugins/continuity/examples/approved-plan-receipt.example.json ``` 範例內容: ```json { "planId": "example-plan", "currentTask": "task-01", "nextDerivedAction": { "kind": "delegate", "target": "subagent", "task": "placeholder" }, "dispatchedAt": "2026-04-24T16:43:00+08:00", "dispatchRunId": "example-run", "childSessionKey": "session-placeholder", "replyClosureState": "pending_verification" } ``` ### 寫 receipt 前先驗證 ```js import plugin from './plugins/continuity/src/index.mjs'; const receiptValidation = plugin.validateReceipt(receipt); if (!receiptValidation.ok) { throw new Error(`Invalid continuity receipt: ${receiptValidation.errors.join('; ')}`); } ``` ### 寫 receipt 範例 ```js await plugin.writeReceipt({ receiptDir: 'state/approved-plan-continuity', receipt, }); ``` ### receiptDir 先建立 ```bash cd mkdir -p state/approved-plan-continuity ``` --- ## 9. manual/generic preflight 驗證流程 這一段是本文件最重要的實作 checklist。 ### Case A:應該被 gate 擋下 測這種輸入: - task 已完成 - 下一步已知 - 同一 approved plan - 有 nextDerivedAction - 沒有 dispatchReceipt - closure 想直接完成 例如: ```js const out = plugin.runGenericPreflightContinuityAdapter({ config: plugin.defaultConfig, source: { planId: 'approved-plan-1', currentTask: 'task-3', taskState: 'complete', nextTaskKnown: true, sameApprovedPlan: true, taskBoundaryStop: true, nextTaskId: 'task-4', nextDerivedAction: { type: 'message_subagent', task: 'continue' }, replyClosureState: 'completed', dispatchReceipt: null, }, }); ``` 你應該預期: - `out.block` 非空 - `out.evaluation` 顯示這不是可安全直接結案的狀態 - agent prompt 若接上此 block,應被提醒不可把整件事直接收掉 ### Case B:有合法 terminal state,不應誤擋 例如: - `replyClosureState = 'waiting_user'` - 或 `blocked` - 或 `pending_verification` 你應該預期: - 不會把合法等待/阻塞/待驗證情境誤當成錯誤結案 ### Case C:有真實 receipt,應與 dry-run 區分 把 `dispatchReceipt` 換成合法 receipt object,再重跑一次。 你要確認: - plugin 有辨識 receipt - 行為和 `dispatchReceipt = null` 時不同 - 不是只因為 `nextDerivedAction` 存在,就一律當成已派發 --- ## 10. 如何驗證 continuity block 真的有被接進 prompt 這一步很常被忽略,但它才是整合成功與否的分水嶺。 ### 最低驗證標準 你至少要證明以下三件事: 1. plugin 有回傳 `out.block` 2. host 有把 `out.block` prepend 到 agent prompt/body 3. 最終 agent 真正看到的 prompt 中,存在: ```text [APPROVED_PLAN_CONTINUITY_GATE] ``` ### 建議做法 - 在 preflight / orchestrator 測試輸出中印出最終 prompt 前段 - 或在 debug mode 下 dump prompt body - 或加一個 integration smoke test,直接 assert block label 存在 ### 不能只驗證什麼 以下都 **不夠**: - 只看到 `out.evaluation` - 只看到 console log 說有 run adapter - 只看到 code 裡有 prepend 那一行,但沒有實際驗證 prompt 結果 --- ## 11. 建議 smoke test 順序 ### Step 1:plugin 本體測試 ```bash cd /plugins/continuity npm test ``` ### Step 2:快速 smoke ```bash cd /plugins/continuity node test/continuity.smoke.test.mjs ``` ### Step 3:你的 generic/manual host integration test 如果你有自家 orchestrator,請至少補一個 smoke case: - 無 receipt + 想 completed → 應被 gate - 合法 terminal state → 不應誤擋 - 有合法 receipt → 行為應與無 receipt 不同 ### Step 4:人工檢查 prompt block 確認輸出真的含有: ```text [APPROVED_PLAN_CONTINUITY_GATE] ``` --- ## 12. 宿主實作者最常踩的坑 ### 坑 1:只有 planner output,沒有實際 prompt injection 這會讓 continuity 只停在「有計算」,沒有真正影響 agent 行為。 ### 坑 2:把 dry-run next action 當成已 dispatch `nextDerivedAction != null` 不等於 `dispatchReceipt != null`。 ### 坑 3:payload 缺少 `planId` 或 `currentTask` `buildGenericContinuityInput()` 若拿不到這些關鍵欄位,可能直接回不出有效 input。 ### 坑 4:把合法 terminal state 設定錯 例如你宿主其實接受 `waiting_user`,但 config 沒放進 `legalTerminalStates`,就可能造成誤判。 ### 坑 5:receipt 寫檔了,但內容不合法 先 `validateReceipt()`,不要跳過。 ### 坑 6:只測 happy path 至少要測: - 應 fail 的情況 - 應 pass / 不誤擋的情況 - 有 receipt 與無 receipt 的差異 --- ## 13. 一頁版 generic/manual checklist ### 安裝 - [ ] 複製 `plugins/continuity` 到 workspace - [ ] 確認 `src/index.mjs` 可 import - [ ] 建立 `state/approved-plan-continuity/` ### config - [ ] 套用 example config - [ ] 開啟 `adapter.genericPreflight.enabled` - [ ] 確認 `receiptDir` 可寫 ### host 接法 - [ ] 準備 generic continuity payload - [ ] 呼叫 `runGenericPreflightContinuityAdapter(...)` 或 `runManualContinuityPreflight(...)` - [ ] 將 `out.block` prepend 到 agent prompt/body ### receipt - [ ] 真有派發時才產生 receipt - [ ] 先 `validateReceipt()` - [ ] 再 `writeReceipt()` ### 驗證 - [ ] `cd plugins/continuity && npm test` - [ ] 驗證無 receipt 時會擋錯誤結案 - [ ] 驗證合法 terminal state 不誤擋 - [ ] 驗證最終 prompt 含 continuity block --- ## 14. 什麼叫 generic/manual 整合完成 只有在以下條件都成立時,才算真的完成: - 不是只完成 plugin 安裝,而是 host 真的有呼叫 adapter - 不是只拿到 evaluation,而是 block 真的有注入 agent prompt - 不是只做 dry-run,而是實際 dispatch 時可提供真實 receipt - 不是只測 happy path,而是有驗證 gate fail/pass 的差異 - 文件足夠讓陌生宿主實作者知道自己要準備哪些欄位、怎麼驗證 如果這五點都做到,才算接近「沒有 `force-recall` 的宿主也能照文件安裝」。 --- ## 15. 參考文件 - `plugins/continuity/README.zh-TW.md` - `plugins/continuity/README.md` - `plugins/continuity/AGENT_GUIDE.zh-TW.md` - `plugins/continuity/AGENT_GUIDE.md` - `plugins/continuity/src/continuity/types.md` - `plugins/continuity/examples/openclaw.continuity.example.json` - `plugins/continuity/examples/approved-plan-receipt.example.json` 如果你的宿主未來會新增專屬 adapter,也建議保留這份 generic/manual runbook,作為最低整合基準。