Skip to content

Commit ec82365

Browse files
authored
Merge branch 'main' into vincentkoc-code/fix-android-canvas-action-trust
2 parents e967e12 + a60672b commit ec82365

File tree

10 files changed

+283
-107
lines changed

10 files changed

+283
-107
lines changed

src/agents/pi-embedded-runner/run/attempt.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import {
77
DefaultResourceLoader,
88
SessionManager,
99
} from "@mariozechner/pi-coding-agent";
10+
import {
11+
resolveTelegramInlineButtonsScope,
12+
resolveTelegramReactionLevel,
13+
} from "../../../../extensions/telegram/api.js";
1014
import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
1115
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
1216
import type { OpenClawConfig } from "../../../config/config.js";
@@ -17,10 +21,6 @@ import {
1721
} from "../../../infra/net/undici-global-dispatcher.js";
1822
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
1923
import { resolveSignalReactionLevel } from "../../../plugin-sdk/signal.js";
20-
import {
21-
resolveTelegramInlineButtonsScope,
22-
resolveTelegramReactionLevel,
23-
} from "../../../plugin-sdk/telegram.js";
2424
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
2525
import type {
2626
PluginHookAgentContext,
@@ -1663,6 +1663,7 @@ export async function runEmbeddedAttempt(
16631663
params: EmbeddedRunAttemptParams,
16641664
): Promise<EmbeddedRunAttemptResult> {
16651665
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
1666+
const prevCwd = process.cwd();
16661667
const runAbortController = new AbortController();
16671668
// Proxy bootstrap must happen before timeout tuning so the timeouts wrap the
16681669
// active EnvHttpProxyAgent instead of being replaced by a bare proxy dispatcher.
@@ -1689,6 +1690,7 @@ export async function runEmbeddedAttempt(
16891690
await fs.mkdir(effectiveWorkspace, { recursive: true });
16901691

16911692
let restoreSkillEnv: (() => void) | undefined;
1693+
process.chdir(effectiveWorkspace);
16921694
try {
16931695
const { shouldLoadSkillEntries, skillEntries } = resolveEmbeddedRunSkillEntries({
16941696
workspaceDir: effectiveWorkspace,
@@ -1943,7 +1945,7 @@ export async function runEmbeddedAttempt(
19431945
config: params.config,
19441946
agentId: sessionAgentId,
19451947
workspaceDir: effectiveWorkspace,
1946-
cwd: effectiveWorkspace,
1948+
cwd: process.cwd(),
19471949
runtime: {
19481950
host: machineName,
19491951
os: `${os.type()} ${os.release()}`,
@@ -1962,7 +1964,7 @@ export async function runEmbeddedAttempt(
19621964
const docsPath = await resolveOpenClawDocsPath({
19631965
workspaceDir: effectiveWorkspace,
19641966
argv1: process.argv[1],
1965-
cwd: effectiveWorkspace,
1967+
cwd: process.cwd(),
19661968
moduleUrl: import.meta.url,
19671969
});
19681970
const ttsHint = params.config ? buildTtsSystemPromptHint(params.config) : undefined;
@@ -2833,6 +2835,11 @@ export async function runEmbeddedAttempt(
28332835
);
28342836
}
28352837

2838+
if (process.env.OPENCLAW_PLUGIN_CHECKPOINTS === "1") {
2839+
log.warn(
2840+
`[hooks][checkpoints] attempt llm_input runId=${params.runId} sessionKey=${params.sessionKey ?? "unknown"} pid=${process.pid} hookRunner=${hookRunner ? "present" : "missing"} hasHooks=${hookRunner?.hasHooks("llm_input") === true}`,
2841+
);
2842+
}
28362843
if (hookRunner?.hasHooks("llm_input")) {
28372844
hookRunner
28382845
.runLlmInput(
@@ -3105,6 +3112,11 @@ export async function runEmbeddedAttempt(
31053112
// Run agent_end hooks to allow plugins to analyze the conversation
31063113
// This is fire-and-forget, so we don't await
31073114
// Run even on compaction timeout so plugins can log/cleanup
3115+
if (process.env.OPENCLAW_PLUGIN_CHECKPOINTS === "1") {
3116+
log.warn(
3117+
`[hooks][checkpoints] attempt agent_end runId=${params.runId} sessionKey=${params.sessionKey ?? "unknown"} pid=${process.pid} hookRunner=${hookRunner ? "present" : "missing"} hasHooks=${hookRunner?.hasHooks("agent_end") === true}`,
3118+
);
3119+
}
31083120
if (hookRunner?.hasHooks("agent_end")) {
31093121
hookRunner
31103122
.runAgentEnd(
@@ -3164,6 +3176,11 @@ export async function runEmbeddedAttempt(
31643176
)
31653177
.map((entry) => ({ toolName: entry.toolName, meta: entry.meta }));
31663178

3179+
if (process.env.OPENCLAW_PLUGIN_CHECKPOINTS === "1") {
3180+
log.warn(
3181+
`[hooks][checkpoints] attempt llm_output runId=${params.runId} sessionKey=${params.sessionKey ?? "unknown"} pid=${process.pid} hookRunner=${hookRunner ? "present" : "missing"} hasHooks=${hookRunner?.hasHooks("llm_output") === true}`,
3182+
);
3183+
}
31673184
if (hookRunner?.hasHooks("llm_output")) {
31683185
hookRunner
31693186
.runLlmOutput(
@@ -3242,5 +3259,6 @@ export async function runEmbeddedAttempt(
32423259
}
32433260
} finally {
32443261
restoreSkillEnv?.();
3262+
process.chdir(prevCwd);
32453263
}
32463264
}

src/agents/runtime-plugins.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const hoisted = vi.hoisted(() => ({
4+
loadOpenClawPlugins: vi.fn(),
5+
getActivePluginRegistryKey: vi.fn<() => string | null>(),
6+
}));
7+
8+
vi.mock("../plugins/loader.js", () => ({
9+
loadOpenClawPlugins: hoisted.loadOpenClawPlugins,
10+
}));
11+
12+
vi.mock("../plugins/runtime.js", () => ({
13+
getActivePluginRegistryKey: hoisted.getActivePluginRegistryKey,
14+
}));
15+
16+
const { ensureRuntimePluginsLoaded } = await import("./runtime-plugins.js");
17+
18+
describe("ensureRuntimePluginsLoaded", () => {
19+
beforeEach(() => {
20+
hoisted.loadOpenClawPlugins.mockReset();
21+
hoisted.getActivePluginRegistryKey.mockReset();
22+
hoisted.getActivePluginRegistryKey.mockReturnValue(null);
23+
});
24+
25+
it("does not reactivate plugins when a process already has an active registry", () => {
26+
hoisted.getActivePluginRegistryKey.mockReturnValue("gateway-registry");
27+
28+
ensureRuntimePluginsLoaded({
29+
config: {} as never,
30+
workspaceDir: "/tmp/workspace",
31+
allowGatewaySubagentBinding: true,
32+
});
33+
34+
expect(hoisted.loadOpenClawPlugins).not.toHaveBeenCalled();
35+
});
36+
37+
it("loads runtime plugins when no active registry exists", () => {
38+
ensureRuntimePluginsLoaded({
39+
config: {} as never,
40+
workspaceDir: "/tmp/workspace",
41+
allowGatewaySubagentBinding: true,
42+
});
43+
44+
expect(hoisted.loadOpenClawPlugins).toHaveBeenCalledWith({
45+
config: {} as never,
46+
workspaceDir: "/tmp/workspace",
47+
runtimeOptions: {
48+
allowGatewaySubagentBinding: true,
49+
},
50+
});
51+
});
52+
});

src/agents/runtime-plugins.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import type { OpenClawConfig } from "../config/config.js";
22
import { loadOpenClawPlugins } from "../plugins/loader.js";
3+
import { getActivePluginRegistryKey } from "../plugins/runtime.js";
34
import { resolveUserPath } from "../utils.js";
45

56
export function ensureRuntimePluginsLoaded(params: {
67
config?: OpenClawConfig;
78
workspaceDir?: string | null;
89
allowGatewaySubagentBinding?: boolean;
910
}): void {
11+
if (getActivePluginRegistryKey()) {
12+
return;
13+
}
14+
1015
const workspaceDir =
1116
typeof params.workspaceDir === "string" && params.workspaceDir.trim()
1217
? resolveUserPath(params.workspaceDir)

src/image-generation/provider-registry.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
11
import { normalizeProviderId } from "../agents/model-selection.js";
22
import type { OpenClawConfig } from "../config/config.js";
3-
import { isBlockedObjectKey } from "../infra/prototype-keys.js";
43
import { loadOpenClawPlugins } from "../plugins/loader.js";
5-
import { getActivePluginRegistry } from "../plugins/runtime.js";
4+
import { getActivePluginRegistry, getActivePluginRegistryKey } from "../plugins/runtime.js";
65
import type { ImageGenerationProviderPlugin } from "../plugins/types.js";
76

87
const BUILTIN_IMAGE_GENERATION_PROVIDERS: readonly ImageGenerationProviderPlugin[] = [];
98

109
function normalizeImageGenerationProviderId(id: string | undefined): string | undefined {
1110
const normalized = normalizeProviderId(id ?? "");
12-
if (!normalized || isBlockedObjectKey(normalized)) {
13-
return undefined;
14-
}
15-
return normalized;
11+
return normalized || undefined;
1612
}
1713

1814
function resolvePluginImageGenerationProviders(
1915
cfg?: OpenClawConfig,
2016
): ImageGenerationProviderPlugin[] {
2117
const active = getActivePluginRegistry();
22-
const activeEntries = active?.imageGenerationProviders?.map((entry) => entry.provider) ?? [];
23-
if (activeEntries.length > 0 || !cfg) {
24-
return activeEntries;
25-
}
26-
return loadOpenClawPlugins({ config: cfg }).imageGenerationProviders.map(
27-
(entry) => entry.provider,
28-
);
18+
const registry =
19+
getActivePluginRegistryKey() || !cfg ? active : loadOpenClawPlugins({ config: cfg });
20+
return registry?.imageGenerationProviders?.map((entry) => entry.provider) ?? [];
2921
}
3022

3123
function buildProviderMaps(cfg?: OpenClawConfig): {

src/infra/channel-summary.test.ts

Lines changed: 85 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { describe, expect, it, vi } from "vitest";
2-
import { listChannelPlugins } from "../channels/plugins/index.js";
1+
import { afterEach, describe, expect, it } from "vitest";
32
import type { ChannelPlugin } from "../channels/plugins/types.js";
3+
import { setActivePluginRegistry } from "../plugins/runtime.js";
4+
import { createTestRegistry } from "../test-utils/channel-plugins.js";
45
import { buildChannelSummary } from "./channel-summary.js";
56

6-
vi.mock("../channels/plugins/index.js", () => ({
7-
listChannelPlugins: vi.fn(),
8-
}));
9-
107
function makeSlackHttpSummaryPlugin(): ChannelPlugin {
118
return {
129
id: "slack",
@@ -207,8 +204,16 @@ function makeFallbackSummaryPlugin(params: {
207204
}
208205

209206
describe("buildChannelSummary", () => {
207+
afterEach(() => {
208+
setActivePluginRegistry(createTestRegistry([]));
209+
});
210+
210211
it("preserves Slack HTTP signing-secret unavailable state from source config", async () => {
211-
vi.mocked(listChannelPlugins).mockReturnValue([makeSlackHttpSummaryPlugin()]);
212+
setActivePluginRegistry(
213+
createTestRegistry([
214+
{ pluginId: "slack", plugin: makeSlackHttpSummaryPlugin(), source: "test" },
215+
]),
216+
);
212217

213218
const lines = await buildChannelSummary({ marker: "resolved", channels: {} } as never, {
214219
colorize: false,
@@ -223,9 +228,15 @@ describe("buildChannelSummary", () => {
223228
});
224229

225230
it("shows disabled status without configured account detail lines", async () => {
226-
vi.mocked(listChannelPlugins).mockReturnValue([
227-
makeTelegramSummaryPlugin({ enabled: false, configured: false }),
228-
]);
231+
setActivePluginRegistry(
232+
createTestRegistry([
233+
{
234+
pluginId: "telegram",
235+
plugin: makeTelegramSummaryPlugin({ enabled: false, configured: false }),
236+
source: "test",
237+
},
238+
]),
239+
);
229240

230241
const lines = await buildChannelSummary({ channels: {} } as never, {
231242
colorize: false,
@@ -236,15 +247,21 @@ describe("buildChannelSummary", () => {
236247
});
237248

238249
it("includes linked summary metadata and truncates allow-from details", async () => {
239-
vi.mocked(listChannelPlugins).mockReturnValue([
240-
makeTelegramSummaryPlugin({
241-
enabled: true,
242-
configured: true,
243-
linked: true,
244-
authAgeMs: 300_000,
245-
allowFrom: ["alice", "bob", "carol"],
246-
}),
247-
]);
250+
setActivePluginRegistry(
251+
createTestRegistry([
252+
{
253+
pluginId: "telegram",
254+
plugin: makeTelegramSummaryPlugin({
255+
enabled: true,
256+
configured: true,
257+
linked: true,
258+
authAgeMs: 300_000,
259+
allowFrom: ["alice", "bob", "carol"],
260+
}),
261+
source: "test",
262+
},
263+
]),
264+
);
248265

249266
const lines = await buildChannelSummary({ channels: {} } as never, {
250267
colorize: false,
@@ -256,13 +273,19 @@ describe("buildChannelSummary", () => {
256273
});
257274

258275
it("shows not-linked status when linked metadata is explicitly false", async () => {
259-
vi.mocked(listChannelPlugins).mockReturnValue([
260-
makeTelegramSummaryPlugin({
261-
enabled: true,
262-
configured: true,
263-
linked: false,
264-
}),
265-
]);
276+
setActivePluginRegistry(
277+
createTestRegistry([
278+
{
279+
pluginId: "telegram",
280+
plugin: makeTelegramSummaryPlugin({
281+
enabled: true,
282+
configured: true,
283+
linked: false,
284+
}),
285+
source: "test",
286+
},
287+
]),
288+
);
266289

267290
const lines = await buildChannelSummary({ channels: {} } as never, {
268291
colorize: false,
@@ -274,9 +297,15 @@ describe("buildChannelSummary", () => {
274297
});
275298

276299
it("renders non-slack account detail fields for configured accounts", async () => {
277-
vi.mocked(listChannelPlugins).mockReturnValue([
278-
makeSignalSummaryPlugin({ enabled: false, configured: true }),
279-
]);
300+
setActivePluginRegistry(
301+
createTestRegistry([
302+
{
303+
pluginId: "signal",
304+
plugin: makeSignalSummaryPlugin({ enabled: false, configured: true }),
305+
source: "test",
306+
},
307+
]),
308+
);
280309

281310
const lines = await buildChannelSummary({ channels: {} } as never, {
282311
colorize: false,
@@ -290,14 +319,20 @@ describe("buildChannelSummary", () => {
290319
});
291320

292321
it("uses the channel label and default account id when no accounts exist", async () => {
293-
vi.mocked(listChannelPlugins).mockReturnValue([
294-
makeFallbackSummaryPlugin({
295-
enabled: true,
296-
configured: true,
297-
accountIds: [],
298-
defaultAccountId: "fallback-account",
299-
}),
300-
]);
322+
setActivePluginRegistry(
323+
createTestRegistry([
324+
{
325+
pluginId: "fallback-plugin",
326+
plugin: makeFallbackSummaryPlugin({
327+
enabled: true,
328+
configured: true,
329+
accountIds: [],
330+
defaultAccountId: "fallback-account",
331+
}),
332+
source: "test",
333+
},
334+
]),
335+
);
301336

302337
const lines = await buildChannelSummary({ channels: {} } as never, {
303338
colorize: false,
@@ -308,13 +343,19 @@ describe("buildChannelSummary", () => {
308343
});
309344

310345
it("shows not-configured status when enabled accounts exist without configured ones", async () => {
311-
vi.mocked(listChannelPlugins).mockReturnValue([
312-
makeFallbackSummaryPlugin({
313-
enabled: true,
314-
configured: false,
315-
accountIds: ["fallback-account"],
316-
}),
317-
]);
346+
setActivePluginRegistry(
347+
createTestRegistry([
348+
{
349+
pluginId: "fallback-plugin",
350+
plugin: makeFallbackSummaryPlugin({
351+
enabled: true,
352+
configured: false,
353+
accountIds: ["fallback-account"],
354+
}),
355+
source: "test",
356+
},
357+
]),
358+
);
318359

319360
const lines = await buildChannelSummary({ channels: {} } as never, {
320361
colorize: false,

0 commit comments

Comments
 (0)