agenr 0.8.21 → 0.8.23
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/CHANGELOG.md +33 -0
- package/dist/openclaw-plugin/index.d.ts +0 -1
- package/dist/openclaw-plugin/index.js +193 -131
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.22] - 2026-02-23
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- feat(openclaw-plugin): replace thin-prompt/stash session-start recall with three-phase
|
|
7
|
+
cross-session context injection (issue #205)
|
|
8
|
+
- Phase 1A (always): reads last 7 user+assistant turns from most recently modified
|
|
9
|
+
session JSONL file in ~/.openclaw/agents/<agentId>/sessions/
|
|
10
|
+
- Phase 1B (always): runs agenr recall --browse --since 1d --limit 20, picks up
|
|
11
|
+
importance:10 handoff entry written at /new time
|
|
12
|
+
- Phase 2 (conditional): semantic recall seeded from Phase 1A turns + first user
|
|
13
|
+
message if >= 5 words; results deduplicated against Phase 1B by entry id
|
|
14
|
+
- Handoff entries retired after first use (one-time read)
|
|
15
|
+
- feat(openclaw-plugin): added findPreviousSessionFile, extractRecentTurns,
|
|
16
|
+
buildSemanticSeed to src/openclaw-plugin/session-query.ts
|
|
17
|
+
- feat(openclaw-plugin): findPreviousSessionFile uses parallel stat() calls for
|
|
18
|
+
performance on large sessions dirs
|
|
19
|
+
- feat(openclaw-plugin): sessionsDir configurable via AgenrPluginConfig.sessionsDir;
|
|
20
|
+
defaults to ~/.openclaw/agents/<agentId>/sessions using ctx.agentId with "main"
|
|
21
|
+
fallback
|
|
22
|
+
- feat(openclaw-plugin): RunRecallOptions extended with limit?: number to support
|
|
23
|
+
--limit flag in browse recall
|
|
24
|
+
- refactor(openclaw-plugin): removed isThinPrompt, resolveSessionQuery,
|
|
25
|
+
sessionTopicStash, stashSessionTopic, shouldStashTopic, sweepInterval, clearStash,
|
|
26
|
+
readLatestArchivedUserMessages
|
|
27
|
+
|
|
28
|
+
### Tests
|
|
29
|
+
- test(openclaw-plugin): added unit tests for findPreviousSessionFile, extractRecentTurns,
|
|
30
|
+
buildSemanticSeed in session-query.test.ts
|
|
31
|
+
- test(openclaw-plugin): added integration tests for three-phase before_prompt_build flow,
|
|
32
|
+
Phase 2 deduplication, and isFirstInSession guard in index.test.ts
|
|
33
|
+
- test(openclaw-plugin): added second-message guard test (isFirstInSession prevents
|
|
34
|
+
re-injection on subsequent messages in same session)
|
|
35
|
+
|
|
3
36
|
## [0.8.19] - 2026-02-23
|
|
4
37
|
|
|
5
38
|
### Changed
|
|
@@ -8,6 +8,8 @@ import {
|
|
|
8
8
|
|
|
9
9
|
// src/openclaw-plugin/index.ts
|
|
10
10
|
import { Type } from "@sinclair/typebox";
|
|
11
|
+
import os from "os";
|
|
12
|
+
import path3 from "path";
|
|
11
13
|
|
|
12
14
|
// src/openclaw-plugin/recall.ts
|
|
13
15
|
import { spawn } from "child_process";
|
|
@@ -45,6 +47,9 @@ async function runRecall(agenrPath, budget, project, query, options) {
|
|
|
45
47
|
}
|
|
46
48
|
const isBrowse = options?.context === "browse";
|
|
47
49
|
const args = isBrowse ? ["recall", "--browse", "--since", options?.since ?? "1d", "--json"] : ["recall", "--context", "session-start", "--budget", String(budget), "--json"];
|
|
50
|
+
if (isBrowse && options?.limit !== void 0) {
|
|
51
|
+
args.push("--limit", String(options.limit));
|
|
52
|
+
}
|
|
48
53
|
if (project) {
|
|
49
54
|
args.push("--project", project);
|
|
50
55
|
}
|
|
@@ -143,15 +148,15 @@ function formatRecallAsMarkdown(result) {
|
|
|
143
148
|
}
|
|
144
149
|
|
|
145
150
|
// src/openclaw-plugin/session-query.ts
|
|
146
|
-
import {
|
|
151
|
+
import { createReadStream } from "fs";
|
|
152
|
+
import { readdir, stat } from "fs/promises";
|
|
147
153
|
import path2 from "path";
|
|
148
|
-
|
|
149
|
-
var SESSION_TOPIC_MIN_LENGTH = 40;
|
|
150
|
-
var ARCHIVED_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
151
|
-
var SESSION_QUERY_LOOKBACK = 3;
|
|
154
|
+
import { createInterface } from "readline";
|
|
152
155
|
var EXCHANGE_TEXT_MAX_CHARS = 200;
|
|
153
156
|
var EXCHANGE_USER_TURN_LIMIT = 5;
|
|
154
|
-
var
|
|
157
|
+
var RECENT_TURN_MAX_CHARS = 150;
|
|
158
|
+
var SEMANTIC_SEED_PREVIOUS_TURNS_MAX_CHARS = 400;
|
|
159
|
+
var DEFAULT_RECENT_TURN_LIMIT = 7;
|
|
155
160
|
function isRecord(value) {
|
|
156
161
|
return typeof value === "object" && value !== null;
|
|
157
162
|
}
|
|
@@ -209,13 +214,8 @@ function extractTextFromAssistantMessage(message) {
|
|
|
209
214
|
}
|
|
210
215
|
return textParts.join(" ").trim();
|
|
211
216
|
}
|
|
212
|
-
function truncateMessageText(text) {
|
|
213
|
-
return text.length >
|
|
214
|
-
}
|
|
215
|
-
var OPENCLAW_BARE_RESET_PREFIX = "a new session was started via /new";
|
|
216
|
-
function isThinPrompt(prompt) {
|
|
217
|
-
const trimmed = prompt.trim().toLowerCase();
|
|
218
|
-
return trimmed === "" || trimmed === "/new" || trimmed === "/reset" || trimmed.startsWith(OPENCLAW_BARE_RESET_PREFIX);
|
|
217
|
+
function truncateMessageText(text, maxChars = EXCHANGE_TEXT_MAX_CHARS) {
|
|
218
|
+
return text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
219
219
|
}
|
|
220
220
|
function stripPromptMetadata(raw) {
|
|
221
221
|
if (!raw) {
|
|
@@ -236,25 +236,6 @@ function stripPromptMetadata(raw) {
|
|
|
236
236
|
return "";
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
function extractLastUserText(messages) {
|
|
240
|
-
try {
|
|
241
|
-
const collected = [];
|
|
242
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
243
|
-
const extracted = extractTextFromUserMessage(messages[index]);
|
|
244
|
-
if (!extracted) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
collected.push(extracted);
|
|
248
|
-
if (collected.length >= SESSION_QUERY_LOOKBACK) {
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const joined = collected.reverse().join(" ").trim();
|
|
253
|
-
return joined || "";
|
|
254
|
-
} catch {
|
|
255
|
-
return "";
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
239
|
function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT) {
|
|
259
240
|
try {
|
|
260
241
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
@@ -292,74 +273,124 @@ function extractLastExchangeText(messages, maxTurns = EXCHANGE_USER_TURN_LIMIT)
|
|
|
292
273
|
return "";
|
|
293
274
|
}
|
|
294
275
|
}
|
|
295
|
-
function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
276
|
+
async function findPreviousSessionFile(sessionsDir, currentSessionId, logger) {
|
|
277
|
+
try {
|
|
278
|
+
const normalizedSessionId = currentSessionId?.trim();
|
|
279
|
+
const currentSessionFileName = normalizedSessionId ? `${normalizedSessionId}.jsonl` : void 0;
|
|
280
|
+
const entries = await readdir(sessionsDir, { withFileTypes: true });
|
|
281
|
+
const candidatePaths = [];
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
if (!entry.isFile()) {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (!entry.name.endsWith(".jsonl")) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (entry.name.includes(".deleted.")) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
if (currentSessionFileName && entry.name === currentSessionFileName) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
candidatePaths.push(path2.join(sessionsDir, entry.name));
|
|
307
296
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
var sweepInterval = setInterval(sweepExpiredStash, 5 * 60 * 1e3);
|
|
311
|
-
if (sweepInterval !== void 0 && typeof sweepInterval.unref === "function") {
|
|
312
|
-
sweepInterval.unref();
|
|
313
|
-
}
|
|
314
|
-
function stripResetPrefix(prompt) {
|
|
315
|
-
const lower = prompt.toLowerCase();
|
|
316
|
-
for (const cmd of ["/new", "/reset"]) {
|
|
317
|
-
if (lower.startsWith(cmd + " ")) {
|
|
318
|
-
return prompt.slice(cmd.length).trim();
|
|
297
|
+
if (candidatePaths.length === 0) {
|
|
298
|
+
return null;
|
|
319
299
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
300
|
+
const statResults = await Promise.all(
|
|
301
|
+
candidatePaths.map(async (filePath) => {
|
|
302
|
+
try {
|
|
303
|
+
const fileStats = await stat(filePath);
|
|
304
|
+
return { filePath, mtimeMs: fileStats.mtimeMs };
|
|
305
|
+
} catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
const candidates = statResults.filter(
|
|
311
|
+
(result) => result !== null
|
|
312
|
+
);
|
|
313
|
+
if (candidates.length === 0) {
|
|
314
|
+
return null;
|
|
333
315
|
}
|
|
316
|
+
candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
317
|
+
return candidates[0]?.filePath ?? null;
|
|
318
|
+
} catch (err) {
|
|
319
|
+
logger?.debug?.(
|
|
320
|
+
`[agenr] findPreviousSessionFile: failed to read sessions dir "${sessionsDir}": ${err instanceof Error ? err.message : String(err)}`
|
|
321
|
+
);
|
|
322
|
+
return null;
|
|
334
323
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (!
|
|
339
|
-
return
|
|
324
|
+
}
|
|
325
|
+
async function extractRecentTurns(filePath, maxTurns = DEFAULT_RECENT_TURN_LIMIT) {
|
|
326
|
+
try {
|
|
327
|
+
if (!filePath) {
|
|
328
|
+
return "";
|
|
340
329
|
}
|
|
341
|
-
|
|
342
|
-
|
|
330
|
+
const parsedMaxTurns = Number.isFinite(maxTurns) ? Math.max(0, Math.trunc(maxTurns)) : 0;
|
|
331
|
+
if (parsedMaxTurns === 0) {
|
|
332
|
+
return "";
|
|
343
333
|
}
|
|
344
|
-
|
|
334
|
+
const ring = [];
|
|
335
|
+
await new Promise((resolve, reject) => {
|
|
336
|
+
const rl = createInterface({
|
|
337
|
+
input: createReadStream(filePath, { encoding: "utf8" }),
|
|
338
|
+
crlfDelay: Infinity
|
|
339
|
+
});
|
|
340
|
+
rl.on("line", (line) => {
|
|
341
|
+
const trimmed = line.trim();
|
|
342
|
+
if (!trimmed) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
let record;
|
|
346
|
+
try {
|
|
347
|
+
record = JSON.parse(trimmed);
|
|
348
|
+
} catch {
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!isRecord(record) || record["type"] !== "message") {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const message = record["message"];
|
|
355
|
+
const userText = extractTextFromUserMessage(message);
|
|
356
|
+
if (userText) {
|
|
357
|
+
ring.push(`U: ${truncateMessageText(userText, RECENT_TURN_MAX_CHARS)}`);
|
|
358
|
+
if (ring.length > parsedMaxTurns) {
|
|
359
|
+
ring.shift();
|
|
360
|
+
}
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
const assistantText = extractTextFromAssistantMessage(message);
|
|
364
|
+
if (assistantText) {
|
|
365
|
+
ring.push(`A: ${truncateMessageText(assistantText, RECENT_TURN_MAX_CHARS)}`);
|
|
366
|
+
if (ring.length > parsedMaxTurns) {
|
|
367
|
+
ring.shift();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
rl.on("close", resolve);
|
|
372
|
+
rl.on("error", reject);
|
|
373
|
+
});
|
|
374
|
+
return ring.join(" | ");
|
|
375
|
+
} catch {
|
|
376
|
+
return "";
|
|
345
377
|
}
|
|
346
|
-
return stashedText;
|
|
347
378
|
}
|
|
348
|
-
function
|
|
349
|
-
|
|
350
|
-
|
|
379
|
+
function buildSemanticSeed(previousTurns, firstUserMessage) {
|
|
380
|
+
const stripped = stripPromptMetadata(firstUserMessage).trim();
|
|
381
|
+
const wordCount = stripped.split(/\s+/).filter(Boolean).length;
|
|
382
|
+
const messageHasSignal = wordCount >= 5;
|
|
383
|
+
const truncatedTurns = previousTurns.slice(0, SEMANTIC_SEED_PREVIOUS_TURNS_MAX_CHARS);
|
|
384
|
+
if (truncatedTurns && messageHasSignal) {
|
|
385
|
+
return `${truncatedTurns} ${stripped}`;
|
|
351
386
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
function clearStash() {
|
|
358
|
-
sessionTopicStash.clear();
|
|
359
|
-
if (sweepInterval !== void 0) {
|
|
360
|
-
clearInterval(sweepInterval);
|
|
361
|
-
sweepInterval = void 0;
|
|
387
|
+
if (truncatedTurns) {
|
|
388
|
+
return truncatedTurns;
|
|
389
|
+
}
|
|
390
|
+
if (messageHasSignal) {
|
|
391
|
+
return stripped;
|
|
362
392
|
}
|
|
393
|
+
return void 0;
|
|
363
394
|
}
|
|
364
395
|
|
|
365
396
|
// src/db/signals.ts
|
|
@@ -967,51 +998,88 @@ var plugin = {
|
|
|
967
998
|
const agenrPath = resolveAgenrPath(config);
|
|
968
999
|
const budget = resolveBudget(config);
|
|
969
1000
|
const project = config?.project?.trim() || void 0;
|
|
970
|
-
const
|
|
971
|
-
const
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
}
|
|
978
|
-
recallResult = await runRecall(agenrPath, budget, project, void 0, {
|
|
1001
|
+
const agentId = ctx.agentId?.trim() || "main";
|
|
1002
|
+
const sessionsDir = config?.sessionsDir ?? path3.join(os.homedir(), `.openclaw/agents/${agentId}/sessions`);
|
|
1003
|
+
const [previousTurns, browseResult] = await Promise.all([
|
|
1004
|
+
findPreviousSessionFile(sessionsDir, ctx.sessionId, api.logger).then(
|
|
1005
|
+
(file) => file ? extractRecentTurns(file) : Promise.resolve("")
|
|
1006
|
+
),
|
|
1007
|
+
runRecall(agenrPath, budget, project, void 0, {
|
|
979
1008
|
context: "browse",
|
|
980
|
-
since: "1d"
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1009
|
+
since: "1d",
|
|
1010
|
+
limit: 20
|
|
1011
|
+
})
|
|
1012
|
+
]);
|
|
1013
|
+
const seed = buildSemanticSeed(previousTurns, event.prompt ?? "");
|
|
1014
|
+
let semanticResult = null;
|
|
1015
|
+
if (seed) {
|
|
1016
|
+
const browseIds = /* @__PURE__ */ new Set();
|
|
1017
|
+
for (const item of browseResult?.results ?? []) {
|
|
1018
|
+
const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
|
|
1019
|
+
if (id) {
|
|
1020
|
+
browseIds.add(id);
|
|
1021
|
+
}
|
|
989
1022
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
if (!
|
|
995
|
-
|
|
1023
|
+
const rawSemantic = await runRecall(agenrPath, budget, project, seed);
|
|
1024
|
+
if (rawSemantic) {
|
|
1025
|
+
rawSemantic.results = rawSemantic.results.filter((item) => {
|
|
1026
|
+
const id = typeof item.entry?.id === "string" && item.entry.id.trim() ? item.entry.id.trim() : null;
|
|
1027
|
+
if (!id) {
|
|
1028
|
+
return true;
|
|
996
1029
|
}
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1030
|
+
return !browseIds.has(id);
|
|
1031
|
+
});
|
|
1032
|
+
semanticResult = rawSemantic.results.length > 0 ? rawSemantic : null;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (browseResult) {
|
|
1036
|
+
const retirePromises = [];
|
|
1037
|
+
for (const item of browseResult.results) {
|
|
1038
|
+
const entry = item.entry;
|
|
1039
|
+
const subject = typeof entry.subject === "string" ? entry.subject : "";
|
|
1040
|
+
if (!subject.toLowerCase().startsWith("session handoff")) {
|
|
1041
|
+
continue;
|
|
1042
|
+
}
|
|
1043
|
+
const entryId = typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : null;
|
|
1044
|
+
if (entryId) {
|
|
1045
|
+
retirePromises.push(
|
|
1046
|
+
runRetireTool(agenrPath, {
|
|
1000
1047
|
entry_id: entryId,
|
|
1001
1048
|
reason: "consumed at session start"
|
|
1002
|
-
}).catch((err) => {
|
|
1049
|
+
}).then(() => void 0).catch((err) => {
|
|
1003
1050
|
api.logger.debug?.(
|
|
1004
1051
|
`[agenr] session-start: retire handoff ${entryId} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1005
1052
|
);
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1053
|
+
})
|
|
1054
|
+
);
|
|
1055
|
+
} else {
|
|
1056
|
+
api.logger.debug?.(
|
|
1057
|
+
"[agenr] session-start: handoff entry missing id, skipping retire"
|
|
1058
|
+
);
|
|
1012
1059
|
}
|
|
1013
1060
|
}
|
|
1061
|
+
await Promise.allSettled(retirePromises);
|
|
1062
|
+
}
|
|
1063
|
+
const sections = [];
|
|
1064
|
+
if (previousTurns.trim()) {
|
|
1065
|
+
sections.push(`## Recent session
|
|
1066
|
+
${previousTurns.trim()}`);
|
|
1067
|
+
}
|
|
1068
|
+
if (browseResult) {
|
|
1069
|
+
const formatted = formatRecallAsMarkdown(browseResult);
|
|
1070
|
+
if (formatted.trim()) {
|
|
1071
|
+
sections.push(`## Recent memory
|
|
1072
|
+
${formatted.trim()}`);
|
|
1073
|
+
}
|
|
1014
1074
|
}
|
|
1075
|
+
if (semanticResult) {
|
|
1076
|
+
const formatted = formatRecallAsMarkdown(semanticResult);
|
|
1077
|
+
if (formatted.trim()) {
|
|
1078
|
+
sections.push(`## Relevant memory
|
|
1079
|
+
${formatted.trim()}`);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
markdown = sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
1015
1083
|
}
|
|
1016
1084
|
let signal;
|
|
1017
1085
|
if (config?.signalsEnabled !== false) {
|
|
@@ -1054,10 +1122,6 @@ var plugin = {
|
|
|
1054
1122
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
1055
1123
|
return;
|
|
1056
1124
|
}
|
|
1057
|
-
const lastUserText = extractLastUserText(messages);
|
|
1058
|
-
if (shouldStashTopic(lastUserText)) {
|
|
1059
|
-
stashSessionTopic(sessionKey, lastUserText);
|
|
1060
|
-
}
|
|
1061
1125
|
const handoffText = extractLastExchangeText(messages);
|
|
1062
1126
|
if (handoffText) {
|
|
1063
1127
|
const agenrPath = resolveAgenrPath(config);
|
|
@@ -1086,7 +1150,7 @@ var plugin = {
|
|
|
1086
1150
|
}
|
|
1087
1151
|
} catch (err) {
|
|
1088
1152
|
api.logger.warn(
|
|
1089
|
-
`agenr plugin before_reset
|
|
1153
|
+
`agenr plugin before_reset failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1090
1154
|
);
|
|
1091
1155
|
}
|
|
1092
1156
|
});
|
|
@@ -1242,9 +1306,7 @@ var plugin = {
|
|
|
1242
1306
|
}
|
|
1243
1307
|
};
|
|
1244
1308
|
var __testing = {
|
|
1245
|
-
SESSION_TOPIC_TTL_MS,
|
|
1246
1309
|
clearState() {
|
|
1247
|
-
clearStash();
|
|
1248
1310
|
seenSessions.clear();
|
|
1249
1311
|
sessionSignalState.clear();
|
|
1250
1312
|
}
|