refactor(plugin): extract shared session state and decision pruning
This commit is contained in:
31
plugin/core/session-state.ts
Normal file
31
plugin/core/session-state.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Decision } from "../rules.js";
|
||||||
|
|
||||||
|
export type DecisionRecord = {
|
||||||
|
decision: Decision;
|
||||||
|
createdAt: number;
|
||||||
|
needsRestore?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MAX_SESSION_DECISIONS = 2000;
|
||||||
|
export const DECISION_TTL_MS = 5 * 60 * 1000;
|
||||||
|
|
||||||
|
export const sessionDecision = new Map<string, DecisionRecord>();
|
||||||
|
export const sessionAllowed = new Map<string, boolean>();
|
||||||
|
export const sessionInjected = new Set<string>();
|
||||||
|
export const sessionChannelId = new Map<string, string>();
|
||||||
|
export const sessionAccountId = new Map<string, string>();
|
||||||
|
export const sessionTurnHandled = new Set<string>();
|
||||||
|
|
||||||
|
export function pruneDecisionMap(now = Date.now()): void {
|
||||||
|
for (const [k, v] of sessionDecision.entries()) {
|
||||||
|
if (now - v.createdAt > DECISION_TTL_MS) sessionDecision.delete(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionDecision.size <= MAX_SESSION_DECISIONS) return;
|
||||||
|
const keys = sessionDecision.keys();
|
||||||
|
while (sessionDecision.size > MAX_SESSION_DECISIONS) {
|
||||||
|
const k = keys.next();
|
||||||
|
if (k.done) break;
|
||||||
|
sessionDecision.delete(k.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||||
import type { Decision, DirigentConfig } from "./rules.js";
|
import type { DirigentConfig } from "./rules.js";
|
||||||
import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.js";
|
import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.js";
|
||||||
import { registerMessageReceivedHook } from "./hooks/message-received.js";
|
import { registerMessageReceivedHook } from "./hooks/message-received.js";
|
||||||
import { registerBeforeModelResolveHook } from "./hooks/before-model-resolve.js";
|
import { registerBeforeModelResolveHook } from "./hooks/before-model-resolve.js";
|
||||||
@@ -18,27 +18,22 @@ import { ensureTurnOrder, recordChannelAccount } from "./core/turn-bootstrap.js"
|
|||||||
import { debugCtxSummary, pickDefined, shouldDebugLog } from "./core/utils.js";
|
import { debugCtxSummary, pickDefined, shouldDebugLog } from "./core/utils.js";
|
||||||
import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js";
|
import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js";
|
||||||
import { startNoReplyApi, stopNoReplyApi } from "./core/no-reply-process.js";
|
import { startNoReplyApi, stopNoReplyApi } from "./core/no-reply-process.js";
|
||||||
|
import {
|
||||||
type DecisionRecord = {
|
DECISION_TTL_MS,
|
||||||
decision: Decision;
|
pruneDecisionMap,
|
||||||
createdAt: number;
|
sessionAccountId,
|
||||||
needsRestore?: boolean;
|
sessionAllowed,
|
||||||
};
|
sessionChannelId,
|
||||||
|
sessionDecision,
|
||||||
|
sessionInjected,
|
||||||
|
sessionTurnHandled,
|
||||||
|
} from "./core/session-state.js";
|
||||||
|
|
||||||
type DebugConfig = {
|
type DebugConfig = {
|
||||||
enableDebugLogs?: boolean;
|
enableDebugLogs?: boolean;
|
||||||
debugLogChannelIds?: string[];
|
debugLogChannelIds?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const sessionDecision = new Map<string, DecisionRecord>();
|
|
||||||
const sessionAllowed = new Map<string, boolean>(); // Track if session was allowed to speak (true) or forced no-reply (false)
|
|
||||||
const sessionInjected = new Set<string>(); // Track which sessions have already injected the end marker
|
|
||||||
const sessionChannelId = new Map<string, string>(); // Track sessionKey -> channelId mapping
|
|
||||||
const sessionAccountId = new Map<string, string>(); // Track sessionKey -> accountId mapping
|
|
||||||
const sessionTurnHandled = new Set<string>(); // Track sessions where turn was already advanced in before_message_write
|
|
||||||
const MAX_SESSION_DECISIONS = 2000;
|
|
||||||
const DECISION_TTL_MS = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string): string {
|
function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string): string {
|
||||||
const symbols = endSymbols.length > 0 ? endSymbols.join("") : "🔚";
|
const symbols = endSymbols.length > 0 ? endSymbols.join("") : "🔚";
|
||||||
let instruction = `Your response MUST end with ${symbols}. Exception: gateway keywords (e.g. NO_REPLY, HEARTBEAT_OK) must NOT include ${symbols}.`;
|
let instruction = `Your response MUST end with ${symbols}. Exception: gateway keywords (e.g. NO_REPLY, HEARTBEAT_OK) must NOT include ${symbols}.`;
|
||||||
@@ -53,20 +48,6 @@ function buildSchedulingIdentifierInstruction(schedulingIdentifier: string): str
|
|||||||
return `\n\nScheduling identifier: "${schedulingIdentifier}". This identifier itself is meaningless — it carries no semantic content. When you receive a message containing <@YOUR_USER_ID> followed by the scheduling identifier, check recent chat history and decide whether you have something to say. If not, reply NO_REPLY.`;
|
return `\n\nScheduling identifier: "${schedulingIdentifier}". This identifier itself is meaningless — it carries no semantic content. When you receive a message containing <@YOUR_USER_ID> followed by the scheduling identifier, check recent chat history and decide whether you have something to say. If not, reply NO_REPLY.`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pruneDecisionMap(now = Date.now()) {
|
|
||||||
for (const [k, v] of sessionDecision.entries()) {
|
|
||||||
if (now - v.createdAt > DECISION_TTL_MS) sessionDecision.delete(k);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionDecision.size <= MAX_SESSION_DECISIONS) return;
|
|
||||||
const keys = sessionDecision.keys();
|
|
||||||
while (sessionDecision.size > MAX_SESSION_DECISIONS) {
|
|
||||||
const k = keys.next();
|
|
||||||
if (k.done) break;
|
|
||||||
sessionDecision.delete(k.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: "dirigent",
|
id: "dirigent",
|
||||||
name: "Dirigent",
|
name: "Dirigent",
|
||||||
|
|||||||
Reference in New Issue
Block a user