Skip to content

Commit b434dd5

Browse files
committed
perf(inbound): trim cold startup import graph
1 parent 0f6dbb4 commit b434dd5

30 files changed

+633
-391
lines changed

extensions/discord/src/session-key-normalization.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1-
import { normalizeChatType } from "openclaw/plugin-sdk/account-resolution";
2-
import type { MsgContext } from "openclaw/plugin-sdk/reply-runtime";
1+
type DiscordSessionKeyContext = {
2+
ChatType?: string;
3+
From?: string;
4+
SenderId?: string;
5+
};
6+
7+
function normalizeDiscordChatType(raw?: string): "direct" | "group" | "channel" | undefined {
8+
const normalized = (raw ?? "").trim().toLowerCase();
9+
if (!normalized) {
10+
return undefined;
11+
}
12+
if (normalized === "dm") {
13+
return "direct";
14+
}
15+
if (normalized === "group" || normalized === "channel" || normalized === "direct") {
16+
return normalized;
17+
}
18+
return undefined;
19+
}
320

421
export function normalizeExplicitDiscordSessionKey(
522
sessionKey: string,
6-
ctx: Pick<MsgContext, "ChatType" | "From" | "SenderId">,
23+
ctx: DiscordSessionKeyContext,
724
): string {
825
let normalized = sessionKey.trim().toLowerCase();
9-
if (normalizeChatType(ctx.ChatType) !== "direct") {
26+
if (normalizeDiscordChatType(ctx.ChatType) !== "direct") {
1027
return normalized;
1128
}
1229

src/agents/context-cache.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const MODEL_CONTEXT_TOKEN_CACHE = new Map<string, number>();
2+
3+
export function lookupCachedContextTokens(modelId?: string): number | undefined {
4+
if (!modelId) {
5+
return undefined;
6+
}
7+
return MODEL_CONTEXT_TOKEN_CACHE.get(modelId);
8+
}

src/agents/context.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { OpenClawConfig } from "../config/config.js";
77
import { computeBackoff, type BackoffPolicy } from "../infra/backoff.js";
88
import { consumeRootOptionToken, FLAG_TERMINATOR } from "../infra/cli-root-options.js";
99
import { resolveOpenClawAgentDir } from "./agent-paths.js";
10+
import { lookupCachedContextTokens, MODEL_CONTEXT_TOKEN_CACHE } from "./context-cache.js";
1011
import { normalizeProviderId } from "./model-selection.js";
1112

1213
type ModelEntry = { id: string; contextWindow?: number };
@@ -78,7 +79,6 @@ export function applyConfiguredContextWindows(params: {
7879
}
7980
}
8081

81-
const MODEL_CACHE = new Map<string, number>();
8282
let loadPromise: Promise<void> | null = null;
8383
let configuredConfig: OpenClawConfig | undefined;
8484
let configLoadFailures = 0;
@@ -169,7 +169,7 @@ function primeConfiguredContextWindows(): OpenClawConfig | undefined {
169169
try {
170170
const cfg = loadConfig();
171171
applyConfiguredContextWindows({
172-
cache: MODEL_CACHE,
172+
cache: MODEL_CONTEXT_TOKEN_CACHE,
173173
modelsConfig: cfg.models as ModelsConfig | undefined,
174174
});
175175
configuredConfig = cfg;
@@ -213,15 +213,15 @@ function ensureContextWindowCacheLoaded(): Promise<void> {
213213
? modelRegistry.getAvailable()
214214
: modelRegistry.getAll();
215215
applyDiscoveredContextWindows({
216-
cache: MODEL_CACHE,
216+
cache: MODEL_CONTEXT_TOKEN_CACHE,
217217
models,
218218
});
219219
} catch {
220220
// If model discovery fails, continue with config overrides only.
221221
}
222222

223223
applyConfiguredContextWindows({
224-
cache: MODEL_CACHE,
224+
cache: MODEL_CONTEXT_TOKEN_CACHE,
225225
modelsConfig: cfg.models as ModelsConfig | undefined,
226226
});
227227
})().catch(() => {
@@ -241,7 +241,7 @@ export function lookupContextTokens(
241241
if (options?.allowAsyncLoad !== false) {
242242
void ensureContextWindowCacheLoaded();
243243
}
244-
return MODEL_CACHE.get(modelId);
244+
return lookupCachedContextTokens(modelId);
245245
}
246246

247247
if (shouldEagerWarmContextWindowCache()) {
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { createOpenClawTools } from "./openclaw-tools.js";

src/auto-reply/command-detection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
listChatCommandsForConfig,
66
normalizeCommandBody,
77
} from "./commands-registry.js";
8-
import { isAbortTrigger } from "./reply/abort.js";
8+
import { isAbortTrigger } from "./reply/abort-primitives.js";
99

1010
export function hasControlCommand(
1111
text?: string,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { updateSessionStore } from "../../config/sessions/store.js";
2+
import type { SessionEntry } from "../../config/sessions/types.js";
3+
import { applyAbortCutoffToSessionEntry, hasAbortCutoff } from "./abort-cutoff.js";
4+
5+
export async function clearAbortCutoffInSessionRuntime(params: {
6+
sessionEntry?: SessionEntry;
7+
sessionStore?: Record<string, SessionEntry>;
8+
sessionKey?: string;
9+
storePath?: string;
10+
}): Promise<boolean> {
11+
const { sessionEntry, sessionStore, sessionKey, storePath } = params;
12+
if (!sessionEntry || !sessionStore || !sessionKey || !hasAbortCutoff(sessionEntry)) {
13+
return false;
14+
}
15+
16+
applyAbortCutoffToSessionEntry(sessionEntry, undefined);
17+
sessionEntry.updatedAt = Date.now();
18+
sessionStore[sessionKey] = sessionEntry;
19+
20+
if (storePath) {
21+
await updateSessionStore(storePath, (store) => {
22+
const existing = store[sessionKey] ?? sessionEntry;
23+
if (!existing) {
24+
return;
25+
}
26+
applyAbortCutoffToSessionEntry(existing, undefined);
27+
existing.updatedAt = Date.now();
28+
store[sessionKey] = existing;
29+
});
30+
}
31+
32+
return true;
33+
}

src/auto-reply/reply/abort-cutoff.ts

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { SessionEntry } from "../../config/sessions.js";
2-
import { updateSessionStore } from "../../config/sessions.js";
1+
import type { SessionEntry } from "../../config/sessions/types.js";
32
import type { MsgContext } from "../templating.js";
43

54
export type AbortCutoff = {
@@ -51,36 +50,6 @@ export function applyAbortCutoffToSessionEntry(
5150
entry.abortCutoffTimestamp = cutoff?.timestamp;
5251
}
5352

54-
export async function clearAbortCutoffInSession(params: {
55-
sessionEntry?: SessionEntry;
56-
sessionStore?: Record<string, SessionEntry>;
57-
sessionKey?: string;
58-
storePath?: string;
59-
}): Promise<boolean> {
60-
const { sessionEntry, sessionStore, sessionKey, storePath } = params;
61-
if (!sessionEntry || !sessionStore || !sessionKey || !hasAbortCutoff(sessionEntry)) {
62-
return false;
63-
}
64-
65-
applyAbortCutoffToSessionEntry(sessionEntry, undefined);
66-
sessionEntry.updatedAt = Date.now();
67-
sessionStore[sessionKey] = sessionEntry;
68-
69-
if (storePath) {
70-
await updateSessionStore(storePath, (store) => {
71-
const existing = store[sessionKey] ?? sessionEntry;
72-
if (!existing) {
73-
return;
74-
}
75-
applyAbortCutoffToSessionEntry(existing, undefined);
76-
existing.updatedAt = Date.now();
77-
store[sessionKey] = existing;
78-
});
79-
}
80-
81-
return true;
82-
}
83-
8453
function toNumericMessageSid(value: string | undefined): bigint | undefined {
8554
const trimmed = value?.trim();
8655
if (!trimmed || !/^\d+$/.test(trimmed)) {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { normalizeCommandBody, type CommandNormalizeOptions } from "../commands-registry.js";
2+
3+
const ABORT_TRIGGERS = new Set([
4+
"stop",
5+
"esc",
6+
"abort",
7+
"wait",
8+
"exit",
9+
"interrupt",
10+
"detente",
11+
"deten",
12+
"detén",
13+
"arrete",
14+
"arrête",
15+
"停止",
16+
"やめて",
17+
"止めて",
18+
"रुको",
19+
"توقف",
20+
"стоп",
21+
"остановись",
22+
"останови",
23+
"остановить",
24+
"прекрати",
25+
"halt",
26+
"anhalten",
27+
"aufhören",
28+
"hoer auf",
29+
"stopp",
30+
"pare",
31+
"stop openclaw",
32+
"openclaw stop",
33+
"stop action",
34+
"stop current action",
35+
"stop run",
36+
"stop current run",
37+
"stop agent",
38+
"stop the agent",
39+
"stop don't do anything",
40+
"stop dont do anything",
41+
"stop do not do anything",
42+
"stop doing anything",
43+
"do not do that",
44+
"please stop",
45+
"stop please",
46+
]);
47+
const ABORT_MEMORY = new Map<string, boolean>();
48+
const ABORT_MEMORY_MAX = 2000;
49+
const TRAILING_ABORT_PUNCTUATION_RE = /[.!?,;:'")\]}]+$/u;
50+
51+
function normalizeAbortTriggerText(text: string): string {
52+
return text
53+
.trim()
54+
.toLowerCase()
55+
.replace(/[`]/g, "'")
56+
.replace(/\s+/g, " ")
57+
.replace(TRAILING_ABORT_PUNCTUATION_RE, "")
58+
.trim();
59+
}
60+
61+
export function isAbortTrigger(text?: string): boolean {
62+
if (!text) {
63+
return false;
64+
}
65+
const normalized = normalizeAbortTriggerText(text);
66+
return ABORT_TRIGGERS.has(normalized);
67+
}
68+
69+
export function isAbortRequestText(text?: string, options?: CommandNormalizeOptions): boolean {
70+
if (!text) {
71+
return false;
72+
}
73+
const normalized = normalizeCommandBody(text, options).trim();
74+
if (!normalized) {
75+
return false;
76+
}
77+
const normalizedLower = normalized.toLowerCase();
78+
return (
79+
normalizedLower === "/stop" ||
80+
normalizeAbortTriggerText(normalizedLower) === "/stop" ||
81+
isAbortTrigger(normalizedLower)
82+
);
83+
}
84+
85+
export function getAbortMemory(key: string): boolean | undefined {
86+
const normalized = key.trim();
87+
if (!normalized) {
88+
return undefined;
89+
}
90+
return ABORT_MEMORY.get(normalized);
91+
}
92+
93+
function pruneAbortMemory(): void {
94+
if (ABORT_MEMORY.size <= ABORT_MEMORY_MAX) {
95+
return;
96+
}
97+
const excess = ABORT_MEMORY.size - ABORT_MEMORY_MAX;
98+
let removed = 0;
99+
for (const entryKey of ABORT_MEMORY.keys()) {
100+
ABORT_MEMORY.delete(entryKey);
101+
removed += 1;
102+
if (removed >= excess) {
103+
break;
104+
}
105+
}
106+
}
107+
108+
export function setAbortMemory(key: string, value: boolean): void {
109+
const normalized = key.trim();
110+
if (!normalized) {
111+
return;
112+
}
113+
if (!value) {
114+
ABORT_MEMORY.delete(normalized);
115+
return;
116+
}
117+
if (ABORT_MEMORY.has(normalized)) {
118+
ABORT_MEMORY.delete(normalized);
119+
}
120+
ABORT_MEMORY.set(normalized, true);
121+
pruneAbortMemory();
122+
}
123+
124+
export function getAbortMemorySizeForTest(): number {
125+
return ABORT_MEMORY.size;
126+
}
127+
128+
export function resetAbortMemoryForTest(): void {
129+
ABORT_MEMORY.clear();
130+
}

0 commit comments

Comments
 (0)