openclaw-topic-shift-reset 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -2
- package/docs/configuration.md +5 -1
- package/openclaw.plugin.json +6 -1
- package/package.json +1 -1
- package/src/index.ts +131 -25
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ Add this plugin entry in `~/.openclaw/openclaw.json` (or merge into your existin
|
|
|
47
47
|
},
|
|
48
48
|
"softSuspect": {
|
|
49
49
|
"action": "ask",
|
|
50
|
+
"mode": "strict",
|
|
50
51
|
"ttlSeconds": 120
|
|
51
52
|
},
|
|
52
53
|
"dryRun": false,
|
|
@@ -116,6 +117,7 @@ When the classifier sees a soft topic-shift signal (`suspect`) but not enough co
|
|
|
116
117
|
{
|
|
117
118
|
"softSuspect": {
|
|
118
119
|
"action": "ask",
|
|
120
|
+
"mode": "strict",
|
|
119
121
|
"prompt": "Potential topic shift detected. Ask one short clarification question to confirm the user's new goal before proceeding.",
|
|
120
122
|
"ttlSeconds": 120
|
|
121
123
|
}
|
|
@@ -123,6 +125,7 @@ When the classifier sees a soft topic-shift signal (`suspect`) but not enough co
|
|
|
123
125
|
```
|
|
124
126
|
|
|
125
127
|
- `action`: `ask` (default) or `none`.
|
|
128
|
+
- `mode`: `strict` (default, require clarification turn before soft-confirm reset) or `best_effort` (legacy timing-based behavior).
|
|
126
129
|
- `prompt`: optional custom steer text.
|
|
127
130
|
- `ttlSeconds`: max age before a pending steer expires.
|
|
128
131
|
|
|
@@ -151,8 +154,16 @@ All plugin logs are prefixed with `topic-shift-reset:`.
|
|
|
151
154
|
|
|
152
155
|
### Debug (`debug: true`)
|
|
153
156
|
|
|
154
|
-
- `classify source=<...> kind=<warmup|stable|suspect|rotate-hard|rotate-soft> reason=<...>
|
|
155
|
-
Full classifier output and metrics for a processed message.
|
|
157
|
+
- `classify source=<...> kind=<warmup|stable|suspect|rotate-hard|rotate-soft> reason=<...> ... textHash=<...> tokens=[...] text="..."`
|
|
158
|
+
Full classifier output and metrics plus a compact message preview for a processed message.
|
|
159
|
+
- `suspect-queued session=<...>`
|
|
160
|
+
Soft-suspect state queued for clarification steering.
|
|
161
|
+
- `ask-injected session=<...>`
|
|
162
|
+
Clarification steer was injected into prompt build for this session.
|
|
163
|
+
- `ask-resolved user-reply session=<...>`
|
|
164
|
+
A new user reply arrived after the injected clarification turn.
|
|
165
|
+
- `ask-blocked-waiting-injection session=<...>`
|
|
166
|
+
Strict mode prevented soft-confirm reset until clarification steer is injected.
|
|
156
167
|
- `skip-internal-provider source=<...> provider=<...> session=<...>`
|
|
157
168
|
Skipped event from internal/non-user provider (for example cron/system paths).
|
|
158
169
|
- `skip-low-signal source=<...> session=<...> chars=<n> tokens=<n>`
|
package/docs/configuration.md
CHANGED
|
@@ -19,6 +19,7 @@ This plugin now accepts one canonical key per concept:
|
|
|
19
19
|
},
|
|
20
20
|
"softSuspect": {
|
|
21
21
|
"action": "ask",
|
|
22
|
+
"mode": "strict",
|
|
22
23
|
"ttlSeconds": 120
|
|
23
24
|
},
|
|
24
25
|
"dryRun": false,
|
|
@@ -29,6 +30,7 @@ This plugin now accepts one canonical key per concept:
|
|
|
29
30
|
## Public options
|
|
30
31
|
|
|
31
32
|
Classifier inputs are limited to inbound user message text and successful outbound agent message text.
|
|
33
|
+
With `softSuspect.mode: "strict"` (default), a soft-confirm reset is blocked until a clarification steer is injected and a subsequent user reply is observed.
|
|
32
34
|
|
|
33
35
|
- `enabled`: plugin on/off.
|
|
34
36
|
- `preset`: `conservative | balanced | aggressive`.
|
|
@@ -41,10 +43,11 @@ Classifier inputs are limited to inbound user message text and successful outbou
|
|
|
41
43
|
- `handoff.lastN`: number of transcript messages to include in handoff.
|
|
42
44
|
- `handoff.maxChars`: per-message truncation cap in handoff text.
|
|
43
45
|
- `softSuspect.action`: `ask | none`.
|
|
46
|
+
- `softSuspect.mode`: `strict | best_effort`.
|
|
44
47
|
- `softSuspect.prompt`: optional steer text injected on soft-suspect.
|
|
45
48
|
- `softSuspect.ttlSeconds`: expiry for pending soft-suspect steer.
|
|
46
49
|
- `dryRun`: logs would-rotate events without session resets.
|
|
47
|
-
- `debug`: emits per-message classifier diagnostics.
|
|
50
|
+
- `debug`: emits per-message classifier diagnostics, including compact message previews.
|
|
48
51
|
|
|
49
52
|
## Built-in preset defaults
|
|
50
53
|
|
|
@@ -71,6 +74,7 @@ Classifier inputs are limited to inbound user message text and successful outbou
|
|
|
71
74
|
- `handoff.lastN`: `6`
|
|
72
75
|
- `handoff.maxChars`: `220`
|
|
73
76
|
- `softSuspect.action`: `ask`
|
|
77
|
+
- `softSuspect.mode`: `strict`
|
|
74
78
|
- `softSuspect.ttlSeconds`: `120`
|
|
75
79
|
- `advanced.minSignalChars`: `20`
|
|
76
80
|
- `advanced.minSignalTokenCount`: `3`
|
package/openclaw.plugin.json
CHANGED
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
"enum": ["none", "ask"],
|
|
74
74
|
"default": "ask"
|
|
75
75
|
},
|
|
76
|
+
"mode": {
|
|
77
|
+
"type": "string",
|
|
78
|
+
"enum": ["strict", "best_effort"],
|
|
79
|
+
"default": "strict"
|
|
80
|
+
},
|
|
76
81
|
"prompt": {
|
|
77
82
|
"type": "string",
|
|
78
83
|
"default": "Potential topic shift detected. Ask one short clarification question to confirm the user's new goal before proceeding."
|
|
@@ -268,7 +273,7 @@
|
|
|
268
273
|
},
|
|
269
274
|
"softSuspect": {
|
|
270
275
|
"label": "Soft-Suspect Clarification",
|
|
271
|
-
"help": "
|
|
276
|
+
"help": "strict = require one clarification turn before soft-confirm reset. best_effort = steer when possible."
|
|
272
277
|
},
|
|
273
278
|
"dryRun": {
|
|
274
279
|
"label": "Dry Run",
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ type PresetName = "conservative" | "balanced" | "aggressive";
|
|
|
12
12
|
type EmbeddingProvider = "auto" | "none" | "openai" | "ollama";
|
|
13
13
|
type HandoffMode = "none" | "summary" | "verbatim_last_n";
|
|
14
14
|
type SoftSuspectAction = "none" | "ask";
|
|
15
|
+
type SoftSuspectMode = "best_effort" | "strict";
|
|
15
16
|
|
|
16
17
|
type EmbeddingConfig = {
|
|
17
18
|
provider?: EmbeddingProvider;
|
|
@@ -29,6 +30,7 @@ type HandoffConfig = {
|
|
|
29
30
|
|
|
30
31
|
type SoftSuspectConfig = {
|
|
31
32
|
action?: SoftSuspectAction;
|
|
33
|
+
mode?: SoftSuspectMode;
|
|
32
34
|
prompt?: string;
|
|
33
35
|
ttlSeconds?: number;
|
|
34
36
|
};
|
|
@@ -110,6 +112,7 @@ type ResolvedConfig = {
|
|
|
110
112
|
};
|
|
111
113
|
softSuspect: {
|
|
112
114
|
action: SoftSuspectAction;
|
|
115
|
+
mode: SoftSuspectMode;
|
|
113
116
|
prompt: string;
|
|
114
117
|
ttlMs: number;
|
|
115
118
|
};
|
|
@@ -285,6 +288,7 @@ const DEFAULTS = {
|
|
|
285
288
|
handoffLastN: 6,
|
|
286
289
|
handoffMaxChars: 220,
|
|
287
290
|
softSuspectAction: "ask" as SoftSuspectAction,
|
|
291
|
+
softSuspectMode: "strict" as SoftSuspectMode,
|
|
288
292
|
softSuspectPrompt:
|
|
289
293
|
"Potential topic shift detected. Ask one short clarification question to confirm the user's new goal before proceeding.",
|
|
290
294
|
softSuspectTtlSeconds: 120,
|
|
@@ -325,6 +329,8 @@ const STALE_SESSION_STATE_MS = 4 * 60 * 60 * 1000;
|
|
|
325
329
|
const MAX_RECENT_MESSAGE_EVENTS = 20_000;
|
|
326
330
|
const MESSAGE_EVENT_TTL_MS = 5 * 60 * 1000;
|
|
327
331
|
const ROTATION_DEDUPE_MS = 25_000;
|
|
332
|
+
const DEBUG_LOG_TEXT_PREVIEW_CHARS = 180;
|
|
333
|
+
const DEBUG_LOG_TOKEN_PREVIEW_COUNT = 12;
|
|
328
334
|
const PERSISTENCE_SCHEMA_VERSION = 1;
|
|
329
335
|
const PERSISTENCE_FILE_NAME = "runtime-state.v1.json";
|
|
330
336
|
const PERSISTENCE_FLUSH_DEBOUNCE_MS = 1_200;
|
|
@@ -408,6 +414,17 @@ function normalizeSoftSuspectAction(value: unknown): SoftSuspectAction {
|
|
|
408
414
|
return DEFAULTS.softSuspectAction;
|
|
409
415
|
}
|
|
410
416
|
|
|
417
|
+
function normalizeSoftSuspectMode(value: unknown): SoftSuspectMode {
|
|
418
|
+
if (typeof value !== "string") {
|
|
419
|
+
return DEFAULTS.softSuspectMode;
|
|
420
|
+
}
|
|
421
|
+
const normalized = value.trim().toLowerCase();
|
|
422
|
+
if (normalized === "best_effort" || normalized === "strict") {
|
|
423
|
+
return normalized;
|
|
424
|
+
}
|
|
425
|
+
return DEFAULTS.softSuspectMode;
|
|
426
|
+
}
|
|
427
|
+
|
|
411
428
|
function compileRegexList(values: unknown, fallback: readonly string[]): RegExp[] {
|
|
412
429
|
const source = Array.isArray(values) ? values : fallback;
|
|
413
430
|
const out: RegExp[] = [];
|
|
@@ -790,6 +807,7 @@ function resolveConfig(raw: unknown): ResolvedConfig {
|
|
|
790
807
|
},
|
|
791
808
|
softSuspect: {
|
|
792
809
|
action: normalizeSoftSuspectAction(softSuspect.action),
|
|
810
|
+
mode: normalizeSoftSuspectMode(softSuspect.mode),
|
|
793
811
|
prompt:
|
|
794
812
|
typeof softSuspect.prompt === "string" && softSuspect.prompt.trim()
|
|
795
813
|
? softSuspect.prompt.trim()
|
|
@@ -1671,8 +1689,14 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
1671
1689
|
const recentAgentEvents = new Map<string, number>();
|
|
1672
1690
|
const recentRotationBySession = new Map<string, number>();
|
|
1673
1691
|
const pendingSoftSuspectSteeringBySession = new Map<string, number>();
|
|
1692
|
+
const awaitingSoftSuspectReplyBySession = new Map<string, number>();
|
|
1674
1693
|
const sessionWorkQueue = new Map<string, Promise<unknown>>();
|
|
1675
1694
|
|
|
1695
|
+
const clearSoftSuspectSteerState = (sessionKey: string) => {
|
|
1696
|
+
pendingSoftSuspectSteeringBySession.delete(sessionKey);
|
|
1697
|
+
awaitingSoftSuspectReplyBySession.delete(sessionKey);
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1676
1700
|
const persistencePath = (() => {
|
|
1677
1701
|
try {
|
|
1678
1702
|
const stateDir = api.runtime.state.resolveStateDir();
|
|
@@ -1914,8 +1938,9 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
1914
1938
|
}
|
|
1915
1939
|
|
|
1916
1940
|
const tokens = new Set(tokenList);
|
|
1917
|
-
|
|
1918
1941
|
const now = Date.now();
|
|
1942
|
+
const entry: HistoryEntry = { tokens, at: now };
|
|
1943
|
+
|
|
1919
1944
|
const contentHash = hashString(normalizeTextForHash(text));
|
|
1920
1945
|
const lastRotationAt = recentRotationBySession.get(`${sessionKey}:${contentHash}`);
|
|
1921
1946
|
if (typeof lastRotationAt === "number" && now - lastRotationAt < ROTATION_DEDUPE_MS) {
|
|
@@ -1935,10 +1960,41 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
1935
1960
|
} satisfies SessionState);
|
|
1936
1961
|
state.lastSeenAt = now;
|
|
1937
1962
|
|
|
1963
|
+
if (params.source === "agent") {
|
|
1964
|
+
// Outbound agent text helps keep topic context fresh, but it should not
|
|
1965
|
+
// advance soft-suspect confirmation state by itself.
|
|
1966
|
+
state.history = trimHistory([...state.history, entry], cfg.historyWindow);
|
|
1967
|
+
updateTopicCentroid(state, entry.embedding);
|
|
1968
|
+
sessionState.set(sessionKey, state);
|
|
1969
|
+
pruneStateMaps(sessionState);
|
|
1970
|
+
schedulePersistentFlush(false);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
const pendingSoftSteerAt = pendingSoftSuspectSteeringBySession.get(sessionKey);
|
|
1975
|
+
const pendingSoftSteerActive =
|
|
1976
|
+
typeof pendingSoftSteerAt === "number" && now - pendingSoftSteerAt <= cfg.softSuspect.ttlMs;
|
|
1977
|
+
if (typeof pendingSoftSteerAt === "number" && !pendingSoftSteerActive) {
|
|
1978
|
+
pendingSoftSuspectSteeringBySession.delete(sessionKey);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
const awaitingSoftReplyAt = awaitingSoftSuspectReplyBySession.get(sessionKey);
|
|
1982
|
+
const awaitingSoftReplyActive =
|
|
1983
|
+
typeof awaitingSoftReplyAt === "number" && now - awaitingSoftReplyAt <= cfg.softSuspect.ttlMs;
|
|
1984
|
+
if (typeof awaitingSoftReplyAt === "number" && !awaitingSoftReplyActive) {
|
|
1985
|
+
awaitingSoftSuspectReplyBySession.delete(sessionKey);
|
|
1986
|
+
}
|
|
1987
|
+
if (awaitingSoftReplyActive) {
|
|
1988
|
+
awaitingSoftSuspectReplyBySession.delete(sessionKey);
|
|
1989
|
+
if (cfg.debug) {
|
|
1990
|
+
api.logger.debug(`topic-shift-reset: ask-resolved user-reply session=${sessionKey}`);
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1938
1994
|
const baselineTokens = unionTokens(state.history);
|
|
1939
1995
|
const lexical = computeLexicalFeatures({
|
|
1940
1996
|
cfg,
|
|
1941
|
-
entry
|
|
1997
|
+
entry,
|
|
1942
1998
|
tokenList,
|
|
1943
1999
|
baselineTokens,
|
|
1944
2000
|
});
|
|
@@ -1969,12 +2025,14 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
1969
2025
|
}
|
|
1970
2026
|
}
|
|
1971
2027
|
|
|
1972
|
-
|
|
2028
|
+
if (Array.isArray(embedding) && embedding.length > 0) {
|
|
2029
|
+
entry.embedding = embedding;
|
|
2030
|
+
}
|
|
1973
2031
|
const similarity =
|
|
1974
2032
|
entry.embedding && state.topicCentroid
|
|
1975
2033
|
? cosineSimilarity(entry.embedding, state.topicCentroid)
|
|
1976
2034
|
: undefined;
|
|
1977
|
-
|
|
2035
|
+
let decision = classifyMessage({
|
|
1978
2036
|
cfg,
|
|
1979
2037
|
state,
|
|
1980
2038
|
baselineTokenCount: baselineTokens.size,
|
|
@@ -1984,28 +2042,52 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
1984
2042
|
now,
|
|
1985
2043
|
});
|
|
1986
2044
|
|
|
2045
|
+
if (
|
|
2046
|
+
cfg.softSuspect.action === "ask" &&
|
|
2047
|
+
cfg.softSuspect.mode === "strict" &&
|
|
2048
|
+
pendingSoftSteerActive &&
|
|
2049
|
+
decision.kind === "rotate-soft"
|
|
2050
|
+
) {
|
|
2051
|
+
if (cfg.debug) {
|
|
2052
|
+
api.logger.debug(`topic-shift-reset: ask-blocked-waiting-injection session=${sessionKey}`);
|
|
2053
|
+
}
|
|
2054
|
+
decision = {
|
|
2055
|
+
kind: "suspect",
|
|
2056
|
+
reason: "soft-suspect-awaiting-ask",
|
|
2057
|
+
metrics: decision.metrics,
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
|
|
1987
2061
|
if (cfg.debug) {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2062
|
+
const textPreview = truncateText(text, DEBUG_LOG_TEXT_PREVIEW_CHARS);
|
|
2063
|
+
const rawPreview = truncateText(rawText, DEBUG_LOG_TEXT_PREVIEW_CHARS);
|
|
2064
|
+
const debugFields = [
|
|
2065
|
+
`topic-shift-reset: classify`,
|
|
2066
|
+
`source=${params.source}`,
|
|
2067
|
+
`kind=${decision.kind}`,
|
|
2068
|
+
`reason=${decision.reason}`,
|
|
2069
|
+
`session=${sessionKey}`,
|
|
2070
|
+
`score=${decision.metrics.score.toFixed(3)}`,
|
|
2071
|
+
`novelty=${decision.metrics.novelty.toFixed(3)}`,
|
|
2072
|
+
`lex=${decision.metrics.lexicalDistance.toFixed(3)}`,
|
|
2073
|
+
`uniq=${decision.metrics.uniqueTokenRatio.toFixed(3)}`,
|
|
2074
|
+
`entropy=${decision.metrics.entropy.toFixed(3)}`,
|
|
2075
|
+
`sim=${typeof decision.metrics.similarity === "number" ? decision.metrics.similarity.toFixed(3) : "n/a"}`,
|
|
2076
|
+
`embed=${decision.metrics.usedEmbedding ? "1" : "0"}`,
|
|
2077
|
+
`pending=${state.pendingSoftSignals}`,
|
|
2078
|
+
`baseline=${baselineTokens.size}`,
|
|
2079
|
+
`textHash=${contentHash}`,
|
|
2080
|
+
`tokens=${maybeJson(tokenList.slice(0, DEBUG_LOG_TOKEN_PREVIEW_COUNT))}`,
|
|
2081
|
+
`text=${maybeJson(textPreview)}`,
|
|
2082
|
+
];
|
|
2083
|
+
if (rawPreview !== textPreview) {
|
|
2084
|
+
debugFields.push(`raw=${maybeJson(rawPreview)}`);
|
|
2085
|
+
}
|
|
2086
|
+
api.logger.debug(debugFields.join(" "));
|
|
2005
2087
|
}
|
|
2006
2088
|
|
|
2007
2089
|
if (decision.kind === "warmup") {
|
|
2008
|
-
|
|
2090
|
+
clearSoftSuspectSteerState(sessionKey);
|
|
2009
2091
|
state.pendingSoftSignals = 0;
|
|
2010
2092
|
state.pendingEntries = [];
|
|
2011
2093
|
state.history = trimHistory([...state.history, entry], cfg.historyWindow);
|
|
@@ -2017,7 +2099,7 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
2017
2099
|
}
|
|
2018
2100
|
|
|
2019
2101
|
if (decision.kind === "stable") {
|
|
2020
|
-
|
|
2102
|
+
clearSoftSuspectSteerState(sessionKey);
|
|
2021
2103
|
const merged = [...state.history, ...state.pendingEntries, entry];
|
|
2022
2104
|
for (const item of [...state.pendingEntries, entry]) {
|
|
2023
2105
|
updateTopicCentroid(state, item.embedding);
|
|
@@ -2039,6 +2121,9 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
2039
2121
|
Math.max(cfg.softSuspect.ttlMs * 2, 60_000),
|
|
2040
2122
|
MAX_RECENT_MESSAGE_EVENTS,
|
|
2041
2123
|
);
|
|
2124
|
+
if (cfg.debug) {
|
|
2125
|
+
api.logger.debug(`topic-shift-reset: suspect-queued session=${sessionKey}`);
|
|
2126
|
+
}
|
|
2042
2127
|
}
|
|
2043
2128
|
state.pendingSoftSignals += 1;
|
|
2044
2129
|
state.pendingEntries = trimHistory([...state.pendingEntries, entry], cfg.softConsecutiveSignals);
|
|
@@ -2065,7 +2150,7 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
2065
2150
|
});
|
|
2066
2151
|
|
|
2067
2152
|
if (rotated) {
|
|
2068
|
-
|
|
2153
|
+
clearSoftSuspectSteerState(sessionKey);
|
|
2069
2154
|
if (!cfg.dryRun) {
|
|
2070
2155
|
recentRotationBySession.set(`${sessionKey}:${contentHash}`, Date.now());
|
|
2071
2156
|
}
|
|
@@ -2229,15 +2314,36 @@ export default function register(api: OpenClawPluginApi): void {
|
|
|
2229
2314
|
if (!sessionKey) {
|
|
2230
2315
|
return;
|
|
2231
2316
|
}
|
|
2317
|
+
const now = Date.now();
|
|
2318
|
+
const awaitingAt = awaitingSoftSuspectReplyBySession.get(sessionKey);
|
|
2319
|
+
if (typeof awaitingAt === "number") {
|
|
2320
|
+
if (now - awaitingAt > cfg.softSuspect.ttlMs) {
|
|
2321
|
+
awaitingSoftSuspectReplyBySession.delete(sessionKey);
|
|
2322
|
+
}
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2232
2326
|
const seenAt = pendingSoftSuspectSteeringBySession.get(sessionKey);
|
|
2233
2327
|
if (typeof seenAt !== "number") {
|
|
2234
2328
|
return;
|
|
2235
2329
|
}
|
|
2236
|
-
if (
|
|
2330
|
+
if (now - seenAt > cfg.softSuspect.ttlMs) {
|
|
2237
2331
|
pendingSoftSuspectSteeringBySession.delete(sessionKey);
|
|
2238
2332
|
return;
|
|
2239
2333
|
}
|
|
2334
|
+
|
|
2335
|
+
if (cfg.softSuspect.mode === "strict") {
|
|
2336
|
+
awaitingSoftSuspectReplyBySession.set(sessionKey, now);
|
|
2337
|
+
pruneRecentMap(
|
|
2338
|
+
awaitingSoftSuspectReplyBySession,
|
|
2339
|
+
Math.max(cfg.softSuspect.ttlMs * 2, 60_000),
|
|
2340
|
+
MAX_RECENT_MESSAGE_EVENTS,
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2240
2343
|
pendingSoftSuspectSteeringBySession.delete(sessionKey);
|
|
2344
|
+
if (cfg.debug) {
|
|
2345
|
+
api.logger.debug(`topic-shift-reset: ask-injected session=${sessionKey}`);
|
|
2346
|
+
}
|
|
2241
2347
|
return { prependContext: cfg.softSuspect.prompt };
|
|
2242
2348
|
});
|
|
2243
2349
|
|