Skip to content

Commit 2e62d2e

Browse files
authored
Merge branch 'main' into fix/heartbeat-clear-pending-final-delivery
2 parents 6154a7f + f90b8cf commit 2e62d2e

14 files changed

Lines changed: 196 additions & 88 deletions

extensions/memory-core/src/memory/qmd-manager.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,8 @@ import {
166166
requireNodeSqlite,
167167
resolveMemoryBackendConfig,
168168
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
169-
import { QmdMemoryManager } from "./qmd-manager.js";
169+
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
170+
import { QmdMemoryManager, resolveQmdMcporterSearchProcessTimeoutMs } from "./qmd-manager.js";
170171

171172
const spawnMock = mockedSpawn as unknown as Mock;
172173
const originalPath = process.env.PATH;
@@ -226,6 +227,17 @@ describe("QmdMemoryManager", () => {
226227
expect(mockMessages(mock).join("\n")).not.toContain(text);
227228
}
228229

230+
it("caps mcporter search process timeout grace", () => {
231+
expect(resolveQmdMcporterSearchProcessTimeoutMs(1_000)).toBe(5_000);
232+
expect(resolveQmdMcporterSearchProcessTimeoutMs(10_000)).toBe(12_000);
233+
expect(resolveQmdMcporterSearchProcessTimeoutMs(Number.MAX_SAFE_INTEGER)).toBe(
234+
MAX_TIMER_TIMEOUT_MS,
235+
);
236+
expect(resolveQmdMcporterSearchProcessTimeoutMs(MAX_TIMER_TIMEOUT_MS - 100)).toBe(
237+
MAX_TIMER_TIMEOUT_MS,
238+
);
239+
});
240+
229241
async function expectPathMissing(targetPath: string): Promise<void> {
230242
try {
231243
await fs.lstat(targetPath);

extensions/memory-core/src/memory/qmd-manager.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
type ResolvedQmdConfig,
5050
type ResolvedQmdMcporterConfig,
5151
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
52+
import { clampTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
5253
import {
5354
localeLowercasePreservingWhitespace,
5455
normalizeLowercaseStringOrEmpty,
@@ -199,6 +200,10 @@ function resolveQmdEmbedLockOptions(embedTimeoutMs: number) {
199200
};
200201
}
201202

203+
export function resolveQmdMcporterSearchProcessTimeoutMs(timeoutMs: number): number {
204+
return Math.max(clampTimerTimeoutMs(timeoutMs + 2_000) ?? 1, 5_000);
205+
}
206+
202207
function shouldIgnoreMemoryWatchPath(watchPath: string): boolean {
203208
const normalized = path.normalize(watchPath);
204209
const parts = normalized
@@ -2130,7 +2135,7 @@ export class QmdMemoryManager implements MemorySearchManager {
21302135
"--timeout",
21312136
String(Math.max(0, params.timeoutMs)),
21322137
],
2133-
{ timeoutMs: Math.max(params.timeoutMs + 2_000, 5_000) },
2138+
{ timeoutMs: resolveQmdMcporterSearchProcessTimeoutMs(params.timeoutMs) },
21342139
);
21352140
// If we got here with the v2 "query" tool, confirm v2 for future calls.
21362141
if (useUnifiedQueryTool && this.qmdMcpToolVersion === null) {

src/agents/openclaw-tools.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { selectApplicableRuntimeConfig } from "../config/config.js";
44
import type { OpenClawConfig } from "../config/types.openclaw.js";
55
import { callGateway } from "../gateway/call.js";
66
import { isEmbeddedMode } from "../infra/embedded-mode.js";
7-
import { getActiveSecretsRuntimeSnapshot } from "../secrets/runtime-state.js";
7+
import { getActiveSecretsRuntimeConfigSnapshot } from "../secrets/runtime-state.js";
88
import { getActiveRuntimeWebToolsMetadata } from "../secrets/runtime-web-tools-state.js";
99
import { isCronRunSessionKey } from "../sessions/session-key-utils.js";
1010
import { resolveTranscriptsConfig } from "../transcripts/config.js";
@@ -169,7 +169,7 @@ export function createOpenClawTools(
169169
} & SpawnedToolContext,
170170
): AnyAgentTool[] {
171171
const resolvedConfig = options?.config ?? openClawToolsDeps.config;
172-
const runtimeSnapshot = getActiveSecretsRuntimeSnapshot();
172+
const runtimeSnapshot = getActiveSecretsRuntimeConfigSnapshot();
173173
const availabilityConfig = selectApplicableRuntimeConfig({
174174
inputConfig: resolvedConfig,
175175
runtimeConfig: runtimeSnapshot?.config,

src/agents/tools/web-fetch.provider-fallback.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ vi.mock("../../web-fetch/runtime.js", () => ({
1515
resolveWebFetchDefinition: resolveWebFetchDefinitionMock,
1616
}));
1717
vi.mock("../../secrets/runtime-state.js", () => ({
18-
getActiveSecretsRuntimeSnapshot: () => runtimeState.activeSecretsRuntimeSnapshot,
18+
getActiveSecretsRuntimeConfigSnapshot: () => runtimeState.activeSecretsRuntimeSnapshot,
1919
}));
2020
vi.mock("../../secrets/runtime-web-tools-state.js", () => ({
2121
getActiveRuntimeWebToolsMetadata: () => runtimeState.activeRuntimeWebToolsMetadata,

src/agents/tools/web-search.late-bind.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const mocks = vi.hoisted(() => ({
55
runWebSearch: vi.fn(),
66
resolveManifestContractOwnerPluginId: vi.fn(),
77
getActiveRuntimeWebToolsMetadata: vi.fn(),
8-
getActiveSecretsRuntimeSnapshot: vi.fn(),
8+
getActiveSecretsRuntimeConfigSnapshot: vi.fn(),
99
}));
1010

1111
vi.mock("../../web-search/runtime.js", () => ({
@@ -22,7 +22,7 @@ vi.mock("../../secrets/runtime-web-tools-state.js", () => ({
2222
}));
2323

2424
vi.mock("../../secrets/runtime-state.js", () => ({
25-
getActiveSecretsRuntimeSnapshot: mocks.getActiveSecretsRuntimeSnapshot,
25+
getActiveSecretsRuntimeConfigSnapshot: mocks.getActiveSecretsRuntimeConfigSnapshot,
2626
}));
2727

2828
type RunWebSearchParams = {
@@ -58,8 +58,8 @@ describe("web_search late-bound runtime fallback", () => {
5858
mocks.resolveManifestContractOwnerPluginId.mockReturnValue(undefined);
5959
mocks.getActiveRuntimeWebToolsMetadata.mockReset();
6060
mocks.getActiveRuntimeWebToolsMetadata.mockReturnValue(null);
61-
mocks.getActiveSecretsRuntimeSnapshot.mockReset();
62-
mocks.getActiveSecretsRuntimeSnapshot.mockReturnValue(null);
61+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReset();
62+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReturnValue(null);
6363
});
6464

6565
it("falls back to options.runtimeWebSearch when active runtime web tools metadata is absent", async () => {
@@ -79,7 +79,7 @@ describe("web_search late-bound runtime fallback", () => {
7979
expect(firstRunWebSearchParams()?.runtimeWebSearch?.selectedProvider).toBe("brave");
8080
});
8181

82-
it("falls back to options.config when getActiveSecretsRuntimeSnapshot is null", async () => {
82+
it("falls back to options.config when getActiveSecretsRuntimeConfigSnapshot is null", async () => {
8383
const fallbackConfig = {
8484
tools: { web: { search: { provider: "brave" } } },
8585
};
@@ -161,7 +161,7 @@ describe("web_search late-bound runtime fallback", () => {
161161
});
162162

163163
it("honors late-bound disabled search config at execute time", async () => {
164-
mocks.getActiveSecretsRuntimeSnapshot.mockReturnValue({
164+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReturnValue({
165165
config: { tools: { web: { search: { enabled: false } } } },
166166
});
167167
const tool = createWebSearchTool({

src/agents/tools/web-tool-runtime-context.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66

77
const mocks = vi.hoisted(() => ({
88
getActiveRuntimeWebToolsMetadata: vi.fn(),
9-
getActiveSecretsRuntimeSnapshot: vi.fn(),
9+
getActiveSecretsRuntimeConfigSnapshot: vi.fn(),
1010
resolveManifestContractOwnerPluginId: vi.fn(),
1111
}));
1212

@@ -19,7 +19,7 @@ vi.mock("../../secrets/runtime-web-tools-state.js", () => ({
1919
}));
2020

2121
vi.mock("../../secrets/runtime-state.js", () => ({
22-
getActiveSecretsRuntimeSnapshot: mocks.getActiveSecretsRuntimeSnapshot,
22+
getActiveSecretsRuntimeConfigSnapshot: mocks.getActiveSecretsRuntimeConfigSnapshot,
2323
}));
2424

2525
function latestOwnerLookupParams(): Record<string, unknown> {
@@ -34,8 +34,8 @@ describe("web tool runtime context", () => {
3434
beforeEach(() => {
3535
mocks.getActiveRuntimeWebToolsMetadata.mockReset();
3636
mocks.getActiveRuntimeWebToolsMetadata.mockReturnValue(null);
37-
mocks.getActiveSecretsRuntimeSnapshot.mockReset();
38-
mocks.getActiveSecretsRuntimeSnapshot.mockReturnValue(null);
37+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReset();
38+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReturnValue(null);
3939
mocks.resolveManifestContractOwnerPluginId.mockReset();
4040
mocks.resolveManifestContractOwnerPluginId.mockReturnValue(undefined);
4141
});
@@ -44,7 +44,7 @@ describe("web tool runtime context", () => {
4444
const runtimeConfig = {
4545
tools: { web: { search: { provider: "perplexity" } } },
4646
};
47-
mocks.getActiveSecretsRuntimeSnapshot.mockReturnValue({ config: runtimeConfig });
47+
mocks.getActiveSecretsRuntimeConfigSnapshot.mockReturnValue({ config: runtimeConfig });
4848
mocks.getActiveRuntimeWebToolsMetadata.mockReturnValue({
4949
search: {
5050
providerConfigured: "perplexity",

src/agents/tools/web-tool-runtime-context.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { OpenClawConfig } from "../../config/types.openclaw.js";
22
import { resolveManifestContractOwnerPluginId } from "../../plugins/plugin-registry.js";
3-
import { getActiveSecretsRuntimeSnapshot } from "../../secrets/runtime-state.js";
3+
import { getActiveSecretsRuntimeConfigSnapshot } from "../../secrets/runtime-state.js";
44
import { getActiveRuntimeWebToolsMetadata } from "../../secrets/runtime-web-tools-state.js";
55
import type {
66
RuntimeWebFetchMetadata,
@@ -64,7 +64,7 @@ function resolveWebToolRuntimeContext<TMetadata extends WebProviderRuntimeMetada
6464
| undefined;
6565
const config =
6666
params.lateBindRuntimeConfig === true
67-
? (getActiveSecretsRuntimeSnapshot()?.config ?? params.capturedConfig)
67+
? (getActiveSecretsRuntimeConfigSnapshot()?.config ?? params.capturedConfig)
6868
: params.capturedConfig;
6969
const providerSelectionId =
7070
resolveRuntimeWebProviderId(runtimeMetadata) ||

src/agents/tools/web-tools.enabled-defaults.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function readConfiguredSearchProvider(config: unknown): string | undefined {
4040
}
4141

4242
vi.mock("../../secrets/runtime-state.js", () => ({
43-
getActiveSecretsRuntimeSnapshot: () => activeSecretsRuntimeSnapshot.current,
43+
getActiveSecretsRuntimeConfigSnapshot: () => activeSecretsRuntimeSnapshot.current,
4444
}));
4545

4646
vi.mock("../../web-search/runtime.js", async () => {

src/plugins/public-surface-loader.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,44 @@ describe("bundled plugin public surface loader", () => {
205205
expect(createJiti).not.toHaveBeenCalled();
206206
});
207207

208+
it("keeps package-local dist public artifacts on the native path for source plugin roots", async () => {
209+
const createJiti = vi.fn(() => vi.fn(() => ({ marker: "jiti-should-not-run" })));
210+
vi.doMock("jiti", () => ({
211+
createJiti,
212+
}));
213+
vi.doMock("./native-module-require.js", () => ({
214+
tryNativeRequireJavaScriptModule: (modulePath: string) => ({
215+
ok: true,
216+
moduleExport: {
217+
marker: modulePath.includes(`${path.sep}dist${path.sep}`) ? "dist" : "source",
218+
},
219+
}),
220+
}));
221+
222+
const publicSurfaceLoader = await importFreshModule<
223+
typeof import("./public-surface-loader.js")
224+
>(import.meta.url, "./public-surface-loader.js?scope=source-root-local-dist-public-artifacts");
225+
const tempRoot = createTempDir();
226+
const bundledPluginsDir = path.join(tempRoot, "extensions");
227+
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
228+
process.env.OPENCLAW_TEST_TRUST_BUNDLED_PLUGINS_DIR = "1";
229+
230+
const sourcePath = path.join(bundledPluginsDir, "demo", "api.ts");
231+
const distPath = path.join(bundledPluginsDir, "demo", "dist", "api.js");
232+
fs.mkdirSync(path.dirname(sourcePath), { recursive: true });
233+
fs.mkdirSync(path.dirname(distPath), { recursive: true });
234+
fs.writeFileSync(sourcePath, 'export const marker = "source";\n', "utf8");
235+
fs.writeFileSync(distPath, 'export const marker = "dist";\n', "utf8");
236+
237+
expect(
238+
publicSurfaceLoader.loadBundledPluginPublicArtifactModuleSync<{ marker: string }>({
239+
dirName: "demo",
240+
artifactBasename: "api.js",
241+
}).marker,
242+
).toBe("dist");
243+
expect(createJiti).not.toHaveBeenCalled();
244+
});
245+
208246
it.runIf(process.platform !== "win32")(
209247
"allows hardlinked bundled public artifacts under the trusted bundled root",
210248
async () => {

src/plugins/public-surface-runtime.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,31 @@ describe("bundled plugin public surface runtime", () => {
7070
).toBe(sourceModulePath);
7171
});
7272

73+
it("prefers package-local dist artifacts before source artifacts in source plugin trees", () => {
74+
const packageRoot = createTempDir();
75+
const sourceModulePath = path.join(packageRoot, "extensions", "demo", "api.ts");
76+
const packageLocalDistModulePath = path.join(
77+
packageRoot,
78+
"extensions",
79+
"demo",
80+
"dist",
81+
"api.js",
82+
);
83+
fs.mkdirSync(path.dirname(sourceModulePath), { recursive: true });
84+
fs.mkdirSync(path.dirname(packageLocalDistModulePath), { recursive: true });
85+
fs.writeFileSync(sourceModulePath, "export const marker = 'source';\n", "utf8");
86+
fs.writeFileSync(packageLocalDistModulePath, "export const marker = 'local-dist';\n", "utf8");
87+
88+
expect(
89+
resolveBundledPluginPublicSurfacePath({
90+
rootDir: packageRoot,
91+
bundledPluginsDir: path.join(packageRoot, "extensions"),
92+
dirName: "demo",
93+
artifactBasename: "api.js",
94+
}),
95+
).toBe(packageLocalDistModulePath);
96+
});
97+
7398
it("prefers source public surfaces over stale auto-resolved dist artifacts in source checkouts", () => {
7499
const packageRoot = createTempDir();
75100
const sourceModulePath = path.join(packageRoot, "extensions", "demo", "api.ts");

0 commit comments

Comments
 (0)