309 lines
8.1 KiB
Markdown
309 lines
8.1 KiB
Markdown
# Continuity Plugin(MVP → generalized checkpoint)
|
||
|
||
> English version: `README.md`
|
||
|
||
這個套件把目前 approved-plan continuity hard gate 抽離成一個可安裝、可測試、可在 hook 內重用的 OpenClaw plugin。
|
||
|
||
它仍保留既有 approved-plan 行為,但現在往比較通用的 **engine + adapter** 結構前進了一步:
|
||
|
||
- 有 host-agnostic 的 continuity engine input/output contract
|
||
- 保留既有 `force-recall` parity adapter
|
||
- 新增 `generic-preflight` adapter 與 manual runner,讓未使用 `force-recall` 的 workspace 也能接
|
||
|
||
## 目前能做什麼
|
||
|
||
- 驗證 continuity config
|
||
- 驗證 dispatch receipt contract
|
||
- 寫出 receipt 檔案
|
||
- 評估 approved-plan continuity gate
|
||
- 產生可注入 prompt 的 continuity gate block
|
||
- 透過 `force-recall` adapter,把 hook 端的 wrapper/planner 結果轉成 continuity input
|
||
- 透過 `generic-preflight` adapter,直接吃 host-agnostic continuity input
|
||
- 透過 manual preflight runner,在沒有 `force-recall` 的情況下也能直接呼叫
|
||
|
||
## 安裝位置
|
||
|
||
建議直接放在 OpenClaw workspace 內:
|
||
|
||
```text
|
||
<workspace>/plugins/continuity
|
||
```
|
||
|
||
現在可支援兩類整合路徑:原本的 `force-recall` 路徑,以及較通用的 generic path。
|
||
|
||
```text
|
||
<workspace>/
|
||
hooks/
|
||
force-recall/
|
||
handler.ts
|
||
HOOK.md
|
||
plugins/
|
||
continuity/
|
||
README.zh-TW.md
|
||
README.md
|
||
HOOK.md
|
||
package.json
|
||
examples/
|
||
src/
|
||
test/
|
||
scripts/
|
||
test_force_recall_long_task_preflight.mjs
|
||
```
|
||
|
||
## 目錄結構
|
||
|
||
```text
|
||
plugins/continuity/
|
||
README.zh-TW.md
|
||
README.md
|
||
HOOK.md
|
||
package.json
|
||
examples/
|
||
approved-plan-receipt.example.json
|
||
openclaw.continuity.example.json
|
||
src/
|
||
index.mjs
|
||
adapters/
|
||
force-recall.mjs
|
||
generic-preflight.mjs
|
||
config/
|
||
defaults.mjs
|
||
schema.mjs
|
||
continuity/
|
||
engine.mjs
|
||
evaluator.mjs
|
||
receipt-store.mjs
|
||
receipt-validator.mjs
|
||
types.md
|
||
test/
|
||
continuity.config.test.mjs
|
||
continuity.evaluator.test.mjs
|
||
continuity.plugin.test.mjs
|
||
continuity.receipt-store.test.mjs
|
||
continuity.receipt-validator.test.mjs
|
||
continuity.smoke.test.mjs
|
||
```
|
||
|
||
## 公開介面
|
||
|
||
- `src/config/schema.mjs`
|
||
- `src/config/defaults.mjs`
|
||
- `src/continuity/engine.mjs`
|
||
- `src/continuity/evaluator.mjs`
|
||
- `src/continuity/receipt-validator.mjs`
|
||
- `src/continuity/receipt-store.mjs`
|
||
- `src/adapters/force-recall.mjs`
|
||
- `src/adapters/generic-preflight.mjs`
|
||
- `src/index.mjs`
|
||
|
||
`src/index.mjs` 目前會 re-export:
|
||
|
||
- `defaultConfig`
|
||
- `cloneDefaultConfig()`
|
||
- `validateContinuityConfig()` / `normalizeContinuityConfig()`
|
||
- `normalizeContinuityEngineInput()`
|
||
- `createContinuityEngineResult()` / `createContinuityEngineContract()`
|
||
- `evaluateContinuity()` / `buildContinuityGateBlock()`
|
||
- `validateReceipt()` / `isValidReceipt()`
|
||
- `slugifyReceiptSegment()` / `buildReceiptFilename()` / `writeReceipt()`
|
||
- `buildApprovedPlanContinuityInput()`
|
||
- `createForceRecallContinuityAdapter()` / `runForceRecallContinuityAdapter()`
|
||
- `buildGenericContinuityInput()`
|
||
- `createGenericPreflightContinuityAdapter()` / `runGenericPreflightContinuityAdapter()`
|
||
- `runManualContinuityPreflight()`
|
||
|
||
## Host-agnostic engine contract
|
||
|
||
generalized engine 會吃一個正規化後的 continuity input,常用欄位包括:
|
||
|
||
- `planId`
|
||
- `currentTask`
|
||
- `taskState`
|
||
- `nextDerivedAction`
|
||
- `replyClosureState`
|
||
- `dispatchReceipt`
|
||
- `nextTaskKnown`
|
||
- `sameApprovedPlan`
|
||
- `taskBoundaryStop`
|
||
- `highRiskStop`
|
||
|
||
generalized adapter 會回傳共同 contract:
|
||
|
||
- `input`
|
||
- `result`
|
||
- `evaluation`
|
||
- `block`
|
||
- `meta.adapterName`
|
||
- `meta.hostAgnostic`
|
||
|
||
精簡契約請見 `src/continuity/types.md`。
|
||
|
||
## Example config
|
||
|
||
請從 `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"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
預設值定義在 `src/config/defaults.mjs`。
|
||
|
||
## 整合路徑 A:`force-recall`
|
||
|
||
原本的 MVP 整合點仍是 `hooks/force-recall/handler.ts`。
|
||
|
||
```js
|
||
import plugin from './plugins/continuity/src/index.mjs';
|
||
|
||
const out = plugin.runForceRecallContinuityAdapter({
|
||
config: plugin.defaultConfig,
|
||
wrapperResult,
|
||
autoChainPlanResult,
|
||
});
|
||
|
||
if (out?.block) {
|
||
context.bodyForAgent = `${out.block}\n${context.bodyForAgent}`;
|
||
}
|
||
```
|
||
|
||
## 整合路徑 B:generic / manual preflight
|
||
|
||
如果你的 workspace **沒有** 使用 `force-recall`,現在也可以安裝這個 plugin,直接呼叫 generalized adapter 或 manual runner。
|
||
|
||
### Generic preflight adapter
|
||
|
||
```js
|
||
import plugin from './plugins/continuity/src/index.mjs';
|
||
|
||
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,
|
||
},
|
||
});
|
||
```
|
||
|
||
### 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' },
|
||
replyClosureState: 'waiting_user',
|
||
});
|
||
```
|
||
|
||
若 `out.block` 非空,就把它 prepend 到 agent 會看到的 prompt/body。
|
||
|
||
## Receipt contract
|
||
|
||
最小 receipt shape 如下:
|
||
|
||
- `planId`
|
||
- `currentTask`
|
||
- `nextDerivedAction`
|
||
- `dispatchedAt`
|
||
- `dispatchRunId`
|
||
- `childSessionKey`
|
||
- `replyClosureState`
|
||
|
||
若要把 receipt 落盤,可用:
|
||
|
||
```js
|
||
import { writeReceipt } from './src/index.mjs';
|
||
|
||
await writeReceipt({
|
||
receiptDir: 'state/approved-plan-continuity',
|
||
receipt,
|
||
});
|
||
```
|
||
|
||
## Smoke test / 驗證
|
||
|
||
plugin 本體必要驗證:
|
||
|
||
```bash
|
||
cd plugins/continuity
|
||
npm test
|
||
node test/continuity.smoke.test.mjs
|
||
```
|
||
|
||
若你的 workspace 有使用 `force-recall`,再補跑:
|
||
|
||
```bash
|
||
cd /path/to/workspace
|
||
node scripts/test_force_recall_long_task_preflight.mjs
|
||
node --check hooks/force-recall/handler.ts
|
||
```
|
||
|
||
## 安裝與套用步驟(給其他 OpenClaw 使用者)
|
||
|
||
1. 把 `plugins/continuity` 複製到你的 workspace。
|
||
2. 選一條整合路徑:
|
||
- `force-recall`:載入 `runForceRecallContinuityAdapter(...)`
|
||
- 沒有 `force-recall`:呼叫 `runGenericPreflightContinuityAdapter(...)` 或 `runManualContinuityPreflight(...)`
|
||
3. 視需要調整 continuity config,至少確認:
|
||
- `planMatchers`
|
||
- `legalTerminalStates`
|
||
- `receiptDir`
|
||
- `adapter.forceRecall.injectBlockLabel`
|
||
- `adapter.genericPreflight.injectBlockLabel`
|
||
4. 若你的 dispatch 流程會產生 child run/session,請同步寫出 receipt。
|
||
5. 跑 plugin 測試與對應 workspace smoke path。
|
||
6. 確認 agent prompt 內可見 continuity gate block,且 dry-run dispatch 不會被誤判為 pass。
|
||
|
||
## 目前限制
|
||
|
||
- 它仍以 approved-plan continuity hard gate 為中心,不是完整通用 workflow engine。
|
||
- generalized engine contract 目前刻意保持最小且保守。
|
||
- `force-recall` 仍是目前最成熟的 adapter。
|
||
- receipt store 只負責寫檔,不含 retention、cleanup、indexing。
|
||
- receipt validator 目前只檢查最小 contract,不驗證每個 `nextDerivedAction` 子欄位語意。
|
||
|
||
## 備註
|
||
|
||
- 預設 legal terminal states:`waiting_user`、`blocked`、`pending_verification`
|
||
- evaluator 保留既有行為,包括 `missing_dispatch_receipt` 與 `missing_auto_next_dispatch`
|
||
- 新增 generic path 後,即使沒有 `force-recall` hook,也比較能重用這個 plugin
|
||
- `HOOK.md` 說明的是 plugin/hook adapter 契約定位,不是完整安裝說明
|
||
|
||
## 英文文件
|
||
|
||
英文版請見 `README.md`。
|