mirror of https://github.com/openclaw/openclaw.git
fix: repair runtime seams after rebase
This commit is contained in:
parent
2a06097184
commit
7ba28d6dba
|
|
@ -6,6 +6,8 @@ import type { ModelProviderAuthMode, ModelProviderConfig } from "../config/types
|
|||
import { coerceSecretRef } from "../config/types.secrets.js";
|
||||
import { getShellEnvAppliedKeys } from "../infra/shell-env.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import { buildProviderMissingAuthMessageWithPlugin } from "../plugins/provider-runtime.runtime.js";
|
||||
import { resolveOwningPluginIdsForProvider } from "../plugins/providers.js";
|
||||
import {
|
||||
normalizeOptionalSecretInput,
|
||||
normalizeSecretInput,
|
||||
|
|
@ -35,15 +37,6 @@ const AWS_BEARER_ENV = "AWS_BEARER_TOKEN_BEDROCK";
|
|||
const AWS_ACCESS_KEY_ENV = "AWS_ACCESS_KEY_ID";
|
||||
const AWS_SECRET_KEY_ENV = "AWS_SECRET_ACCESS_KEY";
|
||||
const AWS_PROFILE_ENV = "AWS_PROFILE";
|
||||
let providerRuntimePromise:
|
||||
| Promise<typeof import("../plugins/provider-runtime.runtime.js")>
|
||||
| undefined;
|
||||
|
||||
function loadProviderRuntime() {
|
||||
providerRuntimePromise ??= import("../plugins/provider-runtime.runtime.js");
|
||||
return providerRuntimePromise;
|
||||
}
|
||||
|
||||
function resolveProviderConfig(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
provider: string,
|
||||
|
|
@ -366,20 +359,30 @@ export async function resolveApiKeyForProvider(params: {
|
|||
return resolveAwsSdkAuthInfo();
|
||||
}
|
||||
|
||||
const { buildProviderMissingAuthMessageWithPlugin } = await loadProviderRuntime();
|
||||
const pluginMissingAuthMessage = buildProviderMissingAuthMessageWithPlugin({
|
||||
provider,
|
||||
config: cfg,
|
||||
context: {
|
||||
config: cfg,
|
||||
agentDir: params.agentDir,
|
||||
env: process.env,
|
||||
const providerConfig = resolveProviderConfig(cfg, provider);
|
||||
const hasInlineConfiguredModels =
|
||||
Array.isArray(providerConfig?.models) && providerConfig.models.length > 0;
|
||||
const owningPluginIds = !hasInlineConfiguredModels
|
||||
? resolveOwningPluginIdsForProvider({
|
||||
provider,
|
||||
config: cfg,
|
||||
})
|
||||
: undefined;
|
||||
if (owningPluginIds?.length) {
|
||||
const pluginMissingAuthMessage = buildProviderMissingAuthMessageWithPlugin({
|
||||
provider,
|
||||
listProfileIds: (providerId) => listProfilesForProvider(store, providerId),
|
||||
},
|
||||
});
|
||||
if (pluginMissingAuthMessage) {
|
||||
throw new Error(pluginMissingAuthMessage);
|
||||
config: cfg,
|
||||
context: {
|
||||
config: cfg,
|
||||
agentDir: params.agentDir,
|
||||
env: process.env,
|
||||
provider,
|
||||
listProfileIds: (providerId) => listProfilesForProvider(store, providerId),
|
||||
},
|
||||
});
|
||||
if (pluginMissingAuthMessage) {
|
||||
throw new Error(pluginMissingAuthMessage);
|
||||
}
|
||||
}
|
||||
|
||||
const authStorePath = resolveAuthStorePathForDisplay(params.agentDir);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import type { EmbeddedRunAttemptResult } from "./pi-embedded-runner/run/types.js
|
|||
|
||||
const runEmbeddedAttemptMock = vi.fn<(params: unknown) => Promise<EmbeddedRunAttemptResult>>();
|
||||
const resolveCopilotApiTokenMock = vi.fn();
|
||||
const COPILOT_TOKEN_URL = "https://api.github.com/copilot_internal/v2/token";
|
||||
const { computeBackoffMock, sleepWithAbortMock } = vi.hoisted(() => ({
|
||||
computeBackoffMock: vi.fn(
|
||||
(
|
||||
|
|
@ -38,30 +39,6 @@ vi.mock("../../extensions/github-copilot/token.js", () => ({
|
|||
resolveCopilotApiToken: (...args: unknown[]) => resolveCopilotApiTokenMock(...args),
|
||||
}));
|
||||
|
||||
vi.mock("../plugins/provider-runtime.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../plugins/provider-runtime.js")>();
|
||||
return {
|
||||
...actual,
|
||||
prepareProviderRuntimeAuth: async (params: {
|
||||
provider: string;
|
||||
context: { apiKey: string; env: NodeJS.ProcessEnv };
|
||||
}) => {
|
||||
if (params.provider !== "github-copilot") {
|
||||
return undefined;
|
||||
}
|
||||
const token = await resolveCopilotApiTokenMock({
|
||||
githubToken: params.context.apiKey,
|
||||
env: params.context.env,
|
||||
});
|
||||
return {
|
||||
apiKey: token.token,
|
||||
baseUrl: token.baseUrl,
|
||||
expiresAt: token.expiresAt,
|
||||
};
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./pi-embedded-runner/compact.js", () => ({
|
||||
compactEmbeddedPiSessionDirect: vi.fn(async () => {
|
||||
throw new Error("compact should not run in auth profile rotation tests");
|
||||
|
|
@ -78,6 +55,7 @@ vi.mock("./models-config.js", async (importOriginal) => {
|
|||
|
||||
let runEmbeddedPiAgent: typeof import("./pi-embedded-runner/run.js").runEmbeddedPiAgent;
|
||||
let unregisterLogTransport: (() => void) | undefined;
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
beforeAll(async () => {
|
||||
({ runEmbeddedPiAgent } = await import("./pi-embedded-runner/run.js"));
|
||||
|
|
@ -87,11 +65,27 @@ beforeEach(() => {
|
|||
vi.useRealTimers();
|
||||
runEmbeddedAttemptMock.mockClear();
|
||||
resolveCopilotApiTokenMock.mockReset();
|
||||
globalThis.fetch = vi.fn(async (input: string | URL | Request) => {
|
||||
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
||||
if (url !== COPILOT_TOKEN_URL) {
|
||||
throw new Error(`Unexpected fetch in test: ${url}`);
|
||||
}
|
||||
const token = await resolveCopilotApiTokenMock();
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
json: async () => ({
|
||||
token: token.token,
|
||||
expires_at: Math.floor(token.expiresAt / 1000),
|
||||
}),
|
||||
} as Response;
|
||||
}) as typeof fetch;
|
||||
computeBackoffMock.mockClear();
|
||||
sleepWithAbortMock.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
unregisterLogTransport?.();
|
||||
unregisterLogTransport = undefined;
|
||||
setLoggerOverride(null);
|
||||
|
|
@ -517,11 +511,9 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
|||
it("refreshes copilot token after auth error and retries once", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-"));
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
await writeCopilotAuthStore(agentDir);
|
||||
const now = Date.now();
|
||||
vi.setSystemTime(now);
|
||||
|
||||
resolveCopilotApiTokenMock
|
||||
.mockResolvedValueOnce({
|
||||
|
|
@ -575,7 +567,6 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
|||
expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(2);
|
||||
expect(resolveCopilotApiTokenMock).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
@ -584,11 +575,9 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
|||
it("allows another auth refresh after a successful retry", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-"));
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
await writeCopilotAuthStore(agentDir);
|
||||
const now = Date.now();
|
||||
vi.setSystemTime(now);
|
||||
|
||||
resolveCopilotApiTokenMock
|
||||
.mockResolvedValueOnce({
|
||||
|
|
@ -662,7 +651,6 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
|||
expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(4);
|
||||
expect(resolveCopilotApiTokenMock).toHaveBeenCalledTimes(3);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import {
|
|||
resolveTelegramReactionLevel,
|
||||
} from "../../plugin-sdk/telegram.js";
|
||||
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
|
||||
import { prepareProviderRuntimeAuth } from "../../plugins/provider-runtime.js";
|
||||
import { prepareProviderRuntimeAuth } from "../../plugins/provider-runtime.runtime.js";
|
||||
import { type enqueueCommand, enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js";
|
||||
import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js";
|
||||
|
|
|
|||
|
|
@ -194,6 +194,22 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
return { kind: "suppressed" };
|
||||
}
|
||||
const providerConfig = resolveConfiguredProviderConfig(cfg, provider);
|
||||
const inlineModels = buildInlineProviderModels(cfg?.models?.providers ?? {});
|
||||
const normalizedProvider = normalizeProviderId(provider);
|
||||
const inlineMatch = inlineModels.find(
|
||||
(entry) => normalizeProviderId(entry.provider) === normalizedProvider && entry.id === modelId,
|
||||
);
|
||||
if (inlineMatch?.api) {
|
||||
return {
|
||||
kind: "resolved",
|
||||
model: normalizeResolvedModel({
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: inlineMatch as Model<Api>,
|
||||
}),
|
||||
};
|
||||
}
|
||||
const model = modelRegistry.find(provider, modelId) as Model<Api> | null;
|
||||
|
||||
if (model) {
|
||||
|
|
@ -213,19 +229,17 @@ function resolveExplicitModelWithRegistry(params: {
|
|||
}
|
||||
|
||||
const providers = cfg?.models?.providers ?? {};
|
||||
const inlineModels = buildInlineProviderModels(providers);
|
||||
const normalizedProvider = normalizeProviderId(provider);
|
||||
const inlineMatch = inlineModels.find(
|
||||
const fallbackInlineMatch = buildInlineProviderModels(providers).find(
|
||||
(entry) => normalizeProviderId(entry.provider) === normalizedProvider && entry.id === modelId,
|
||||
);
|
||||
if (inlineMatch?.api) {
|
||||
if (fallbackInlineMatch?.api) {
|
||||
return {
|
||||
kind: "resolved",
|
||||
model: normalizeResolvedModel({
|
||||
provider,
|
||||
cfg,
|
||||
agentDir,
|
||||
model: inlineMatch as Model<Api>,
|
||||
model: fallbackInlineMatch as Model<Api>,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { computeBackoff, sleepWithAbort, type BackoffPolicy } from "../../infra/backoff.js";
|
||||
import { generateSecureToken } from "../../infra/secure-random.js";
|
||||
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
|
||||
import { prepareProviderRuntimeAuth } from "../../plugins/provider-runtime.js";
|
||||
import { prepareProviderRuntimeAuth } from "../../plugins/provider-runtime.runtime.js";
|
||||
import type { PluginHookBeforeAgentStartResult } from "../../plugins/types.js";
|
||||
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
import { type AnnounceQueueItem, enqueueAnnounce } from "./subagent-announce-queue.js";
|
||||
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
||||
import type { SpawnSubagentMode } from "./subagent-spawn.js";
|
||||
import { readLatestAssistantReply } from "./tools/agent-step.js";
|
||||
import { sanitizeTextContent, extractAssistantText } from "./tools/sessions-helpers.js";
|
||||
import { isAnnounceSkip } from "./tools/sessions-send-helpers.js";
|
||||
|
||||
|
|
@ -391,7 +392,12 @@ async function readSubagentOutput(
|
|||
params: { sessionKey, limit: 100 },
|
||||
});
|
||||
const messages = Array.isArray(history?.messages) ? history.messages : [];
|
||||
return selectSubagentOutputText(summarizeSubagentOutputHistory(messages), outcome);
|
||||
const selected = selectSubagentOutputText(summarizeSubagentOutputHistory(messages), outcome);
|
||||
if (selected?.trim()) {
|
||||
return selected;
|
||||
}
|
||||
const latestAssistant = await readLatestAssistantReply({ sessionKey, limit: 100 });
|
||||
return latestAssistant?.trim() ? latestAssistant : undefined;
|
||||
}
|
||||
|
||||
async function readLatestSubagentOutputWithRetry(params: {
|
||||
|
|
@ -1416,16 +1422,6 @@ export async function runSubagentAnnounceFlow(params: {
|
|||
reply = fallbackReply;
|
||||
}
|
||||
|
||||
if (
|
||||
!expectsCompletionMessage &&
|
||||
!reply?.trim() &&
|
||||
childSessionId &&
|
||||
isEmbeddedPiRunActive(childSessionId)
|
||||
) {
|
||||
shouldDeleteChildSession = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAnnounceSkip(reply) || isSilentReplyText(reply, SILENT_REPLY_TOKEN)) {
|
||||
if (fallbackReply && !fallbackIsSilent) {
|
||||
reply = fallbackReply;
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ export function registerGroupIntroPromptCases(): void {
|
|||
Provider: "whatsapp",
|
||||
},
|
||||
expected: [
|
||||
`You are in the WhatsApp group chat "Ops".`,
|
||||
`Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included). ${groupParticipationNote} Address the specific sender noted in the message context.`,
|
||||
`You are in the WhatsApp group chat "Ops". Your replies are automatically sent to this group chat. Do not use the message tool to send to this same group — just reply normally.`,
|
||||
`Activation: trigger-only (you are invoked only when explicitly mentioned; recent context may be included). WhatsApp IDs: SenderId is the participant JID (group participant id). ${groupParticipationNote} Address the specific sender noted in the message context.`,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ async function expectResetBlockedForNonOwner(params: { home: string }): Promise<
|
|||
...cfg.channels.whatsapp,
|
||||
allowFrom: ["+1999"],
|
||||
};
|
||||
cfg.commands = {
|
||||
...cfg.commands,
|
||||
ownerAllowFrom: ["whatsapp:+1999"],
|
||||
};
|
||||
cfg.session = {
|
||||
...cfg.session,
|
||||
store: join(home, "blocked-reset.sessions.json"),
|
||||
|
|
|
|||
|
|
@ -47,16 +47,9 @@ import {
|
|||
import { type BlockReplyPipeline } from "./block-reply-pipeline.js";
|
||||
import type { FollowupRun } from "./queue.js";
|
||||
import { createBlockReplyDeliveryHandler } from "./reply-delivery.js";
|
||||
import { createReplyMediaPathNormalizer } from "./reply-media-paths.runtime.js";
|
||||
import type { TypingSignaler } from "./typing-mode.js";
|
||||
|
||||
let replyMediaPathsRuntimePromise: Promise<typeof import("./reply-media-paths.runtime.js")> | null =
|
||||
null;
|
||||
|
||||
function loadReplyMediaPathsRuntime() {
|
||||
replyMediaPathsRuntimePromise ??= import("./reply-media-paths.runtime.js");
|
||||
return replyMediaPathsRuntimePromise;
|
||||
}
|
||||
|
||||
export type RuntimeFallbackAttempt = {
|
||||
provider: string;
|
||||
model: string;
|
||||
|
|
@ -116,7 +109,6 @@ export async function runAgentTurnWithFallback(params: {
|
|||
const directlySentBlockKeys = new Set<string>();
|
||||
|
||||
const runId = params.opts?.runId ?? crypto.randomUUID();
|
||||
const { createReplyMediaPathNormalizer } = await loadReplyMediaPathsRuntime();
|
||||
const normalizeReplyMediaPaths = createReplyMediaPathNormalizer({
|
||||
cfg: params.followupRun.run.config,
|
||||
sessionKey: params.sessionKey,
|
||||
|
|
|
|||
|
|
@ -380,6 +380,18 @@ describe("runReplyAgent heartbeat followup guard", () => {
|
|||
expect(vi.mocked(enqueueFollowupRunMock)).toHaveBeenCalledTimes(1);
|
||||
expect(state.runEmbeddedPiAgentMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("drains followup queue when an unexpected exception escapes the run path", async () => {
|
||||
accountingState.persistRunSessionUsageMock.mockRejectedValueOnce(new Error("persist exploded"));
|
||||
state.runEmbeddedPiAgentMock.mockResolvedValueOnce({
|
||||
payloads: [{ text: "ok" }],
|
||||
meta: { agentMeta: { usage: { input: 1, output: 1 } } },
|
||||
});
|
||||
|
||||
const { run } = createMinimalRun();
|
||||
await expect(run()).rejects.toThrow("persist exploded");
|
||||
expect(vi.mocked(scheduleFollowupDrainMock)).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("runReplyAgent typing (heartbeat)", () => {
|
||||
|
|
@ -674,26 +686,37 @@ describe("runReplyAgent typing (heartbeat)", () => {
|
|||
|
||||
it("retries transient HTTP failures once with timer-driven backoff", async () => {
|
||||
vi.useFakeTimers();
|
||||
let calls = 0;
|
||||
state.runEmbeddedPiAgentMock.mockImplementation(async () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
throw new Error("502 Bad Gateway");
|
||||
}
|
||||
return { payloads: [{ text: "final" }], meta: {} };
|
||||
});
|
||||
try {
|
||||
let calls = 0;
|
||||
state.runEmbeddedPiAgentMock.mockImplementation(async () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
throw new Error("502 Bad Gateway");
|
||||
}
|
||||
return { payloads: [{ text: "final" }], meta: {} };
|
||||
});
|
||||
|
||||
const { run } = createMinimalRun({
|
||||
typingMode: "message",
|
||||
});
|
||||
const runPromise = run();
|
||||
const { run } = createMinimalRun({
|
||||
typingMode: "message",
|
||||
});
|
||||
const runPromise = run();
|
||||
void runPromise.catch(() => {});
|
||||
await vi.dynamicImportSettled();
|
||||
|
||||
await vi.advanceTimersByTimeAsync(2_499);
|
||||
expect(calls).toBe(1);
|
||||
await vi.advanceTimersByTimeAsync(1);
|
||||
await runPromise;
|
||||
expect(calls).toBe(2);
|
||||
vi.useRealTimers();
|
||||
vi.advanceTimersByTime(2_499);
|
||||
await Promise.resolve();
|
||||
expect(calls).toBe(1);
|
||||
vi.advanceTimersByTime(1);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
expect(calls).toBe(2);
|
||||
|
||||
// Restore real timers before awaiting the settled run to avoid Vitest
|
||||
// fake-timer bookkeeping stalling the test worker after the retry fires.
|
||||
vi.useRealTimers();
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("delivers tool results in order even when dispatched concurrently", async () => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import fs from "node:fs";
|
||||
import { lookupCachedContextTokens } from "../../agents/context-cache.js";
|
||||
import { lookupContextTokens } from "../../agents/context-tokens.runtime.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
|
||||
import { resolveModelAuthMode } from "../../agents/model-auth.js";
|
||||
import { isCliProvider } from "../../agents/model-selection.js";
|
||||
|
|
@ -25,6 +26,7 @@ import {
|
|||
import type { OriginatingChannelType, TemplateContext } from "../templating.js";
|
||||
import { resolveResponseUsageMode, type VerboseLevel } from "../thinking.js";
|
||||
import type { GetReplyOptions, ReplyPayload } from "../types.js";
|
||||
import { runAgentTurnWithFallback } from "./agent-runner-execution.runtime.js";
|
||||
import {
|
||||
createShouldEmitToolOutput,
|
||||
createShouldEmitToolResult,
|
||||
|
|
@ -32,6 +34,7 @@ import {
|
|||
isAudioPayload,
|
||||
signalTypingIfNeeded,
|
||||
} from "./agent-runner-helpers.js";
|
||||
import { runMemoryFlushIfNeeded } from "./agent-runner-memory.runtime.js";
|
||||
import { buildReplyPayloads } from "./agent-runner-payloads.js";
|
||||
import {
|
||||
appendUnscheduledReminderNote,
|
||||
|
|
@ -45,8 +48,9 @@ import { createFollowupRunner } from "./followup-runner.js";
|
|||
import { resolveOriginMessageProvider, resolveOriginMessageTo } from "./origin-routing.js";
|
||||
import { readPostCompactionContext } from "./post-compaction-context.js";
|
||||
import { resolveActiveRunQueueAction } from "./queue-policy.js";
|
||||
import type { FollowupRun, QueueSettings } from "./queue.js";
|
||||
import { enqueueFollowupRun } from "./queue/enqueue.js";
|
||||
import type { FollowupRun, QueueSettings } from "./queue/types.js";
|
||||
import { createReplyMediaPathNormalizer } from "./reply-media-paths.runtime.js";
|
||||
import { createReplyToModeFilterForChannel, resolveReplyToMode } from "./reply-threading.js";
|
||||
import { incrementRunCompactionCount, persistRunSessionUsage } from "./session-run-accounting.js";
|
||||
import { createTypingSignaler } from "./typing-mode.js";
|
||||
|
|
@ -56,18 +60,7 @@ const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000;
|
|||
let piEmbeddedQueueRuntimePromise: Promise<
|
||||
typeof import("../../agents/pi-embedded-queue.runtime.js")
|
||||
> | null = null;
|
||||
let agentRunnerExecutionRuntimePromise: Promise<
|
||||
typeof import("./agent-runner-execution.runtime.js")
|
||||
> | null = null;
|
||||
let agentRunnerMemoryRuntimePromise: Promise<
|
||||
typeof import("./agent-runner-memory.runtime.js")
|
||||
> | null = null;
|
||||
let usageCostRuntimePromise: Promise<typeof import("./usage-cost.runtime.js")> | null = null;
|
||||
let contextTokensRuntimePromise: Promise<
|
||||
typeof import("../../agents/context-tokens.runtime.js")
|
||||
> | null = null;
|
||||
let replyMediaPathsRuntimePromise: Promise<typeof import("./reply-media-paths.runtime.js")> | null =
|
||||
null;
|
||||
let sessionStoreRuntimePromise: Promise<
|
||||
typeof import("../../config/sessions/store.runtime.js")
|
||||
> | null = null;
|
||||
|
|
@ -77,31 +70,11 @@ function loadPiEmbeddedQueueRuntime() {
|
|||
return piEmbeddedQueueRuntimePromise;
|
||||
}
|
||||
|
||||
function loadAgentRunnerExecutionRuntime() {
|
||||
agentRunnerExecutionRuntimePromise ??= import("./agent-runner-execution.runtime.js");
|
||||
return agentRunnerExecutionRuntimePromise;
|
||||
}
|
||||
|
||||
function loadAgentRunnerMemoryRuntime() {
|
||||
agentRunnerMemoryRuntimePromise ??= import("./agent-runner-memory.runtime.js");
|
||||
return agentRunnerMemoryRuntimePromise;
|
||||
}
|
||||
|
||||
function loadUsageCostRuntime() {
|
||||
usageCostRuntimePromise ??= import("./usage-cost.runtime.js");
|
||||
return usageCostRuntimePromise;
|
||||
}
|
||||
|
||||
function loadContextTokensRuntime() {
|
||||
contextTokensRuntimePromise ??= import("../../agents/context-tokens.runtime.js");
|
||||
return contextTokensRuntimePromise;
|
||||
}
|
||||
|
||||
function loadReplyMediaPathsRuntime() {
|
||||
replyMediaPathsRuntimePromise ??= import("./reply-media-paths.runtime.js");
|
||||
return replyMediaPathsRuntimePromise;
|
||||
}
|
||||
|
||||
function loadSessionStoreRuntime() {
|
||||
sessionStoreRuntimePromise ??= import("../../config/sessions/store.runtime.js");
|
||||
return sessionStoreRuntimePromise;
|
||||
|
|
@ -202,7 +175,6 @@ export async function runReplyAgent(params: {
|
|||
);
|
||||
const applyReplyToMode = createReplyToModeFilterForChannel(replyToMode, replyToChannel);
|
||||
const cfg = followupRun.run.config;
|
||||
const { createReplyMediaPathNormalizer } = await loadReplyMediaPathsRuntime();
|
||||
const normalizeReplyMediaPaths = createReplyMediaPathNormalizer({
|
||||
cfg,
|
||||
sessionKey,
|
||||
|
|
@ -274,7 +246,6 @@ export async function runReplyAgent(params: {
|
|||
|
||||
await typingSignals.signalRunStart();
|
||||
|
||||
const { runMemoryFlushIfNeeded } = await loadAgentRunnerMemoryRuntime();
|
||||
activeSessionEntry = await runMemoryFlushIfNeeded({
|
||||
cfg,
|
||||
followupRun,
|
||||
|
|
@ -404,7 +375,6 @@ export async function runReplyAgent(params: {
|
|||
});
|
||||
try {
|
||||
const runStartedAt = Date.now();
|
||||
const { runAgentTurnWithFallback } = await loadAgentRunnerExecutionRuntime();
|
||||
const runOutcome = await runAgentTurnWithFallback({
|
||||
commandBody,
|
||||
followupRun,
|
||||
|
|
@ -531,9 +501,7 @@ export async function runReplyAgent(params: {
|
|||
const contextTokensUsed =
|
||||
agentCfgContextTokens ??
|
||||
cachedContextTokens ??
|
||||
(await loadContextTokensRuntime()).lookupContextTokens(modelUsed, {
|
||||
allowAsyncLoad: false,
|
||||
}) ??
|
||||
lookupContextTokens(modelUsed, { allowAsyncLoad: false }) ??
|
||||
activeSessionEntry?.contextTokens ??
|
||||
DEFAULT_CONTEXT_TOKENS;
|
||||
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ type RunPreparedReplyParams = {
|
|||
sessionCfg: OpenClawConfig["session"];
|
||||
commandAuthorized: boolean;
|
||||
command: ReturnType<typeof buildCommandContext>;
|
||||
commandSource: string;
|
||||
commandSource?: string;
|
||||
allowTextCommands: boolean;
|
||||
directives: InlineDirectives;
|
||||
defaultActivation: Parameters<typeof buildGroupIntro>[0]["defaultActivation"];
|
||||
|
|
@ -214,7 +214,6 @@ export async function runPreparedReply(
|
|||
sessionCfg,
|
||||
commandAuthorized,
|
||||
command,
|
||||
commandSource,
|
||||
allowTextCommands,
|
||||
directives,
|
||||
defaultActivation,
|
||||
|
|
@ -300,11 +299,13 @@ export async function runPreparedReply(
|
|||
// Use CommandBody/RawBody for bare reset detection (clean message without structural context).
|
||||
const rawBodyTrimmed = (ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? "").trim();
|
||||
const baseBodyTrimmedRaw = baseBody.trim();
|
||||
const isWholeMessageCommand = command.commandBodyNormalized.trim() === rawBodyTrimmed;
|
||||
const isResetOrNewCommand = /^\/(new|reset)(?:\s|$)/.test(rawBodyTrimmed);
|
||||
if (
|
||||
allowTextCommands &&
|
||||
(!commandAuthorized || !command.isAuthorizedSender) &&
|
||||
!baseBodyTrimmedRaw &&
|
||||
hasControlCommand(commandSource, cfg)
|
||||
isWholeMessageCommand &&
|
||||
(hasControlCommand(rawBodyTrimmed, cfg) || isResetOrNewCommand)
|
||||
) {
|
||||
typing.cleanup();
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
normalizeChannelId as normalizePluginChannelId,
|
||||
} from "../../channels/plugins/index.js";
|
||||
import type { ChannelId } from "../../channels/plugins/types.js";
|
||||
import { resolveWhatsAppGroupIntroHint } from "../../channels/plugins/whatsapp-shared.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { resolveChannelGroupRequireMention } from "../../config/group-policy.js";
|
||||
import type { GroupKeyResolution, SessionEntry } from "../../config/sessions.js";
|
||||
|
|
@ -157,13 +158,13 @@ export function buildGroupIntro(params: {
|
|||
params.sessionCtx.GroupChannel?.trim() ?? params.sessionCtx.GroupSubject?.trim();
|
||||
const groupSpace = params.sessionCtx.GroupSpace?.trim();
|
||||
const providerIdsLine = providerId
|
||||
? getChannelPlugin(providerId)?.groups?.resolveGroupIntroHint?.({
|
||||
? (getChannelPlugin(providerId)?.groups?.resolveGroupIntroHint?.({
|
||||
cfg: params.cfg,
|
||||
groupId,
|
||||
groupChannel,
|
||||
groupSpace,
|
||||
accountId: params.sessionCtx.AccountId,
|
||||
})
|
||||
}) ?? (providerId === "whatsapp" ? resolveWhatsAppGroupIntroHint() : undefined))
|
||||
: undefined;
|
||||
const silenceLine =
|
||||
activation === "always"
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ export {
|
|||
buildProviderAuthDoctorHintWithPlugin,
|
||||
buildProviderMissingAuthMessageWithPlugin,
|
||||
formatProviderAuthProfileApiKeyWithPlugin,
|
||||
prepareProviderRuntimeAuth,
|
||||
refreshProviderOAuthCredentialWithPlugin,
|
||||
} from "./provider-runtime.js";
|
||||
|
|
|
|||
Loading…
Reference in New Issue