mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-17 04:46:11 +00:00
refactor: remove vestigial runtime indirection (#124554)
* refactor: remove vestigial indirection * test: update Slack runtime API guard
This commit is contained in:
parent
66db70133b
commit
f43544f752
39 changed files with 267 additions and 426 deletions
|
|
@ -5,32 +5,35 @@ export {
|
|||
type SlackActionContext,
|
||||
} from "./src/action-runtime.js";
|
||||
export { listSlackDirectoryGroupsLive, listSlackDirectoryPeersLive } from "./src/directory-live.js";
|
||||
export {
|
||||
listEnabledSlackAccounts,
|
||||
listSlackAccountIds,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
} from "./src/accounts.js";
|
||||
export {
|
||||
deleteSlackMessage,
|
||||
editSlackMessage,
|
||||
getSlackMemberInfo,
|
||||
listEnabledSlackAccounts,
|
||||
listSlackAccountIds,
|
||||
listSlackEmojis,
|
||||
listSlackPins,
|
||||
listSlackReactions,
|
||||
monitorSlackProvider,
|
||||
pinSlackMessage,
|
||||
probeSlack,
|
||||
reactSlackMessage,
|
||||
readSlackMessages,
|
||||
removeOwnSlackReactions,
|
||||
removeSlackReaction,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
resolveSlackAppToken,
|
||||
resolveSlackBotToken,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
sendMessageSlack,
|
||||
sendSlackMessage,
|
||||
unpinSlackMessage,
|
||||
} from "./src/index.js";
|
||||
} from "./src/actions.js";
|
||||
export {
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
} from "./src/group-policy.js";
|
||||
export { monitorSlackProvider } from "./src/monitor.js";
|
||||
export { probeSlack } from "./src/probe.js";
|
||||
export { sendMessageSlack } from "./src/send.js";
|
||||
export { resolveSlackAppToken, resolveSlackBotToken } from "./src/token.js";
|
||||
export {
|
||||
resolveSlackChannelAllowlist,
|
||||
type SlackChannelLookup,
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
// Slack plugin entrypoint registers its OpenClaw integration.
|
||||
export {
|
||||
listEnabledSlackAccounts,
|
||||
listSlackAccountIds,
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
} from "./accounts.js";
|
||||
export {
|
||||
deleteSlackMessage,
|
||||
editSlackMessage,
|
||||
getSlackMemberInfo,
|
||||
listSlackEmojis,
|
||||
listSlackPins,
|
||||
listSlackReactions,
|
||||
pinSlackMessage,
|
||||
reactSlackMessage,
|
||||
readSlackMessages,
|
||||
removeOwnSlackReactions,
|
||||
removeSlackReaction,
|
||||
sendSlackMessage,
|
||||
unpinSlackMessage,
|
||||
} from "./actions.js";
|
||||
export { monitorSlackProvider } from "./monitor.js";
|
||||
export { probeSlack } from "./probe.js";
|
||||
export { sendMessageSlack } from "./send.js";
|
||||
export { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./group-policy.js";
|
||||
export { resolveSlackAppToken, resolveSlackBotToken } from "./token.js";
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
// Voice Call plugin entrypoint registers its OpenClaw integration.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { ErrorCodes, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
|
||||
import { resolveGlobalSingleton } from "openclaw/plugin-sdk/global-singleton";
|
||||
|
|
@ -28,7 +29,6 @@ import {
|
|||
validateProviderConfig,
|
||||
type VoiceCallConfig,
|
||||
} from "./src/config.js";
|
||||
import type { CoreConfig } from "./src/core-bridge.js";
|
||||
import { createVoiceCallContinueOperationStore } from "./src/gateway-continue-operation.js";
|
||||
|
||||
const VOICE_CALL_WRITE_METHOD_SCOPE = { scope: "operator.write" as const };
|
||||
|
|
@ -176,7 +176,7 @@ export default definePluginEntry({
|
|||
};
|
||||
const continueOperationStore = createVoiceCallContinueOperationStore({
|
||||
config,
|
||||
coreConfig: api.config as CoreConfig,
|
||||
coreConfig: api.config as OpenClawConfig,
|
||||
});
|
||||
|
||||
const ensureRuntime = async (): Promise<VoiceCallRuntime> => {
|
||||
|
|
@ -233,7 +233,7 @@ export default definePluginEntry({
|
|||
|
||||
const runtimePromise = createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: api.config as CoreConfig,
|
||||
coreConfig: api.config as OpenClawConfig,
|
||||
fullConfig: api.config,
|
||||
agentRuntime: api.runtime.agent,
|
||||
stateRuntime: api.runtime.state,
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// Voice Call plugin module implements core bridge behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
|
||||
// Narrow core runtime/config contracts consumed by the voice-call plugin.
|
||||
|
||||
/** Core config subset read by voice-call helpers. */
|
||||
export type CoreConfig = OpenClawConfig;
|
||||
|
||||
/** Agent runtime API subset exposed through the plugin SDK. */
|
||||
export type CoreAgentDeps = OpenClawPluginApi["runtime"]["agent"];
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
// Voice Call plugin module implements gateway continue operation behavior.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { CoreConfig } from "./core-bridge.js";
|
||||
import type { VoiceCallRuntime } from "./runtime.js";
|
||||
import { TELEPHONY_DEFAULT_TTS_TIMEOUT_MS } from "./telephony-tts.js";
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ type VoiceCallContinueOperationRequest = {
|
|||
/** Create a process-local operation store for gateway continue-call polling. */
|
||||
export function createVoiceCallContinueOperationStore(params: {
|
||||
config: VoiceCallConfig;
|
||||
coreConfig: CoreConfig;
|
||||
coreConfig: OpenClawConfig;
|
||||
}) {
|
||||
const operations = new Map<string, VoiceCallContinueOperation>();
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { tmpdir } from "node:os";
|
|||
import path from "node:path";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { CoreAgentDeps } from "./core-bridge.js";
|
||||
import { buildRealtimeVoiceInstructions } from "./realtime-agent-context.js";
|
||||
import { createVoiceCallBaseConfig } from "./test-fixtures.js";
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ function createConfig(overrides?: Partial<VoiceCallConfig["realtime"]>): VoiceCa
|
|||
return config;
|
||||
}
|
||||
|
||||
function createAgentRuntime(workspaceDir: string): CoreAgentDeps {
|
||||
function createAgentRuntime(workspaceDir: string): OpenClawPluginApi["runtime"]["agent"] {
|
||||
return {
|
||||
resolveAgentIdentity: vi.fn(() => ({
|
||||
name: "Claw Voice",
|
||||
|
|
@ -55,7 +55,7 @@ function createAgentRuntime(workspaceDir: string): CoreAgentDeps {
|
|||
creature: "operator",
|
||||
})),
|
||||
resolveAgentWorkspaceDir: vi.fn(() => workspaceDir),
|
||||
} as unknown as CoreAgentDeps;
|
||||
} as unknown as OpenClawPluginApi["runtime"]["agent"];
|
||||
}
|
||||
|
||||
describe("buildRealtimeVoiceInstructions", () => {
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import { buildRealtimeVoiceAgentConsultPolicyInstructions } from "openclaw/plugi
|
|||
import { root } from "openclaw/plugin-sdk/security-runtime";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { CoreAgentDeps } from "./core-bridge.js";
|
||||
|
||||
// Builds compact agent context injected into realtime voice sessions.
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ export async function buildRealtimeVoiceInstructions(params: {
|
|||
baseInstructions: string;
|
||||
config: VoiceCallConfig;
|
||||
coreConfig: OpenClawConfig;
|
||||
agentRuntime: CoreAgentDeps;
|
||||
agentRuntime: OpenClawPluginApi["runtime"]["agent"];
|
||||
agentId: string;
|
||||
}): Promise<string> {
|
||||
const { config } = params;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Voice Call tests cover response generator plugin behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { VoiceCallConfigSchema } from "./config.js";
|
||||
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
|
||||
import { generateVoiceResponse } from "./response-generator.js";
|
||||
|
||||
type TestSessionEntry = {
|
||||
|
|
@ -109,13 +110,13 @@ function createAgentRuntime(
|
|||
run: (signal: AbortSignal) => Promise<unknown>,
|
||||
) => await run(new AbortController().signal),
|
||||
);
|
||||
const resolveAgentDir = vi.fn((_cfg: CoreConfig, agentId: string) => {
|
||||
const resolveAgentDir = vi.fn((_cfg: OpenClawConfig, agentId: string) => {
|
||||
return `/tmp/openclaw/agents/${agentId}`;
|
||||
});
|
||||
const resolveAgentWorkspaceDir = vi.fn((_cfg: CoreConfig, agentId: string) => {
|
||||
const resolveAgentWorkspaceDir = vi.fn((_cfg: OpenClawConfig, agentId: string) => {
|
||||
return `/tmp/openclaw/workspace/${agentId}`;
|
||||
});
|
||||
const resolveAgentIdentity = vi.fn((_cfg: CoreConfig, agentId: string) => ({
|
||||
const resolveAgentIdentity = vi.fn((_cfg: OpenClawConfig, agentId: string) => ({
|
||||
name: `${agentId} tester`,
|
||||
}));
|
||||
const resolveStorePath = vi.fn((_store: string | undefined, params: { agentId?: string }) => {
|
||||
|
|
@ -150,7 +151,7 @@ function createAgentRuntime(
|
|||
runWithWorkAdmission,
|
||||
resolveSessionFilePath,
|
||||
},
|
||||
} as unknown as CoreAgentDeps;
|
||||
} as unknown as OpenClawPluginApi["runtime"]["agent"];
|
||||
|
||||
return {
|
||||
runtime,
|
||||
|
|
@ -192,7 +193,7 @@ function requireFirstMockCall(calls: readonly unknown[][], label: string): unkno
|
|||
async function runGenerateVoiceResponse(
|
||||
payloads: Array<Record<string, unknown>>,
|
||||
overrides?: {
|
||||
runtime?: CoreAgentDeps;
|
||||
runtime?: OpenClawPluginApi["runtime"]["agent"];
|
||||
transcript?: Array<{ speaker: "user" | "bot"; text: string }>;
|
||||
onEarlyText?: (text: string) => Promise<boolean>;
|
||||
},
|
||||
|
|
@ -200,7 +201,7 @@ async function runGenerateVoiceResponse(
|
|||
const voiceConfig = VoiceCallConfigSchema.parse({
|
||||
responseTimeoutMs: 5000,
|
||||
});
|
||||
const coreConfig = {} as CoreConfig;
|
||||
const coreConfig = {} as OpenClawConfig;
|
||||
const runtime = overrides?.runtime ?? createAgentRuntime(payloads).runtime;
|
||||
|
||||
const result = await generateVoiceResponse({
|
||||
|
|
@ -531,7 +532,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
from: "+15550001111",
|
||||
|
|
@ -580,7 +581,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
from: "+15550001111",
|
||||
|
|
@ -626,7 +627,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
sessionKey,
|
||||
|
|
@ -657,7 +658,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
sessionKey: "voice:call:call-123",
|
||||
|
|
@ -687,7 +688,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
await generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
sessionKey: "meet-room-1",
|
||||
|
|
@ -713,7 +714,7 @@ describe("generateVoiceResponse", () => {
|
|||
const generate = (sessionKey: string) =>
|
||||
generateVoiceResponse({
|
||||
voiceConfig,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
sessionKey,
|
||||
|
|
@ -773,7 +774,7 @@ describe("generateVoiceResponse", () => {
|
|||
resolveStorePath,
|
||||
sessionStore,
|
||||
} = createAgentRuntime([{ text: '{"spoken":"Default agent."}' }]);
|
||||
const coreConfig = {} as CoreConfig;
|
||||
const coreConfig = {} as OpenClawConfig;
|
||||
|
||||
await generateVoiceResponse({
|
||||
voiceConfig: VoiceCallConfigSchema.parse({ responseTimeoutMs: 5000 }),
|
||||
|
|
@ -818,7 +819,7 @@ describe("generateVoiceResponse", () => {
|
|||
resolveStorePath,
|
||||
sessionStore,
|
||||
} = createAgentRuntime([{ text: '{"spoken":"Voice agent."}' }]);
|
||||
const coreConfig = {} as CoreConfig;
|
||||
const coreConfig = {} as OpenClawConfig;
|
||||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig: VoiceCallConfigSchema.parse({
|
||||
|
|
@ -864,7 +865,7 @@ describe("generateVoiceResponse", () => {
|
|||
|
||||
await generateVoiceResponse({
|
||||
voiceConfig: VoiceCallConfigSchema.parse({ agentId: "voice", responseTimeoutMs: 5000 }),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: runtime,
|
||||
callId: "call-123",
|
||||
agentId: "support",
|
||||
|
|
@ -894,7 +895,7 @@ describe("generateVoiceResponse", () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
} as CoreConfig;
|
||||
} as OpenClawConfig;
|
||||
|
||||
const result = await generateVoiceResponse({
|
||||
voiceConfig: VoiceCallConfigSchema.parse({
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
import crypto from "node:crypto";
|
||||
import { resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import {
|
||||
applyModelOverrideWithAuthProfileCompatibility,
|
||||
ModelSelectionLockedError,
|
||||
|
|
@ -16,8 +17,8 @@ import {
|
|||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { resolveVoiceCallSessionKey, type VoiceCallConfig } from "./config.js";
|
||||
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
|
||||
import { resolveCallAgentId } from "./resolve-call-agent-id.js";
|
||||
import { resolveVoiceResponseModel } from "./response-model.js";
|
||||
|
||||
|
|
@ -25,9 +26,9 @@ type VoiceResponseParams = {
|
|||
/** Voice call config */
|
||||
voiceConfig: VoiceCallConfig;
|
||||
/** Core OpenClaw config */
|
||||
coreConfig: CoreConfig;
|
||||
coreConfig: OpenClawConfig;
|
||||
/** Injected host agent runtime */
|
||||
agentRuntime: CoreAgentDeps;
|
||||
agentRuntime: OpenClawPluginApi["runtime"]["agent"];
|
||||
/** Call ID for session tracking */
|
||||
callId: string;
|
||||
/** Persisted call session key */
|
||||
|
|
@ -70,7 +71,10 @@ function readExplicitToolsAllow(value: unknown): string[] | undefined {
|
|||
return filterStringEntries(allow);
|
||||
}
|
||||
|
||||
function resolveVoiceAgentToolsAllow(config: CoreConfig, agentId: string): string[] | undefined {
|
||||
function resolveVoiceAgentToolsAllow(
|
||||
config: OpenClawConfig,
|
||||
agentId: string,
|
||||
): string[] | undefined {
|
||||
const agents = isRecord(config.agents) ? config.agents : undefined;
|
||||
const list = Array.isArray(agents?.list) ? agents.list : [];
|
||||
const agent = list.find((entry) => isRecord(entry) && entry.id === agentId);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Voice Call tests cover response model plugin behavior.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { VoiceCallConfigSchema } from "./config.js";
|
||||
import type { CoreAgentDeps } from "./core-bridge.js";
|
||||
import { resolveVoiceResponseModel } from "./response-model.js";
|
||||
|
||||
const agentRuntime = {
|
||||
|
|
@ -9,7 +9,7 @@ const agentRuntime = {
|
|||
provider: "together",
|
||||
model: "Qwen/Qwen2.5-7B-Instruct-Turbo",
|
||||
},
|
||||
} as unknown as CoreAgentDeps;
|
||||
} as unknown as OpenClawPluginApi["runtime"]["agent"];
|
||||
|
||||
describe("resolveVoiceResponseModel", () => {
|
||||
it("falls back to the runtime default model", () => {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Voice Call plugin module implements response model behavior.
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { CoreAgentDeps } from "./core-bridge.js";
|
||||
|
||||
// Resolves the model used for voice-call text response generation.
|
||||
|
||||
/** Resolve provider/model fields from explicit voice config or agent defaults. */
|
||||
export function resolveVoiceResponseModel(params: {
|
||||
voiceConfig: VoiceCallConfig;
|
||||
agentRuntime: CoreAgentDeps;
|
||||
agentRuntime: OpenClawPluginApi["runtime"]["agent"];
|
||||
}): {
|
||||
modelRef: string;
|
||||
provider: string;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|||
import { createRequireRecord } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import type { CoreConfig } from "./core-bridge.js";
|
||||
import { createVoiceCallBaseConfig } from "./test-fixtures.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
|
|
@ -293,7 +292,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
const runtime = await createVoiceCallRuntime({
|
||||
config: createBaseConfig(),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: {} as never,
|
||||
});
|
||||
|
||||
|
|
@ -320,7 +319,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
});
|
||||
|
||||
it("passes fullConfig to the webhook server for streaming provider resolution", async () => {
|
||||
const coreConfig = { tts: { provider: "openai" } } as CoreConfig;
|
||||
const coreConfig = { tts: { provider: "openai" } } as OpenClawConfig;
|
||||
const fullConfig = {
|
||||
plugins: {
|
||||
entries: {
|
||||
|
|
@ -359,7 +358,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
const runtime = await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
fullConfig,
|
||||
agentRuntime: {
|
||||
resolveAgentIdentity,
|
||||
|
|
@ -407,7 +406,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
await expect(
|
||||
createVoiceCallRuntime({
|
||||
config: createExternalProviderConfig({ provider }),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: {} as never,
|
||||
}),
|
||||
).rejects.toThrow(`${provider} requires a publicly reachable webhook URL`);
|
||||
|
|
@ -426,7 +425,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
provider: "twilio",
|
||||
publicUrl,
|
||||
}),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: {} as never,
|
||||
}),
|
||||
).rejects.toThrow("twilio requires a publicly reachable webhook URL");
|
||||
|
|
@ -439,7 +438,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
provider: "twilio",
|
||||
publicUrl: "https://voice.example.com/voice/webhook",
|
||||
}),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: {} as never,
|
||||
});
|
||||
|
||||
|
|
@ -461,7 +460,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
provider: "twilio",
|
||||
publicUrl: "https://voice.example.com/voice/webhook",
|
||||
}),
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: {} as never,
|
||||
logger,
|
||||
});
|
||||
|
|
@ -520,7 +519,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
@ -595,7 +594,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
@ -643,7 +642,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
@ -694,7 +693,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
@ -747,7 +746,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
@ -813,7 +812,7 @@ describe("createVoiceCallRuntime lifecycle", () => {
|
|||
|
||||
await createVoiceCallRuntime({
|
||||
config,
|
||||
coreConfig: {} as CoreConfig,
|
||||
coreConfig: {} as OpenClawConfig,
|
||||
agentRuntime: agentRuntime as never,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
type ResolvedRealtimeVoiceProvider,
|
||||
} from "openclaw/plugin-sdk/realtime-voice";
|
||||
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import type { VoiceCallConfig } from "./config.js";
|
||||
import {
|
||||
resolveVoiceCallEffectiveConfig,
|
||||
|
|
@ -23,7 +24,6 @@ import {
|
|||
resolveVoiceCallConfig,
|
||||
validateProviderConfig,
|
||||
} from "./config.js";
|
||||
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
|
||||
import { CallManager } from "./manager.js";
|
||||
import type { VoiceCallProvider } from "./providers/base.js";
|
||||
import type { TwilioProvider } from "./providers/twilio.js";
|
||||
|
|
@ -257,7 +257,7 @@ function listRealtimeAgentIds(config: VoiceCallConfig, coreConfig: OpenClawConfi
|
|||
async function createRealtimeInstructionsResolver(params: {
|
||||
config: VoiceCallConfig & { agentId: string };
|
||||
coreConfig: OpenClawConfig;
|
||||
agentRuntime: CoreAgentDeps;
|
||||
agentRuntime: OpenClawPluginApi["runtime"]["agent"];
|
||||
}): Promise<(call: CallRecord) => string> {
|
||||
const genericConfig: VoiceCallConfig = {
|
||||
...params.config,
|
||||
|
|
@ -297,9 +297,9 @@ async function createRealtimeInstructionsResolver(params: {
|
|||
|
||||
export async function createVoiceCallRuntime(params: {
|
||||
config: VoiceCallConfig;
|
||||
coreConfig: CoreConfig;
|
||||
coreConfig: OpenClawConfig;
|
||||
fullConfig?: OpenClawConfig;
|
||||
agentRuntime: CoreAgentDeps;
|
||||
agentRuntime: OpenClawPluginApi["runtime"]["agent"];
|
||||
stateRuntime?: VoiceCallStateRuntime["state"];
|
||||
ttsRuntime?: TelephonyTtsRuntime;
|
||||
logger?: Logger;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import {
|
|||
readRequestBodyWithLimit,
|
||||
requestBodyErrorToText,
|
||||
} from "../api.js";
|
||||
import type { OpenClawPluginApi } from "../api.js";
|
||||
import { isAllowlistedCaller, normalizePhoneNumber } from "./allowlist.js";
|
||||
import {
|
||||
normalizeVoiceCallConfig,
|
||||
|
|
@ -30,7 +31,6 @@ import {
|
|||
resolveVoiceCallNumberRouteKeyForCall,
|
||||
type VoiceCallConfig,
|
||||
} from "./config.js";
|
||||
import type { CoreAgentDeps, CoreConfig } from "./core-bridge.js";
|
||||
import { getHeader } from "./http-headers.js";
|
||||
import type { CallManager } from "./manager.js";
|
||||
import type { MediaStreamConfig } from "./media-stream.js";
|
||||
|
|
@ -183,9 +183,9 @@ export class VoiceCallWebhookServer {
|
|||
private config: VoiceCallConfig;
|
||||
private manager: CallManager;
|
||||
private provider: VoiceCallProvider;
|
||||
private coreConfig: CoreConfig | null;
|
||||
private coreConfig: OpenClawConfig | null;
|
||||
private fullConfig: OpenClawConfig | null;
|
||||
private agentRuntime: CoreAgentDeps | null;
|
||||
private agentRuntime: OpenClawPluginApi["runtime"]["agent"] | null;
|
||||
private logger: Logger;
|
||||
private stopStaleCallReaper: (() => void) | null = null;
|
||||
private readonly webhookInFlightLimiter = createWebhookInFlightLimiter();
|
||||
|
|
@ -203,9 +203,9 @@ export class VoiceCallWebhookServer {
|
|||
config: VoiceCallConfig,
|
||||
manager: CallManager,
|
||||
provider: VoiceCallProvider,
|
||||
coreConfig?: CoreConfig,
|
||||
coreConfig?: OpenClawConfig,
|
||||
fullConfig?: OpenClawConfig,
|
||||
agentRuntime?: CoreAgentDeps,
|
||||
agentRuntime?: OpenClawPluginApi["runtime"]["agent"],
|
||||
logger?: Logger,
|
||||
) {
|
||||
this.config = normalizeVoiceCallConfig(config);
|
||||
|
|
|
|||
|
|
@ -55,56 +55,28 @@ type NativeCommandProviderLookupOptions = {
|
|||
includeBundledChannelFallback?: boolean;
|
||||
};
|
||||
|
||||
/** Resolves provider-specific native command names while preserving registry defaults. */
|
||||
function resolveNativeName(
|
||||
command: ChatCommandDefinition,
|
||||
function createNativeCommandNameMapper(
|
||||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): string | undefined {
|
||||
if (!command.nativeName) {
|
||||
return undefined;
|
||||
}
|
||||
if (!provider) {
|
||||
return command.nativeName;
|
||||
}
|
||||
const channelPlugin =
|
||||
options?.includeBundledChannelFallback === false
|
||||
? getLoadedChannelPlugin(provider)
|
||||
: getChannelPlugin(provider);
|
||||
return (
|
||||
channelPlugin?.commands?.resolveNativeCommandName?.({
|
||||
commandKey: command.key,
|
||||
defaultName: command.nativeName,
|
||||
}) ?? command.nativeName
|
||||
);
|
||||
}
|
||||
|
||||
function toNativeCommandSpec(
|
||||
command: ChatCommandDefinition,
|
||||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): NativeCommandSpec {
|
||||
const spec: NativeCommandSpec = {
|
||||
name: resolveNativeName(command, provider, options) ?? command.key,
|
||||
description: command.description,
|
||||
acceptsArgs: Boolean(command.acceptsArgs),
|
||||
args: command.args,
|
||||
): (command: ChatCommandDefinition) => Array<{ name: string; normalizedName?: string }> {
|
||||
// Registry state is lifecycle-owned, so resolve the adapter once per list or lookup operation.
|
||||
const resolveNativeCommandName = !provider
|
||||
? undefined
|
||||
: (options?.includeBundledChannelFallback === false
|
||||
? getLoadedChannelPlugin(provider)
|
||||
: getChannelPlugin(provider)
|
||||
)?.commands?.resolveNativeCommandName;
|
||||
return (command) => {
|
||||
const primary = command.nativeName
|
||||
? (resolveNativeCommandName?.({
|
||||
commandKey: command.key,
|
||||
defaultName: command.nativeName,
|
||||
}) ?? command.nativeName)
|
||||
: undefined;
|
||||
return [primary, ...(command.nativeAliases ?? [])]
|
||||
.filter((name): name is string => Boolean(name))
|
||||
.map((name) => ({ name, normalizedName: normalizeOptionalLowercaseString(name) }));
|
||||
};
|
||||
if (command.descriptionLocalizations) {
|
||||
spec.descriptionLocalizations = command.descriptionLocalizations;
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
function resolveNativeNames(
|
||||
command: ChatCommandDefinition,
|
||||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): string[] {
|
||||
const primary = resolveNativeName(command, provider, options);
|
||||
return [primary, ...(command.nativeAliases ?? [])].filter((name): name is string =>
|
||||
Boolean(name),
|
||||
);
|
||||
}
|
||||
|
||||
function supportsNativeProvider(command: ChatCommandDefinition, provider?: string): boolean {
|
||||
|
|
@ -125,28 +97,28 @@ function listNativeSpecsFromCommands(
|
|||
provider?: string,
|
||||
options?: NativeCommandProviderLookupOptions,
|
||||
): NativeCommandSpec[] {
|
||||
const mapNativeCommandNames = createNativeCommandNameMapper(provider, options);
|
||||
return commands
|
||||
.filter(
|
||||
(command) =>
|
||||
command.scope !== "text" && command.nativeName && supportsNativeProvider(command, provider),
|
||||
)
|
||||
.flatMap((command) => {
|
||||
const spec = toNativeCommandSpec(command, provider, options);
|
||||
return resolveNativeNames(command, provider, options).map((name, index) => {
|
||||
return mapNativeCommandNames(command).map(({ name }, index) => {
|
||||
const nativeSpec: NativeCommandSpec = {
|
||||
name,
|
||||
description: spec.description,
|
||||
acceptsArgs: spec.acceptsArgs,
|
||||
description: command.description,
|
||||
acceptsArgs: Boolean(command.acceptsArgs),
|
||||
};
|
||||
// Native aliases carry the same payload shape but are marked for channel registration.
|
||||
if (index > 0) {
|
||||
nativeSpec.isAlias = true;
|
||||
}
|
||||
if (spec.args) {
|
||||
nativeSpec.args = spec.args;
|
||||
if (command.args) {
|
||||
nativeSpec.args = command.args;
|
||||
}
|
||||
if (spec.descriptionLocalizations) {
|
||||
nativeSpec.descriptionLocalizations = spec.descriptionLocalizations;
|
||||
if (command.descriptionLocalizations) {
|
||||
nativeSpec.descriptionLocalizations = command.descriptionLocalizations;
|
||||
}
|
||||
return nativeSpec;
|
||||
});
|
||||
|
|
@ -222,13 +194,12 @@ export function findCommandByNativeName(
|
|||
if (!normalized) {
|
||||
return undefined;
|
||||
}
|
||||
const mapNativeCommandNames = createNativeCommandNameMapper(provider, options);
|
||||
return getChatCommands().find(
|
||||
(command) =>
|
||||
command.scope !== "text" &&
|
||||
supportsNativeProvider(command, provider) &&
|
||||
[resolveNativeName(command, provider, options), ...(command.nativeAliases ?? [])].some(
|
||||
(nameLocal) => normalizeOptionalLowercaseString(nameLocal) === normalized,
|
||||
),
|
||||
mapNativeCommandNames(command).some(({ normalizedName }) => normalizedName === normalized),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// Public channel ingress/message-access barrel. Keep this as the narrow import
|
||||
// point for callers that need access decisions without plugin internals.
|
||||
export { defineStableChannelIngressIdentity } from "./runtime-identity.js";
|
||||
export {
|
||||
channelIngressRoutes,
|
||||
createChannelIngressResolver,
|
||||
resolveChannelMessageIngress,
|
||||
resolveStableChannelMessageIngress,
|
||||
} from "./runtime.js";
|
||||
export { readChannelIngressStoreAllowFromForDmPolicy } from "./store-allow-from.js";
|
||||
|
||||
export type {
|
||||
ChannelIngressAccessGroupMembershipResolver,
|
||||
ChannelIngressCommandPresetInput,
|
||||
ChannelIngressConfigInput,
|
||||
ChannelIngressContextBinding,
|
||||
ChannelIngressEventPresetInput,
|
||||
ChannelIngressIdentityAlias,
|
||||
ChannelIngressIdentityDescriptor,
|
||||
ChannelIngressIdentityField,
|
||||
ChannelIngressIdentitySubjectInput,
|
||||
ChannelIngressRouteAccess,
|
||||
ChannelIngressRouteDescriptor,
|
||||
ChannelIngressResolver,
|
||||
ChannelIngressResolverMessageParams,
|
||||
ChannelMessageIngressCommandInput,
|
||||
CreateChannelIngressResolverParams,
|
||||
ResolvedChannelMessageIngress,
|
||||
ResolveChannelMessageIngressParams,
|
||||
ResolveStableChannelMessageIngressParams,
|
||||
StableChannelIngressIdentityParams,
|
||||
} from "./runtime-types.js";
|
||||
export type * from "./types.js";
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
// Message access tests cover channel message visibility and permission helpers.
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { decideChannelIngress } from "./decision.js";
|
||||
import { resolveChannelIngressState } from "./state.js";
|
||||
import type {
|
||||
ChannelIngressPolicyInput,
|
||||
ChannelIngressStateInput,
|
||||
InternalChannelIngressAdapter,
|
||||
InternalChannelIngressSubject,
|
||||
} from "./index.js";
|
||||
import { resolveChannelIngressState } from "./state.js";
|
||||
} from "./types.js";
|
||||
|
||||
const subject = (value: string): InternalChannelIngressSubject => ({
|
||||
identifiers: [{ opaqueId: "subject-1", kind: "stable-id", value }],
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
// Public barrel for channel message delivery, live preview, receipt, receive, and recovery
|
||||
// contracts used by channel plugins and core delivery code.
|
||||
export { deriveDurableFinalDeliveryRequirements } from "./capabilities.js";
|
||||
export { defineChannelMessageAdapter } from "./adapter.js";
|
||||
export { createChannelMessageAdapterFromOutbound } from "./outbound-bridge.js";
|
||||
export { createDurableInboundReceiveJournalFromQueue } from "./durable-receive.js";
|
||||
export { INGRESS_CLAIM_PROCESS_ID, processPidFromOwnerId } from "./ingress-claim-owner.js";
|
||||
export {
|
||||
bindIngressLifecycleToReplyOptions,
|
||||
createChannelIngressDrain,
|
||||
DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
||||
} from "./ingress-drain.js";
|
||||
export {
|
||||
CHANNEL_INGRESS_RETENTION_DEFAULTS,
|
||||
createChannelIngressError,
|
||||
createChannelIngressMonitor,
|
||||
} from "./ingress-monitor.js";
|
||||
export {
|
||||
DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS,
|
||||
DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
||||
} from "./ingress-retry-policy.js";
|
||||
|
||||
export {
|
||||
verifyChannelMessageAdapterCapabilityProofs,
|
||||
verifyChannelMessageLiveCapabilityAdapterProofs,
|
||||
verifyChannelMessageLiveFinalizerProofs,
|
||||
verifyChannelMessageReceiveAckPolicyAdapterProofs,
|
||||
verifyDurableFinalCapabilityProofs,
|
||||
} from "./contracts.js";
|
||||
export {
|
||||
createPreviewMessageReceipt,
|
||||
defineFinalizableLivePreviewAdapter,
|
||||
deliverWithFinalizableLivePreviewAdapter,
|
||||
} from "./live.js";
|
||||
export {
|
||||
createMessageReceiptFromOutboundResults,
|
||||
listMessageReceiptPlatformIds,
|
||||
resolveMessageReceiptPrimaryId,
|
||||
} from "./receipt.js";
|
||||
export { createMessageReceiveContext } from "./receive.js";
|
||||
export {
|
||||
createChannelReplyPipeline,
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
resolveChannelSourceReplyDeliveryMode,
|
||||
} from "./reply-pipeline.js";
|
||||
export type { ChannelIngressDrain } from "./ingress-drain.js";
|
||||
export type {
|
||||
ChannelIngressMonitorDeliveryResult,
|
||||
ChannelIngressMonitorLifecycle,
|
||||
} from "./ingress-monitor.js";
|
||||
export type {
|
||||
ChannelIngressQueue,
|
||||
ChannelIngressQueueClaim,
|
||||
ChannelIngressQueueClaimRef,
|
||||
ChannelIngressQueueCorruptClaim,
|
||||
ChannelIngressQueueRecord,
|
||||
} from "./ingress-queue.js";
|
||||
export type { MessageAckPolicy, MessageReceiveContext } from "./receive.js";
|
||||
export type {
|
||||
ChannelMessageAdapterShape,
|
||||
ChannelMessageDurableFinalAdapter,
|
||||
ChannelMessageSendMediaContext,
|
||||
ChannelMessageSendPayloadContext,
|
||||
ChannelMessageSendResult,
|
||||
ChannelMessageSendTextContext,
|
||||
ChannelMessageUnknownSendContext,
|
||||
ChannelMessageUnknownSendReconciliationResult,
|
||||
MessageReceipt,
|
||||
MessageReceiptPart,
|
||||
MessageReceiptPartKind,
|
||||
MessageReceiptSourceResult,
|
||||
} from "./types.js";
|
||||
|
|
@ -109,15 +109,6 @@ vi.mock("../../config/paths.js", () => ({
|
|||
resolveIsNixMode: resolveIsNixModeMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../commands/gateway-install-token.persist.runtime.js", () => ({
|
||||
readConfigFileSnapshot: readConfigFileSnapshotMock,
|
||||
readConfigFileSnapshotForWrite: vi.fn(async () => ({
|
||||
snapshot: await readConfigFileSnapshotMock(),
|
||||
writeOptions: { expectedConfigPath: "/tmp/openclaw.json" },
|
||||
})),
|
||||
replaceConfigFile: replaceConfigFileMock,
|
||||
}));
|
||||
|
||||
vi.mock("../../config/types.secrets.js", () => ({
|
||||
hasConfiguredSecretInput: hasConfiguredSecretInputMock,
|
||||
resolveSecretInputRef: resolveSecretInputRefMock,
|
||||
|
|
|
|||
|
|
@ -309,6 +309,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
|
|||
explicitToken: opts.token,
|
||||
autoGenerateWhenMissing: true,
|
||||
persistGeneratedToken: true,
|
||||
persistence: { readConfigFileSnapshotForWrite, replaceConfigFile },
|
||||
});
|
||||
if (tokenResolution.unavailableReason) {
|
||||
fail(`Gateway install blocked: ${tokenResolution.unavailableReason}`);
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
/** Runtime persistence seam for gateway install token config writes. */
|
||||
export { readConfigFileSnapshotForWrite } from "../config/io.js";
|
||||
export { replaceConfigFile } from "../config/mutate.js";
|
||||
|
|
@ -19,11 +19,6 @@ const resolveSecretRefValuesMock = vi.hoisted(() => vi.fn());
|
|||
const secretRefKeyMock = vi.hoisted(() => vi.fn(() => "env:default:OPENCLAW_GATEWAY_TOKEN"));
|
||||
const randomTokenMock = vi.hoisted(() => vi.fn(() => "generated-token"));
|
||||
|
||||
vi.mock("./gateway-install-token.persist.runtime.js", () => ({
|
||||
readConfigFileSnapshotForWrite: readConfigFileSnapshotForWriteMock,
|
||||
replaceConfigFile: replaceConfigFileMock,
|
||||
}));
|
||||
|
||||
vi.mock("../gateway/auth.js", () => ({
|
||||
resolveGatewayAuth: resolveGatewayAuthMock,
|
||||
}));
|
||||
|
|
@ -32,7 +27,8 @@ vi.mock("../gateway/auth-install-policy.js", () => ({
|
|||
shouldRequireGatewayTokenForInstall: shouldRequireGatewayTokenForInstallMock,
|
||||
}));
|
||||
|
||||
vi.mock("../secrets/ref-contract.js", () => ({
|
||||
vi.mock("../secrets/ref-contract.js", async (importOriginal) => ({
|
||||
...(await importOriginal<typeof import("../secrets/ref-contract.js")>()),
|
||||
secretRefKey: secretRefKeyMock,
|
||||
}));
|
||||
|
||||
|
|
@ -52,6 +48,11 @@ function firstReplaceConfigRequest(): unknown {
|
|||
return call[0];
|
||||
}
|
||||
|
||||
const persistence = {
|
||||
readConfigFileSnapshotForWrite: readConfigFileSnapshotForWriteMock,
|
||||
replaceConfigFile: replaceConfigFileMock,
|
||||
};
|
||||
|
||||
describe("resolveGatewayInstallToken", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
|
@ -135,6 +136,7 @@ describe("resolveGatewayInstallToken", () => {
|
|||
env: {} as NodeJS.ProcessEnv,
|
||||
autoGenerateWhenMissing: true,
|
||||
persistGeneratedToken: true,
|
||||
persistence,
|
||||
});
|
||||
|
||||
expect(result.token).toBeUndefined();
|
||||
|
|
@ -171,6 +173,7 @@ describe("resolveGatewayInstallToken", () => {
|
|||
env: {} as NodeJS.ProcessEnv,
|
||||
autoGenerateWhenMissing: true,
|
||||
persistGeneratedToken: true,
|
||||
persistence,
|
||||
});
|
||||
|
||||
expect(result.warnings.join("\n")).toContain("saving to config");
|
||||
|
|
@ -214,6 +217,7 @@ describe("resolveGatewayInstallToken", () => {
|
|||
env: {} as NodeJS.ProcessEnv,
|
||||
autoGenerateWhenMissing: true,
|
||||
persistGeneratedToken: true,
|
||||
persistence,
|
||||
});
|
||||
|
||||
expect(result.token).toBeUndefined();
|
||||
|
|
@ -240,6 +244,7 @@ describe("resolveGatewayInstallToken", () => {
|
|||
env: {} as NodeJS.ProcessEnv,
|
||||
autoGenerateWhenMissing: true,
|
||||
persistGeneratedToken: true,
|
||||
persistence,
|
||||
});
|
||||
|
||||
expect(result.token).toBeUndefined();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
/** Resolves the gateway token used when installing or updating the managed service. */
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { ConfigWriteOptions } from "../config/io.js";
|
||||
import { readConfigFileSnapshotForWrite, type ConfigWriteOptions } from "../config/io.js";
|
||||
import { replaceConfigFile } from "../config/mutate.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import type { ConfigFileSnapshot } from "../config/types.openclaw.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
|
|
@ -13,11 +14,17 @@ import {
|
|||
formatUnsafeGatewayTailscaleNoAuthMessage,
|
||||
isUnsafeGatewayTailscaleNoAuth,
|
||||
} from "../shared/gateway-tailscale-auth-policy.js";
|
||||
import {
|
||||
import { randomToken } from "./random-token.js";
|
||||
|
||||
type GatewayInstallTokenPersistence = {
|
||||
readConfigFileSnapshotForWrite: typeof readConfigFileSnapshotForWrite;
|
||||
replaceConfigFile: typeof replaceConfigFile;
|
||||
};
|
||||
|
||||
const defaultGatewayInstallTokenPersistence: GatewayInstallTokenPersistence = {
|
||||
readConfigFileSnapshotForWrite,
|
||||
replaceConfigFile,
|
||||
} from "./gateway-install-token.persist.runtime.js";
|
||||
import { randomToken } from "./random-token.js";
|
||||
};
|
||||
|
||||
type GatewayInstallTokenOptions = {
|
||||
config: OpenClawConfig;
|
||||
|
|
@ -27,6 +34,7 @@ type GatewayInstallTokenOptions = {
|
|||
explicitToken?: string;
|
||||
autoGenerateWhenMissing?: boolean;
|
||||
persistGeneratedToken?: boolean;
|
||||
persistence?: GatewayInstallTokenPersistence;
|
||||
};
|
||||
|
||||
type GatewayInstallTokenResolution = {
|
||||
|
|
@ -42,6 +50,7 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
|||
configSnapshot?: ConfigFileSnapshot;
|
||||
configWriteOptions?: ConfigWriteOptions;
|
||||
warnings: string[];
|
||||
persistence: GatewayInstallTokenPersistence;
|
||||
}): Promise<string | undefined> {
|
||||
try {
|
||||
const prepared =
|
||||
|
|
@ -50,7 +59,7 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
|||
snapshot: params.configSnapshot,
|
||||
writeOptions: params.configWriteOptions,
|
||||
}
|
||||
: await readConfigFileSnapshotForWrite();
|
||||
: await params.persistence.readConfigFileSnapshotForWrite();
|
||||
const snapshot = params.configSnapshot ?? prepared.snapshot;
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
params.warnings.push(
|
||||
|
|
@ -70,7 +79,7 @@ async function maybePersistAutoGeneratedGatewayInstallToken(params: {
|
|||
: normalizeOptionalString(baseConfig.gateway.auth.token);
|
||||
// Only persist a generated plaintext token when config has no token or SecretRef.
|
||||
if (!existingTokenRef && !baseConfigToken) {
|
||||
await replaceConfigFile({
|
||||
await params.persistence.replaceConfigFile({
|
||||
nextConfig: {
|
||||
...baseConfig,
|
||||
gateway: {
|
||||
|
|
@ -202,6 +211,7 @@ export async function resolveGatewayInstallToken(
|
|||
configSnapshot: options.configSnapshot,
|
||||
configWriteOptions: options.configWriteOptions,
|
||||
warnings,
|
||||
persistence: options.persistence ?? defaultGatewayInstallTokenPersistence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
/** Runtime seams for loading model command config and secret target ids. */
|
||||
export { getModelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
export {
|
||||
getRuntimeConfig,
|
||||
getRuntimeConfigSourceSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
} from "../../config/config.js";
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
/** Config loader for model commands with command-scoped secret resolution. */
|
||||
import { resolveCommandConfigWithSecrets } from "../../cli/command-config-resolution.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import { getModelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js";
|
||||
import {
|
||||
getRuntimeConfig,
|
||||
getRuntimeConfigSourceSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
type OpenClawConfig,
|
||||
getModelsCommandSecretTargetIds,
|
||||
} from "./load-config.runtime.js";
|
||||
} from "../../config/config.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
|
||||
/** Source and resolved config pair returned by model command config loading. */
|
||||
type LoadedModelsConfig = {
|
||||
|
|
|
|||
|
|
@ -23,15 +23,11 @@ import {
|
|||
import { subscribePluginSessionsChanged } from "../plugins/gateway-events.js";
|
||||
|
||||
const persistGatewaySessionLifecycleEventMock = vi.fn();
|
||||
const loadGatewaySessionLifecycleSnapshotMock = vi.hoisted(() => vi.fn());
|
||||
const logErrorMock = vi.fn();
|
||||
const normalizeLiveAssistantBufferedTextMock = vi.hoisted(() => vi.fn());
|
||||
const loadGatewaySessionRow = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("./server-chat.persist-session-lifecycle.runtime.js", () => ({
|
||||
persistGatewaySessionLifecycleEvent: (...args: unknown[]) =>
|
||||
persistGatewaySessionLifecycleEventMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
logError: (...args: unknown[]) => logErrorMock(...args),
|
||||
}));
|
||||
|
|
@ -59,10 +55,6 @@ vi.mock("../infra/heartbeat-visibility.js", () => ({
|
|||
})),
|
||||
}));
|
||||
|
||||
vi.mock("./server-chat.load-gateway-session-row.runtime.js", () => ({
|
||||
loadGatewaySessionLifecycleSnapshot: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("./session-utils.js", () => {
|
||||
const loadSessionEntry = vi.fn(() => ({
|
||||
cfg: {},
|
||||
|
|
@ -76,6 +68,8 @@ vi.mock("./session-utils.js", () => {
|
|||
return {
|
||||
loadSessionEntry,
|
||||
loadGatewaySessionEntryReadOnly: loadSessionEntry,
|
||||
loadGatewaySessionLifecycleSnapshot: (...args: unknown[]) =>
|
||||
loadGatewaySessionLifecycleSnapshotMock(...args),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -97,7 +91,6 @@ import {
|
|||
resolveChatErrorKindFromError,
|
||||
type AgentEventHandlerOptions,
|
||||
} from "./server-chat.js";
|
||||
import { loadGatewaySessionLifecycleSnapshot } from "./server-chat.load-gateway-session-row.runtime.js";
|
||||
import { loadSessionEntry } from "./session-utils.js";
|
||||
|
||||
function waitForFast<T>(
|
||||
|
|
@ -129,7 +122,7 @@ describe("agent event handler", () => {
|
|||
legacyKey: undefined,
|
||||
});
|
||||
vi.mocked(loadGatewaySessionRow).mockReset().mockReturnValue(null);
|
||||
vi.mocked(loadGatewaySessionLifecycleSnapshot)
|
||||
loadGatewaySessionLifecycleSnapshotMock
|
||||
.mockReset()
|
||||
.mockImplementation((sessionKey, options) => ({
|
||||
row: options
|
||||
|
|
@ -183,7 +176,8 @@ describe("agent event handler", () => {
|
|||
toolEventRecipients,
|
||||
sessionEventSubscribers,
|
||||
sessionMessageSubscribers,
|
||||
loadGatewaySessionLifecycleSnapshotForEvent: loadGatewaySessionLifecycleSnapshot,
|
||||
loadGatewaySessionLifecycleSnapshotForEvent: loadGatewaySessionLifecycleSnapshotMock,
|
||||
persistGatewaySessionLifecycleEventForEvent: persistGatewaySessionLifecycleEventMock,
|
||||
lifecycleErrorRetryGraceMs: params?.lifecycleErrorRetryGraceMs,
|
||||
isChatSendRunActive: params?.isChatSendRunActive,
|
||||
clearTrackedActiveRun: params?.clearTrackedActiveRun ?? clearTrackedActiveRun,
|
||||
|
|
@ -2384,7 +2378,7 @@ describe("agent event handler", () => {
|
|||
])(
|
||||
"projects older lifecycle timestamps only for the owning run ($eventRunId)",
|
||||
async ({ eventRunId, expectedStartedAt }) => {
|
||||
vi.mocked(loadGatewaySessionLifecycleSnapshot).mockReturnValue({
|
||||
loadGatewaySessionLifecycleSnapshotMock.mockReturnValue({
|
||||
lifecycleRunId: "run-current",
|
||||
row: {
|
||||
key: "session-owned",
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
// Runtime barrel for loading Gateway session rows from chat paths without
|
||||
// pulling the rest of session-utils into static startup imports.
|
||||
export { loadGatewaySessionLifecycleSnapshot } from "./session-utils.js";
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
// Runtime barrel for persisting session lifecycle events from chat paths while
|
||||
// keeping lifecycle-state behind a narrow lazy import boundary.
|
||||
export { persistGatewaySessionLifecycleEvent } from "./session-lifecycle-state.js";
|
||||
|
|
@ -52,18 +52,20 @@ import type {
|
|||
SessionMessageSubscriberRegistry,
|
||||
ToolEventRecipientRegistry,
|
||||
} from "./server-chat-state.js";
|
||||
import { loadGatewaySessionLifecycleSnapshot } from "./server-chat.load-gateway-session-row.runtime.js";
|
||||
import { persistGatewaySessionLifecycleEvent } from "./server-chat.persist-session-lifecycle.runtime.js";
|
||||
import { hasSessionChangeReceivers } from "./session-change-receivers.js";
|
||||
import { buildGatewaySessionEventRow } from "./session-event-payload.js";
|
||||
import {
|
||||
deriveGatewaySessionLifecycleProjectionPatch,
|
||||
isRestartRecoveryLifecycleEvent,
|
||||
isStaleLifecycleEventForSession,
|
||||
persistGatewaySessionLifecycleEvent,
|
||||
} from "./session-lifecycle-state.js";
|
||||
import { tryResolveSessionCompatibilityOwnerAgentId } from "./session-request-agent.js";
|
||||
import { resolveSessionSubscriptionKeys } from "./session-subscription-keys.js";
|
||||
import { loadGatewaySessionEntryReadOnly } from "./session-utils.js";
|
||||
import {
|
||||
loadGatewaySessionEntryReadOnly,
|
||||
loadGatewaySessionLifecycleSnapshot,
|
||||
} from "./session-utils.js";
|
||||
import { formatForLog } from "./ws-log.js";
|
||||
|
||||
export {
|
||||
|
|
@ -338,6 +340,7 @@ export type AgentEventHandlerOptions = {
|
|||
sessionEventSubscribers: SessionEventSubscriberRegistry;
|
||||
sessionMessageSubscribers: SessionMessageSubscriberRegistry;
|
||||
loadGatewaySessionLifecycleSnapshotForEvent?: typeof loadGatewaySessionLifecycleSnapshot;
|
||||
persistGatewaySessionLifecycleEventForEvent?: typeof persistGatewaySessionLifecycleEvent;
|
||||
lifecycleErrorRetryGraceMs?: number;
|
||||
isChatSendRunActive?: (runId: string) => boolean;
|
||||
clearTrackedActiveRun?: (params: {
|
||||
|
|
@ -443,6 +446,7 @@ export function createAgentEventHandler({
|
|||
sessionEventSubscribers,
|
||||
sessionMessageSubscribers,
|
||||
loadGatewaySessionLifecycleSnapshotForEvent = loadGatewaySessionLifecycleSnapshot,
|
||||
persistGatewaySessionLifecycleEventForEvent = persistGatewaySessionLifecycleEvent,
|
||||
lifecycleErrorRetryGraceMs = AGENT_LIFECYCLE_ERROR_RETRY_GRACE_MS,
|
||||
isChatSendRunActive = () => false,
|
||||
clearTrackedActiveRun,
|
||||
|
|
@ -876,7 +880,7 @@ export function createAgentEventHandler({
|
|||
if (sessionKey) {
|
||||
clearTrackedActiveRun?.({ runId: evt.runId, clientRunId, sessionKey });
|
||||
if (!suppressRestartRecoveryProjection && projectSessionLifecycle) {
|
||||
const persistence = persistGatewaySessionLifecycleEvent({
|
||||
const persistence = persistGatewaySessionLifecycleEventForEvent({
|
||||
sessionKey,
|
||||
agentId: sessionAgentId,
|
||||
event: evt,
|
||||
|
|
@ -1753,7 +1757,7 @@ export function createAgentEventHandler({
|
|||
}
|
||||
|
||||
if (projectSessionLifecycle && sessionKey && lifecyclePhase === "start") {
|
||||
void persistGatewaySessionLifecycleEvent({
|
||||
void persistGatewaySessionLifecycleEventForEvent({
|
||||
sessionKey,
|
||||
agentId: sessionAgentId,
|
||||
event: evt,
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ const CORE_GATEWAY_HANDLER_MODULES = {
|
|||
),
|
||||
diagnostics: () =>
|
||||
import("./server-methods/diagnostics.js").then((module) => module.diagnosticsHandlers),
|
||||
doctor: () => import("./server-methods/doctor.js").then((module) => module.doctorHandlers),
|
||||
doctor: () =>
|
||||
import("./server-methods/doctor.js").then((module) => module.createDoctorHandlers()),
|
||||
environments: () =>
|
||||
import("./server-methods/environments.js").then((module) => module.environmentsHandlers),
|
||||
worktrees: () =>
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
/**
|
||||
* Lazy boundary for doctor memory-core repair helpers.
|
||||
*
|
||||
* Doctor tests mock this file so the gateway method does not import bundled
|
||||
* memory-core runtime code until a repair action actually needs it.
|
||||
*/
|
||||
export {
|
||||
dedupeDreamDiaryEntries,
|
||||
loadShortTermPromotionDreamingStats,
|
||||
previewGroundedRemMarkdown,
|
||||
removeBackfillDiaryEntries,
|
||||
removeGroundedShortTermCandidates,
|
||||
repairDreamingArtifacts,
|
||||
writeBackfillDiaryEntries,
|
||||
} from "../../plugin-sdk/memory-core-bundled-runtime.js";
|
||||
|
|
@ -58,7 +58,9 @@ vi.mock("../../plugins/memory-runtime.js", () => ({
|
|||
getActiveMemorySearchManagerCore: getMemorySearchManager,
|
||||
}));
|
||||
|
||||
vi.mock("./doctor.memory-core-runtime.js", () => ({
|
||||
import { createDoctorHandlers } from "./doctor.js";
|
||||
|
||||
const doctorHandlers = createDoctorHandlers({
|
||||
dedupeDreamDiaryEntries,
|
||||
loadShortTermPromotionDreamingStats,
|
||||
previewGroundedRemMarkdown,
|
||||
|
|
@ -66,9 +68,7 @@ vi.mock("./doctor.memory-core-runtime.js", () => ({
|
|||
removeBackfillDiaryEntries,
|
||||
removeGroundedShortTermCandidates,
|
||||
repairDreamingArtifacts,
|
||||
}));
|
||||
|
||||
import { doctorHandlers } from "./doctor.js";
|
||||
});
|
||||
|
||||
const makeRuntimeContext = () => ({ getRuntimeConfig: () => getRuntimeConfig() });
|
||||
|
||||
|
|
|
|||
|
|
@ -21,20 +21,23 @@ import {
|
|||
resolveMemoryDreamingWorkspaces,
|
||||
resolveMemoryRemDreamingConfig,
|
||||
} from "../../memory-host-sdk/dreaming.js";
|
||||
import * as defaultMemoryCoreRuntime from "../../plugin-sdk/memory-core-bundled-runtime.js";
|
||||
import { getActiveMemorySearchManagerCore } from "../../plugins/memory-runtime.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { formatError } from "../server-utils.js";
|
||||
import {
|
||||
dedupeDreamDiaryEntries,
|
||||
loadShortTermPromotionDreamingStats,
|
||||
previewGroundedRemMarkdown,
|
||||
removeBackfillDiaryEntries,
|
||||
removeGroundedShortTermCandidates,
|
||||
repairDreamingArtifacts,
|
||||
writeBackfillDiaryEntries,
|
||||
} from "./doctor.memory-core-runtime.js";
|
||||
import type { GatewayRequestContext, GatewayRequestHandlers, RespondFn } from "./types.js";
|
||||
|
||||
type DoctorMemoryCoreRuntime = Pick<
|
||||
typeof defaultMemoryCoreRuntime,
|
||||
| "dedupeDreamDiaryEntries"
|
||||
| "loadShortTermPromotionDreamingStats"
|
||||
| "previewGroundedRemMarkdown"
|
||||
| "removeBackfillDiaryEntries"
|
||||
| "removeGroundedShortTermCandidates"
|
||||
| "repairDreamingArtifacts"
|
||||
| "writeBackfillDiaryEntries"
|
||||
>;
|
||||
|
||||
const MANAGED_DEEP_SLEEP_CRON_NAME = "Memory Dreaming Promotion";
|
||||
const MANAGED_DEEP_SLEEP_CRON_TAG = "[managed-by=memory-core.short-term-promotion]";
|
||||
const DEEP_SLEEP_SYSTEM_EVENT_TEXT = "__openclaw_memory_core_short_term_promotion_dream__";
|
||||
|
|
@ -381,6 +384,7 @@ function trimDreamingEntries(
|
|||
async function loadDreamingStoreStats(
|
||||
workspaceDir: string,
|
||||
nowMs: number,
|
||||
loadShortTermPromotionDreamingStats: DoctorMemoryCoreRuntime["loadShortTermPromotionDreamingStats"],
|
||||
timezone?: string,
|
||||
): Promise<DreamingStoreStats> {
|
||||
try {
|
||||
|
|
@ -699,7 +703,9 @@ const SKIPPED_MEMORY_EMBEDDING_PROBE = {
|
|||
error: "memory embedding readiness not checked; run `openclaw memory status --deep` to probe",
|
||||
} as const;
|
||||
|
||||
export const doctorHandlers: GatewayRequestHandlers = {
|
||||
export const createDoctorHandlers = (
|
||||
memoryCoreRuntime: DoctorMemoryCoreRuntime = defaultMemoryCoreRuntime,
|
||||
): GatewayRequestHandlers => ({
|
||||
"doctor.memory.status": async ({ respond, context, params }) => {
|
||||
const resolved = resolveDoctorMemoryAgent(context, params, respond);
|
||||
if (!resolved) {
|
||||
|
|
@ -755,7 +761,12 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
? mergeDreamingStoreStats(
|
||||
await Promise.all(
|
||||
allWorkspaces.map((entry) =>
|
||||
loadDreamingStoreStats(entry, nowMs, dreamingConfig.timezone),
|
||||
loadDreamingStoreStats(
|
||||
entry,
|
||||
nowMs,
|
||||
memoryCoreRuntime.loadShortTermPromotionDreamingStats,
|
||||
dreamingConfig.timezone,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
@ -850,7 +861,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
respond(true, payload, undefined);
|
||||
return;
|
||||
}
|
||||
const grounded = await previewGroundedRemMarkdown({
|
||||
const grounded = await memoryCoreRuntime.previewGroundedRemMarkdown({
|
||||
workspaceDir,
|
||||
inputPaths: sourceFiles,
|
||||
});
|
||||
|
|
@ -871,7 +882,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
};
|
||||
})
|
||||
.filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
||||
const written = await writeBackfillDiaryEntries({
|
||||
const written = await memoryCoreRuntime.writeBackfillDiaryEntries({
|
||||
workspaceDir,
|
||||
entries,
|
||||
timezone: remConfig.timezone,
|
||||
|
|
@ -894,7 +905,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
return;
|
||||
}
|
||||
const { agentId, workspaceDir } = target;
|
||||
const removed = await removeBackfillDiaryEntries({ workspaceDir });
|
||||
const removed = await memoryCoreRuntime.removeBackfillDiaryEntries({ workspaceDir });
|
||||
const dreamDiary = await readDreamDiary(workspaceDir);
|
||||
const payload: DoctorMemoryDreamActionPayload = {
|
||||
agentId,
|
||||
|
|
@ -911,7 +922,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
return;
|
||||
}
|
||||
const { agentId, workspaceDir } = target;
|
||||
const removed = await removeGroundedShortTermCandidates({ workspaceDir });
|
||||
const removed = await memoryCoreRuntime.removeGroundedShortTermCandidates({ workspaceDir });
|
||||
const payload: DoctorMemoryDreamActionPayload = {
|
||||
agentId,
|
||||
action: "resetGroundedShortTerm",
|
||||
|
|
@ -925,7 +936,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
return;
|
||||
}
|
||||
const { agentId, workspaceDir } = target;
|
||||
const repair = await repairDreamingArtifacts({ workspaceDir });
|
||||
const repair = await memoryCoreRuntime.repairDreamingArtifacts({ workspaceDir });
|
||||
const payload: DoctorMemoryDreamActionPayload = {
|
||||
agentId,
|
||||
action: "repairDreamingArtifacts",
|
||||
|
|
@ -944,7 +955,7 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
return;
|
||||
}
|
||||
const { agentId, workspaceDir } = target;
|
||||
const dedupe = await dedupeDreamDiaryEntries({ workspaceDir });
|
||||
const dedupe = await memoryCoreRuntime.dedupeDreamDiaryEntries({ workspaceDir });
|
||||
const dreamDiary = await readDreamDiary(workspaceDir);
|
||||
const payload: DoctorMemoryDreamActionPayload = {
|
||||
agentId,
|
||||
|
|
@ -957,5 +968,5 @@ export const doctorHandlers: GatewayRequestHandlers = {
|
|||
};
|
||||
respond(true, payload, undefined);
|
||||
},
|
||||
};
|
||||
});
|
||||
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */
|
||||
|
|
|
|||
|
|
@ -38,16 +38,17 @@ describe.skipIf(process.platform === "win32")("Gateway agent CLI shim", () => {
|
|||
mode: 0o700,
|
||||
});
|
||||
|
||||
const shim = await prepareGatewayAgentCliShim({
|
||||
await prepareGatewayAgentCliShim({
|
||||
env: testCase.profile ? { OPENCLAW_PROFILE: testCase.profile } : {},
|
||||
invocation: { command: process.execPath, args: [entryPath], cwd: root },
|
||||
stateDir,
|
||||
});
|
||||
const shimBinDir = path.join(stateDir, "tmp", "agent-cli");
|
||||
const config = {
|
||||
tools: { exec: { pathPrepend: [staleBinDir] } },
|
||||
} satisfies OpenClawConfig;
|
||||
const execConfig = resolveExecToolConfig({ cfg: config });
|
||||
expect(execConfig.pathPrepend?.slice(0, 2)).toEqual([shim.binDir, staleBinDir]);
|
||||
expect(execConfig.pathPrepend?.slice(0, 2)).toEqual([shimBinDir, staleBinDir]);
|
||||
|
||||
process.env.OPENCLAW_EXEC_SHELL_SNAPSHOT = "0";
|
||||
process.env.PATH = `${staleBinDir}${path.delimiter}${process.env.PATH ?? ""}`;
|
||||
|
|
@ -67,7 +68,7 @@ describe.skipIf(process.platform === "win32")("Gateway agent CLI shim", () => {
|
|||
expect(JSON.parse(readExecText(result))).toEqual({
|
||||
source: "gateway",
|
||||
args: testCase.expectedArgs,
|
||||
pathHead: shim.binDir,
|
||||
pathHead: shimBinDir,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -75,7 +76,7 @@ describe.skipIf(process.platform === "win32")("Gateway agent CLI shim", () => {
|
|||
|
||||
it("renders a Windows PATH launcher for the running CLI", async () => {
|
||||
await withTempDir("openclaw-agent-cli-shim-win-", async (root) => {
|
||||
const result = await prepareGatewayAgentCliShim({
|
||||
await prepareGatewayAgentCliShim({
|
||||
env: { OPENCLAW_PROFILE: "work" },
|
||||
invocation: {
|
||||
command: "C:\\Program Files\\nodejs\\node.exe",
|
||||
|
|
@ -86,8 +87,8 @@ it("renders a Windows PATH launcher for the running CLI", async () => {
|
|||
stateDir: root,
|
||||
});
|
||||
|
||||
expect(path.basename(result.executablePath)).toBe("openclaw.cmd");
|
||||
expect(await fs.readFile(result.executablePath, "utf8")).toBe(
|
||||
const executablePath = path.join(root, "tmp", "agent-cli", "openclaw.cmd");
|
||||
expect(await fs.readFile(executablePath, "utf8")).toBe(
|
||||
'@echo off\r\n"C:\\Program Files\\nodejs\\node.exe" C:\\OpenClaw\\dist\\index.js --profile work %*\r\n',
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ export async function prepareGatewayAgentCliShim(
|
|||
platform?: NodeJS.Platform;
|
||||
stateDir?: string;
|
||||
} = {},
|
||||
): Promise<{ binDir: string; executablePath: string }> {
|
||||
): Promise<void> {
|
||||
const env = options.env ?? process.env;
|
||||
const platform = options.platform ?? process.platform;
|
||||
const invocation = options.invocation ?? resolveCurrentOpenClawCliInvocation([]);
|
||||
|
|
@ -70,7 +70,6 @@ export async function prepareGatewayAgentCliShim(
|
|||
tempPrefix: "openclaw-agent-cli",
|
||||
});
|
||||
gatewayAgentCliState.binDir = binDir;
|
||||
return { binDir, executablePath };
|
||||
}
|
||||
|
||||
/** Clear a prepared launcher after startup failure; normal Gateway close resets it globally. */
|
||||
|
|
|
|||
|
|
@ -9,41 +9,43 @@
|
|||
export {
|
||||
channelIngressRoutes,
|
||||
createChannelIngressResolver,
|
||||
defineStableChannelIngressIdentity,
|
||||
readChannelIngressStoreAllowFromForDmPolicy,
|
||||
resolveChannelMessageIngress,
|
||||
resolveStableChannelMessageIngress,
|
||||
} from "../channels/message-access/index.js";
|
||||
} from "../channels/message-access/runtime.js";
|
||||
export { defineStableChannelIngressIdentity } from "../channels/message-access/runtime-identity.js";
|
||||
export { readChannelIngressStoreAllowFromForDmPolicy } from "../channels/message-access/store-allow-from.js";
|
||||
export { resolveChannelImplicitMentions } from "../config/implicit-mentions.js";
|
||||
export type {
|
||||
AccessGroupMembershipFact,
|
||||
ChannelIngressDecision,
|
||||
ChannelIngressAccessGroupMembershipResolver,
|
||||
ChannelIngressCommandPresetInput,
|
||||
ChannelIngressConfigInput,
|
||||
ChannelIngressContextBinding,
|
||||
ChannelIngressEventInput,
|
||||
ChannelIngressEventPresetInput,
|
||||
ChannelIngressIdentityDescriptor,
|
||||
ChannelIngressIdentityAlias,
|
||||
ChannelIngressIdentityField,
|
||||
ChannelIngressIdentitySubjectInput,
|
||||
ChannelIngressIdentifierKind,
|
||||
ChannelIngressPolicyInput,
|
||||
ChannelIngressRouteAccess,
|
||||
ChannelIngressRouteDescriptor,
|
||||
ChannelIngressResolver,
|
||||
ChannelIngressResolverMessageParams,
|
||||
ChannelIngressStateInput,
|
||||
ChannelIngressState,
|
||||
ChannelMessageIngressCommandInput,
|
||||
CreateChannelIngressResolverParams,
|
||||
IngressReasonCode,
|
||||
ResolvedChannelMessageIngress,
|
||||
ResolveChannelMessageIngressParams,
|
||||
ResolveStableChannelMessageIngressParams,
|
||||
StableChannelIngressIdentityParams,
|
||||
} from "../channels/message-access/index.js";
|
||||
} from "../channels/message-access/runtime-types.js";
|
||||
export type {
|
||||
AccessGroupMembershipFact,
|
||||
ChannelIngressDecision,
|
||||
ChannelIngressEventInput,
|
||||
ChannelIngressIdentifierKind,
|
||||
ChannelIngressPolicyInput,
|
||||
ChannelIngressState,
|
||||
ChannelIngressStateInput,
|
||||
IngressReasonCode,
|
||||
} from "../channels/message-access/types.js";
|
||||
export type { ResolvedChannelImplicitMentions } from "../config/implicit-mentions.js";
|
||||
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Tests channel message helper behavior and mocked runtime interactions.
|
||||
*/
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { defineChannelMessageAdapter as defineCoreChannelMessageAdapter } from "../channels/message/index.js";
|
||||
import { defineChannelMessageAdapter as defineCoreChannelMessageAdapter } from "../channels/message/adapter.js";
|
||||
import {
|
||||
defineChannelMessageAdapter,
|
||||
type ChannelMessageDurableFinalAdapter,
|
||||
|
|
|
|||
|
|
@ -21,25 +21,33 @@ export {
|
|||
export type { OutboundMessageIdentity } from "../channels/message/outbound-echo.js";
|
||||
export {
|
||||
bindIngressLifecycleToReplyOptions,
|
||||
CHANNEL_INGRESS_RETENTION_DEFAULTS,
|
||||
createChannelIngressError,
|
||||
createChannelIngressDrain,
|
||||
createChannelIngressMonitor,
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
createChannelReplyPipeline as createChannelMessageReplyPipeline,
|
||||
// Narrow drain seam by maintainer decision (#108924): factory, lifecycle binding,
|
||||
// tuning constants, and processPidFromOwnerId (telegram transport display). All other
|
||||
// claim/retry/adoption internals stay core-owned; test helpers live on the
|
||||
// private-local plugin-state-test-runtime subpath.
|
||||
DEFAULT_INGRESS_ADOPTION_STALL_MS,
|
||||
DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
||||
} from "../channels/message/ingress-drain.js";
|
||||
export {
|
||||
CHANNEL_INGRESS_RETENTION_DEFAULTS,
|
||||
createChannelIngressError,
|
||||
createChannelIngressMonitor,
|
||||
} from "../channels/message/ingress-monitor.js";
|
||||
export {
|
||||
DEFAULT_INGRESS_RETRY_DEAD_LETTER_MIN_AGE_MS,
|
||||
DEFAULT_INGRESS_RETRY_MAX_ATTEMPTS,
|
||||
} from "../channels/message/ingress-retry-policy.js";
|
||||
export {
|
||||
INGRESS_CLAIM_PROCESS_ID,
|
||||
processPidFromOwnerId,
|
||||
} from "../channels/message/ingress-claim-owner.js";
|
||||
export {
|
||||
createChannelReplyPipeline as createChannelMessageReplyPipeline,
|
||||
createReplyPrefixContext,
|
||||
createReplyPrefixOptions,
|
||||
createTypingCallbacks,
|
||||
resolveChannelSourceReplyDeliveryMode as resolveChannelMessageSourceReplyDeliveryMode,
|
||||
} from "../channels/message/index.js";
|
||||
} from "../channels/message/reply-pipeline.js";
|
||||
// Bare interval/stop orchestration for channels that own their typing renewal
|
||||
// policy (e.g. per-message reply budgets) instead of the createTypingCallbacks lifecycle.
|
||||
export { createTypingKeepaliveLoop } from "../channels/typing-lifecycle.js";
|
||||
|
|
@ -151,24 +159,41 @@ export type {
|
|||
ChannelProgressDraftCompositorLine,
|
||||
ChannelProgressDraftCompositorSnapshot,
|
||||
} from "../channels/progress-draft-compositor.js";
|
||||
export { deriveDurableFinalDeliveryRequirements } from "../channels/message/capabilities.js";
|
||||
export { defineChannelMessageAdapter } from "../channels/message/adapter.js";
|
||||
export { createChannelMessageAdapterFromOutbound } from "../channels/message/outbound-bridge.js";
|
||||
export { createDurableInboundReceiveJournalFromQueue } from "../channels/message/durable-receive.js";
|
||||
export {
|
||||
createChannelMessageAdapterFromOutbound,
|
||||
createDurableInboundReceiveJournalFromQueue,
|
||||
createMessageReceiptFromOutboundResults,
|
||||
listMessageReceiptPlatformIds,
|
||||
createMessageReceiveContext,
|
||||
createPreviewMessageReceipt,
|
||||
defineFinalizableLivePreviewAdapter,
|
||||
deriveDurableFinalDeliveryRequirements,
|
||||
deliverWithFinalizableLivePreviewAdapter,
|
||||
defineChannelMessageAdapter,
|
||||
resolveMessageReceiptPrimaryId,
|
||||
verifyChannelMessageAdapterCapabilityProofs,
|
||||
verifyChannelMessageLiveCapabilityAdapterProofs,
|
||||
verifyChannelMessageLiveFinalizerProofs,
|
||||
verifyChannelMessageReceiveAckPolicyAdapterProofs,
|
||||
verifyDurableFinalCapabilityProofs,
|
||||
} from "../channels/message/index.js";
|
||||
} from "../channels/message/contracts.js";
|
||||
export {
|
||||
createPreviewMessageReceipt,
|
||||
defineFinalizableLivePreviewAdapter,
|
||||
deliverWithFinalizableLivePreviewAdapter,
|
||||
} from "../channels/message/live.js";
|
||||
export {
|
||||
createMessageReceiptFromOutboundResults,
|
||||
listMessageReceiptPlatformIds,
|
||||
resolveMessageReceiptPrimaryId,
|
||||
} from "../channels/message/receipt.js";
|
||||
export { createMessageReceiveContext } from "../channels/message/receive.js";
|
||||
export type { ChannelIngressDrain } from "../channels/message/ingress-drain.js";
|
||||
export type {
|
||||
ChannelIngressMonitorDeliveryResult,
|
||||
ChannelIngressMonitorLifecycle,
|
||||
} from "../channels/message/ingress-monitor.js";
|
||||
export type {
|
||||
ChannelIngressQueue,
|
||||
ChannelIngressQueueClaim,
|
||||
ChannelIngressQueueClaimRef,
|
||||
ChannelIngressQueueCorruptClaim,
|
||||
ChannelIngressQueueRecord,
|
||||
} from "../channels/message/ingress-queue.js";
|
||||
export type { MessageAckPolicy, MessageReceiveContext } from "../channels/message/receive.js";
|
||||
export type {
|
||||
ChannelMessageAdapterShape,
|
||||
ChannelMessageDurableFinalAdapter,
|
||||
|
|
@ -178,21 +203,11 @@ export type {
|
|||
ChannelMessageSendTextContext,
|
||||
ChannelMessageUnknownSendContext,
|
||||
ChannelMessageUnknownSendReconciliationResult,
|
||||
ChannelIngressDrain,
|
||||
ChannelIngressMonitorDeliveryResult,
|
||||
ChannelIngressMonitorLifecycle,
|
||||
ChannelIngressQueue,
|
||||
ChannelIngressQueueClaim,
|
||||
ChannelIngressQueueClaimRef,
|
||||
ChannelIngressQueueCorruptClaim,
|
||||
ChannelIngressQueueRecord,
|
||||
MessageAckPolicy,
|
||||
MessageReceiveContext,
|
||||
MessageReceipt,
|
||||
MessageReceiptPart,
|
||||
MessageReceiptPartKind,
|
||||
MessageReceiptSourceResult,
|
||||
} from "../channels/message/index.js";
|
||||
} from "../channels/message/types.js";
|
||||
|
||||
/** Lazily forwards inbound reply delivery through the channel turn durable-delivery module. */
|
||||
export const deliverInboundReplyWithMessageSendContext: ChannelDurableDeliveryModule["deliverInboundReplyWithMessageSendContextCore"] =
|
||||
|
|
|
|||
|
|
@ -192,7 +192,13 @@ const RUNTIME_API_EXPORT_GUARDS: Record<string, readonly string[]> = {
|
|||
[contractPluginPath({ rootDir: ROOT_DIR, pluginId: "slack", relativePath: "runtime-api.ts" })]: [
|
||||
'export { handleSlackAction, slackActionRuntime, type SlackActionContext } from "./src/action-runtime.js";',
|
||||
'export { listSlackDirectoryGroupsLive, listSlackDirectoryPeersLive } from "./src/directory-live.js";',
|
||||
'export { deleteSlackMessage, editSlackMessage, getSlackMemberInfo, listEnabledSlackAccounts, listSlackAccountIds, listSlackEmojis, listSlackPins, listSlackReactions, monitorSlackProvider, pinSlackMessage, probeSlack, reactSlackMessage, readSlackMessages, removeOwnSlackReactions, removeSlackReaction, resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackAppToken, resolveSlackBotToken, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, sendMessageSlack, sendSlackMessage, unpinSlackMessage } from "./src/index.js";',
|
||||
'export { listEnabledSlackAccounts, listSlackAccountIds, resolveDefaultSlackAccountId, resolveSlackAccount } from "./src/accounts.js";',
|
||||
'export { deleteSlackMessage, editSlackMessage, getSlackMemberInfo, listSlackEmojis, listSlackPins, listSlackReactions, pinSlackMessage, reactSlackMessage, readSlackMessages, removeOwnSlackReactions, removeSlackReaction, sendSlackMessage, unpinSlackMessage } from "./src/actions.js";',
|
||||
'export { resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy } from "./src/group-policy.js";',
|
||||
'export { monitorSlackProvider } from "./src/monitor.js";',
|
||||
'export { probeSlack } from "./src/probe.js";',
|
||||
'export { sendMessageSlack } from "./src/send.js";',
|
||||
'export { resolveSlackAppToken, resolveSlackBotToken } from "./src/token.js";',
|
||||
'export { resolveSlackChannelAllowlist, type SlackChannelLookup, type SlackChannelResolution } from "./src/resolve-channels.js";',
|
||||
'export { resolveSlackUserAllowlist, type SlackUserLookup, type SlackUserResolution } from "./src/resolve-users.js";',
|
||||
'export { registerSlackPluginHttpRoutes } from "./src/http/plugin-routes.js";',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue