309 lines
8.2 KiB
Markdown
309 lines
8.2 KiB
Markdown
# Continuity Plugin (MVP → generalized checkpoint)
|
|
|
|
> 中文版:`README.zh-TW.md`
|
|
|
|
This package extracts the current approved-plan continuity hard gate into a small installable, testable OpenClaw plugin.
|
|
|
|
The package still preserves the current approved-plan behavior, but it now moves one step closer to a more general **engine + adapter** structure:
|
|
|
|
- a host-agnostic continuity engine input/output contract
|
|
- the existing `force-recall` parity adapter
|
|
- a new `generic-preflight` adapter and manual runner for non-`force-recall` integration
|
|
|
|
## What this package currently provides
|
|
|
|
- continuity config validation
|
|
- dispatch receipt contract validation
|
|
- receipt file writing
|
|
- approved-plan continuity gate evaluation
|
|
- prompt block generation for the continuity gate
|
|
- a `force-recall` adapter that maps hook wrapper/planner output into continuity input
|
|
- a `generic-preflight` adapter that accepts host-agnostic continuity input directly
|
|
- a manual preflight runner for workspaces that do not use `force-recall`
|
|
|
|
## Install location
|
|
|
|
Recommended location inside an OpenClaw workspace:
|
|
|
|
```text
|
|
<workspace>/plugins/continuity
|
|
```
|
|
|
|
Possible integration layouts now include both the original `force-recall` path and a more 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
|
|
```
|
|
|
|
## Directory structure
|
|
|
|
```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
|
|
```
|
|
|
|
## Public surface
|
|
|
|
- `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` currently re-exports:
|
|
|
|
- `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
|
|
|
|
The generalized engine accepts a normalized continuity input with fields such as:
|
|
|
|
- `planId`
|
|
- `currentTask`
|
|
- `taskState`
|
|
- `nextDerivedAction`
|
|
- `replyClosureState`
|
|
- `dispatchReceipt`
|
|
- `nextTaskKnown`
|
|
- `sameApprovedPlan`
|
|
- `taskBoundaryStop`
|
|
- `highRiskStop`
|
|
|
|
Generalized adapters return a common contract:
|
|
|
|
- `input`
|
|
- `result`
|
|
- `evaluation`
|
|
- `block`
|
|
- `meta.adapterName`
|
|
- `meta.hostAgnostic`
|
|
|
|
See `src/continuity/types.md` for the concise contract notes.
|
|
|
|
## Example config
|
|
|
|
Start from `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"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Defaults are defined in `src/config/defaults.mjs`.
|
|
|
|
## Integration path A: `force-recall`
|
|
|
|
The original MVP integration point remains `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}`;
|
|
}
|
|
```
|
|
|
|
## Integration path B: generic/manual preflight
|
|
|
|
If your workspace does **not** use `force-recall`, you can still install and use the plugin by calling the generalized adapter or the manual runner directly.
|
|
|
|
### 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',
|
|
});
|
|
```
|
|
|
|
If `out.block` is non-empty, prepend it into the prompt/body seen by the agent.
|
|
|
|
## Receipt contract
|
|
|
|
Minimum receipt shape:
|
|
|
|
- `planId`
|
|
- `currentTask`
|
|
- `nextDerivedAction`
|
|
- `dispatchedAt`
|
|
- `dispatchRunId`
|
|
- `childSessionKey`
|
|
- `replyClosureState`
|
|
|
|
To persist a receipt:
|
|
|
|
```js
|
|
import { writeReceipt } from './src/index.mjs';
|
|
|
|
await writeReceipt({
|
|
receiptDir: 'state/approved-plan-continuity',
|
|
receipt,
|
|
});
|
|
```
|
|
|
|
## Smoke test / verification
|
|
|
|
Required plugin verification:
|
|
|
|
```bash
|
|
cd plugins/continuity
|
|
npm test
|
|
node test/continuity.smoke.test.mjs
|
|
```
|
|
|
|
If your workspace uses `force-recall`, also run:
|
|
|
|
```bash
|
|
cd /path/to/workspace
|
|
node scripts/test_force_recall_long_task_preflight.mjs
|
|
node --check hooks/force-recall/handler.ts
|
|
```
|
|
|
|
## Install and apply steps for another OpenClaw workspace
|
|
|
|
1. Copy `plugins/continuity` into your workspace.
|
|
2. Choose one integration path:
|
|
- `force-recall`: load `runForceRecallContinuityAdapter(...)`
|
|
- no `force-recall`: call `runGenericPreflightContinuityAdapter(...)` or `runManualContinuityPreflight(...)`
|
|
3. Adjust config as needed, especially:
|
|
- `planMatchers`
|
|
- `legalTerminalStates`
|
|
- `receiptDir`
|
|
- `adapter.forceRecall.injectBlockLabel`
|
|
- `adapter.genericPreflight.injectBlockLabel`
|
|
4. If your dispatch flow creates child runs/sessions, persist a real receipt.
|
|
5. Run plugin tests and the relevant workspace smoke path.
|
|
6. Confirm the agent prompt contains the continuity gate block and that dry-run dispatch alone does not pass the gate.
|
|
|
|
## Current limitations
|
|
|
|
- This is still centered on the approved-plan continuity hard gate, not a full general workflow engine.
|
|
- The generalized engine contract is intentionally minimal and conservative.
|
|
- `force-recall` remains the most battle-tested adapter.
|
|
- The receipt store only writes files; it does not manage retention, cleanup, or indexing.
|
|
- The receipt validator checks the minimum contract only; it does not deeply validate every `nextDerivedAction` subtype.
|
|
|
|
## Notes
|
|
|
|
- Default legal terminal states are `waiting_user`, `blocked`, and `pending_verification`
|
|
- The evaluator preserves current behavior, including `missing_dispatch_receipt` and `missing_auto_next_dispatch`
|
|
- The new generic path makes the plugin more reusable even without the `force-recall` hook
|
|
- `HOOK.md` describes the plugin/hook adapter contract boundary, not the full installation guide
|
|
|
|
## Chinese documentation
|
|
|
|
See `README.zh-TW.md` for the Traditional Chinese version.
|