fix(ci): repair main type and boundary regressions

This commit is contained in:
Vincent Koc
2026-03-19 07:50:02 -07:00
parent 0c4fdf1284
commit 44cd4fb55f
20 changed files with 246 additions and 144 deletions

View File

@@ -1,5 +1,9 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as acpSessionManager from "../acp/control-plane/manager.js";
import type {
AcpCloseSessionInput,
AcpInitializeSessionInput,
} from "../acp/control-plane/manager.types.js";
import {
clearRuntimeConfigSnapshot,
setRuntimeConfigSnapshot,
@@ -180,16 +184,12 @@ describe("spawnAcpDirect", () => {
metaCleared: false,
});
getAcpSessionManagerSpy.mockReset().mockReturnValue({
initializeSession: async (params: unknown) => await hoisted.initializeSessionMock(params),
closeSession: async (params: unknown) => await hoisted.closeSessionMock(params),
initializeSession: async (params: AcpInitializeSessionInput) =>
await hoisted.initializeSessionMock(params),
closeSession: async (params: AcpCloseSessionInput) => await hoisted.closeSessionMock(params),
} as unknown as ReturnType<typeof acpSessionManager.getAcpSessionManager>);
hoisted.initializeSessionMock.mockReset().mockImplementation(async (argsUnknown: unknown) => {
const args = argsUnknown as {
sessionKey: string;
agent: string;
mode: "persistent" | "oneshot";
cwd?: string;
};
const args = argsUnknown as AcpInitializeSessionInput;
const runtimeSessionName = `${args.sessionKey}:runtime`;
const cwd = typeof args.cwd === "string" ? args.cwd : undefined;
return {
@@ -386,7 +386,6 @@ describe("spawnAcpDirect", () => {
matrix: {
threadBindings: {
enabled: true,
spawnAcpSessions: true,
},
},
},

View File

@@ -6,12 +6,14 @@ import {
type OpenClawConfig,
} from "../config/config.js";
import * as configSessions from "../config/sessions.js";
import type { SessionEntry } from "../config/sessions/types.js";
import * as gatewayCall from "../gateway/call.js";
import {
__testing as sessionBindingServiceTesting,
registerSessionBindingAdapter,
} from "../infra/outbound/session-binding-service.js";
import * as hookRunnerGlobal from "../plugins/hook-runner-global.js";
import type { HookRunner } from "../plugins/hooks.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createTestRegistry } from "../test-utils/channel-plugins.js";
import * as piEmbedded from "./pi-embedded.js";
@@ -65,11 +67,23 @@ const waitForEmbeddedPiRunEndSpy = vi.spyOn(piEmbedded, "waitForEmbeddedPiRunEnd
const readLatestAssistantReplyMock = vi.fn(
async (_sessionKey?: string): Promise<string | undefined> => "raw subagent reply",
);
const embeddedPiRunActiveMock = vi.fn<typeof piEmbedded.isEmbeddedPiRunActive>(
(_sessionId: string) => false,
);
const embeddedPiRunStreamingMock = vi.fn<typeof piEmbedded.isEmbeddedPiRunStreaming>(
(_sessionId: string) => false,
);
const queueEmbeddedPiMessageMock = vi.fn<typeof piEmbedded.queueEmbeddedPiMessage>(
(_sessionId: string, _text: string) => false,
);
const waitForEmbeddedPiRunEndMock = vi.fn<typeof piEmbedded.waitForEmbeddedPiRunEnd>(
async (_sessionId: string, _timeoutMs?: number) => true,
);
const embeddedRunMock = {
isEmbeddedPiRunActive: vi.fn(() => false),
isEmbeddedPiRunStreaming: vi.fn(() => false),
queueEmbeddedPiMessage: vi.fn((_: string, __: string) => false),
waitForEmbeddedPiRunEnd: vi.fn(async (_: string, __?: number) => true),
isEmbeddedPiRunActive: embeddedPiRunActiveMock,
isEmbeddedPiRunStreaming: embeddedPiRunStreamingMock,
queueEmbeddedPiMessage: queueEmbeddedPiMessageMock,
waitForEmbeddedPiRunEnd: waitForEmbeddedPiRunEndMock,
};
const { subagentRegistryMock } = vi.hoisted(() => ({
subagentRegistryMock: {
@@ -92,18 +106,21 @@ const subagentDeliveryTargetHookMock = vi.fn(
undefined,
);
let hasSubagentDeliveryTargetHook = false;
const hookHasHooksMock = vi.fn<HookRunner["hasHooks"]>(
(hookName) => hookName === "subagent_delivery_target" && hasSubagentDeliveryTargetHook,
);
const hookRunSubagentDeliveryTargetMock = vi.fn<HookRunner["runSubagentDeliveryTarget"]>(
async (event, ctx) => await subagentDeliveryTargetHookMock(event, ctx),
);
const hookRunnerMock = {
hasHooks: vi.fn(
(hookName: string) => hookName === "subagent_delivery_target" && hasSubagentDeliveryTargetHook,
),
runSubagentDeliveryTarget: vi.fn((event: unknown, ctx: unknown) =>
subagentDeliveryTargetHookMock(event, ctx),
),
};
hasHooks: hookHasHooksMock,
runSubagentDeliveryTarget: hookRunSubagentDeliveryTargetMock,
} as unknown as HookRunner;
const chatHistoryMock = vi.fn(async (_sessionKey?: string) => ({
messages: [] as Array<unknown>,
}));
let sessionStore: Record<string, Record<string, unknown>> = {};
type TestSessionStore = Record<string, Partial<SessionEntry>>;
let sessionStore: TestSessionStore = {};
let configOverride: OpenClawConfig = {
session: {
mainKey: "main",
@@ -131,19 +148,34 @@ function setConfigOverride(next: OpenClawConfig): void {
setRuntimeConfigSnapshot(configOverride);
}
function loadSessionStoreFixture(): ReturnType<typeof configSessions.loadSessionStore> {
return new Proxy(sessionStore as ReturnType<typeof configSessions.loadSessionStore>, {
get(target, key: string | symbol) {
if (typeof key === "string" && !(key in target) && key.includes(":subagent:")) {
return {
sessionId: key,
updatedAt: Date.now(),
function toSessionEntry(
sessionKey: string,
entry?: Partial<SessionEntry>,
): SessionEntry | undefined {
if (!entry) {
return undefined;
}
return {
sessionId: entry.sessionId ?? sessionKey,
updatedAt: entry.updatedAt ?? Date.now(),
...entry,
};
}
function loadSessionStoreFixture(): Record<string, SessionEntry> {
return new Proxy({} as Record<string, SessionEntry>, {
get(_target, key: string | symbol) {
if (typeof key !== "string") {
return undefined;
}
if (!(key in sessionStore) && key.includes(":subagent:")) {
return toSessionEntry(key, {
inputTokens: 1,
outputTokens: 1,
totalTokens: 2,
};
});
}
return target[key as keyof typeof target];
return toSessionEntry(key, sessionStore[key]);
},
});
}
@@ -223,17 +255,20 @@ describe("subagent announce formatting", () => {
.mockImplementation(async (params) => await readLatestAssistantReplyMock(params?.sessionKey));
isEmbeddedPiRunActiveSpy
.mockReset()
.mockImplementation(() => embeddedRunMock.isEmbeddedPiRunActive());
.mockImplementation((sessionId) => embeddedRunMock.isEmbeddedPiRunActive(sessionId));
isEmbeddedPiRunStreamingSpy
.mockReset()
.mockImplementation(() => embeddedRunMock.isEmbeddedPiRunStreaming());
.mockImplementation((sessionId) => embeddedRunMock.isEmbeddedPiRunStreaming(sessionId));
queueEmbeddedPiMessageSpy
.mockReset()
.mockImplementation((...args) => embeddedRunMock.queueEmbeddedPiMessage(...args));
.mockImplementation((sessionId, text) =>
embeddedRunMock.queueEmbeddedPiMessage(sessionId, text),
);
waitForEmbeddedPiRunEndSpy
.mockReset()
.mockImplementation(
async (...args) => await embeddedRunMock.waitForEmbeddedPiRunEnd(...args),
async (sessionId, timeoutMs) =>
await embeddedRunMock.waitForEmbeddedPiRunEnd(sessionId, timeoutMs),
);
embeddedRunMock.isEmbeddedPiRunActive.mockClear().mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockClear().mockReturnValue(false);
@@ -258,8 +293,8 @@ describe("subagent announce formatting", () => {
subagentRegistryMock.replaceSubagentRunAfterSteer.mockClear().mockReturnValue(true);
subagentRegistryMock.resolveRequesterForChildSession.mockClear().mockReturnValue(null);
hasSubagentDeliveryTargetHook = false;
hookRunnerMock.hasHooks.mockClear();
hookRunnerMock.runSubagentDeliveryTarget.mockClear();
hookHasHooksMock.mockClear();
hookRunSubagentDeliveryTargetMock.mockClear();
subagentDeliveryTargetHookMock.mockReset().mockResolvedValue(undefined);
readLatestAssistantReplyMock.mockClear().mockResolvedValue("raw subagent reply");
chatHistoryMock.mockReset().mockResolvedValue({ messages: [] });

View File

@@ -53,6 +53,7 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [
"ban",
"set-profile",
"set-presence",
"set-profile",
"download-file",
] as const;

View File

@@ -350,15 +350,15 @@ export async function channelsAddCommand(
await writeConfigFile(nextConfig);
runtime.log(`Added ${channelLabel(channel)} account "${accountId}".`);
const setup = plugin.setup;
if (setup?.afterAccountConfigWritten) {
const afterAccountConfigWritten = plugin.setup?.afterAccountConfigWritten;
if (afterAccountConfigWritten) {
await runCollectedChannelOnboardingPostWriteHooks({
hooks: [
{
channel,
accountId,
run: async ({ cfg: writtenCfg, runtime: hookRuntime }) =>
await setup.afterAccountConfigWritten?.({
await afterAccountConfigWritten({
previousCfg: cfg,
cfg: writtenCfg,
accountId,

View File

@@ -119,7 +119,6 @@ export async function channelsRemoveCommand(
runtime.exit(1);
return;
}
const resolvedAccountId =
normalizeAccountId(accountId) ?? resolveChannelDefaultAccountId({ plugin, cfg });
const accountKey = resolvedAccountId || DEFAULT_ACCOUNT_ID;
@@ -164,14 +163,14 @@ export async function channelsRemoveCommand(
if (useWizard && prompter) {
await prompter.outro(
deleteConfig
? `Deleted ${channelLabel(channel)} account "${accountKey}".`
: `Disabled ${channelLabel(channel)} account "${accountKey}".`,
? `Deleted ${channelLabel(resolvedChannel)} account "${accountKey}".`
: `Disabled ${channelLabel(resolvedChannel)} account "${accountKey}".`,
);
} else {
runtime.log(
deleteConfig
? `Deleted ${channelLabel(channel)} account "${accountKey}".`
: `Disabled ${channelLabel(channel)} account "${accountKey}".`,
? `Deleted ${channelLabel(resolvedChannel)} account "${accountKey}".`
: `Disabled ${channelLabel(resolvedChannel)} account "${accountKey}".`,
);
}
}

View File

@@ -13,8 +13,8 @@ import type {
OpenClawPluginCommandDefinition,
OpenClawPluginConfigSchema,
OpenClawPluginDefinition,
PluginInteractiveTelegramHandlerContext,
PluginCommandContext,
PluginInteractiveTelegramHandlerContext,
} from "../plugins/types.js";
export type {

View File

@@ -6,7 +6,10 @@ export type { SecretInput } from "../config/types.secrets.js";
export type { WizardPrompter } from "../wizard/prompts.js";
export type { ChannelSetupAdapter } from "../channels/plugins/types.adapters.js";
export type { ChannelSetupInput } from "../channels/plugins/types.core.js";
export type { ChannelSetupDmPolicy } from "../channels/plugins/setup-wizard-types.js";
export type {
ChannelSetupDmPolicy,
ChannelSetupWizardAdapter,
} from "../channels/plugins/setup-wizard-types.js";
export type {
ChannelSetupWizard,
ChannelSetupWizardAllowFromEntry,