feat: add reusable core store and OpenClaw state-file adapter skeleton / 新增可重用 state store 與 OpenClaw state-file adapter 骨架
This commit is contained in:
29
src/core/callback-contract.ts
Normal file
29
src/core/callback-contract.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { NormalizedReplyEndEvent, ReplyEndChoice } from "../types.js"
|
||||
|
||||
export function parseTelegramCallbackData(raw: string): ReplyEndChoice | null {
|
||||
if (raw === "rec:continue") return "continue"
|
||||
if (raw === "rec:stop") return "stop"
|
||||
return null
|
||||
}
|
||||
|
||||
export function normalizeTelegramCallback(params: {
|
||||
callbackData: string
|
||||
conversationId: string
|
||||
sessionKey: string | null
|
||||
sourceMessageId: string
|
||||
sourceCallbackId: string
|
||||
timestamp: string
|
||||
}): NormalizedReplyEndEvent | null {
|
||||
const choice = parseTelegramCallbackData(params.callbackData)
|
||||
if (!choice) return null
|
||||
|
||||
return {
|
||||
choice,
|
||||
conversationId: params.conversationId,
|
||||
sessionKey: params.sessionKey,
|
||||
sourceMessageId: params.sourceMessageId,
|
||||
sourceCallbackId: params.sourceCallbackId,
|
||||
channel: "telegram",
|
||||
timestamp: params.timestamp,
|
||||
}
|
||||
}
|
||||
13
src/core/policy.ts
Normal file
13
src/core/policy.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ReplyEndPolicyDecision, ReplyEndPolicyInput } from "../types.js"
|
||||
|
||||
export function evaluateReplyEndPolicy(input: ReplyEndPolicyInput): ReplyEndPolicyDecision {
|
||||
if (input.hasTypedUserFollowup) {
|
||||
return { suppressProactiveContinuation: false }
|
||||
}
|
||||
|
||||
if (input.state?.lastChoice === "stop" && input.state.active) {
|
||||
return { suppressProactiveContinuation: true }
|
||||
}
|
||||
|
||||
return { suppressProactiveContinuation: false }
|
||||
}
|
||||
32
src/core/state.ts
Normal file
32
src/core/state.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { NormalizedReplyEndEvent, ReplyEndState, ReplyEndStateMap } from "../types.js"
|
||||
|
||||
export function buildReplyEndState(event: NormalizedReplyEndEvent): ReplyEndState {
|
||||
return {
|
||||
lastChoice: event.choice,
|
||||
lastChoiceAt: event.timestamp,
|
||||
sourceMessageId: event.sourceMessageId,
|
||||
sourceCallbackId: event.sourceCallbackId,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
export function clearReplyEndState(): null {
|
||||
return null
|
||||
}
|
||||
|
||||
export function setReplyEndState(map: ReplyEndStateMap, conversationId: string, state: ReplyEndState): ReplyEndStateMap {
|
||||
return {
|
||||
...map,
|
||||
[conversationId]: state,
|
||||
}
|
||||
}
|
||||
|
||||
export function getReplyEndState(map: ReplyEndStateMap, conversationId: string): ReplyEndState | null {
|
||||
return map[conversationId] ?? null
|
||||
}
|
||||
|
||||
export function clearReplyEndStateForConversation(map: ReplyEndStateMap, conversationId: string): ReplyEndStateMap {
|
||||
const next = { ...map }
|
||||
delete next[conversationId]
|
||||
return next
|
||||
}
|
||||
41
src/core/store.ts
Normal file
41
src/core/store.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import type { ReplyEndState, ReplyEndStateMap } from "../types.js"
|
||||
import { clearReplyEndStateForConversation, getReplyEndState, setReplyEndState } from "./state.js"
|
||||
|
||||
export function resolveReplyEndStateFile(baseDir: string): string {
|
||||
return path.join(baseDir, "reply-end-controls.json")
|
||||
}
|
||||
|
||||
export function readReplyEndStateMap(filePath: string): ReplyEndStateMap {
|
||||
if (!fs.existsSync(filePath)) return {}
|
||||
try {
|
||||
const raw = fs.readFileSync(filePath, "utf-8")
|
||||
const parsed = JSON.parse(raw)
|
||||
return parsed && typeof parsed === "object" ? parsed as ReplyEndStateMap : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export function writeReplyEndStateMap(filePath: string, stateMap: ReplyEndStateMap): void {
|
||||
fs.writeFileSync(filePath, JSON.stringify(stateMap, null, 2))
|
||||
}
|
||||
|
||||
export function saveReplyEndState(filePath: string, conversationId: string, state: ReplyEndState): ReplyEndStateMap {
|
||||
const current = readReplyEndStateMap(filePath)
|
||||
const next = setReplyEndState(current, conversationId, state)
|
||||
writeReplyEndStateMap(filePath, next)
|
||||
return next
|
||||
}
|
||||
|
||||
export function loadReplyEndState(filePath: string, conversationId: string): ReplyEndState | null {
|
||||
return getReplyEndState(readReplyEndStateMap(filePath), conversationId)
|
||||
}
|
||||
|
||||
export function removeReplyEndState(filePath: string, conversationId: string): ReplyEndStateMap {
|
||||
const current = readReplyEndStateMap(filePath)
|
||||
const next = clearReplyEndStateForConversation(current, conversationId)
|
||||
writeReplyEndStateMap(filePath, next)
|
||||
return next
|
||||
}
|
||||
Reference in New Issue
Block a user