Agents: route embedded discovery and compaction ids

This commit is contained in:
Gustavo Madeira Santana
2026-03-18 00:32:51 +00:00
parent ab1da26f4d
commit ab62f3b9f4
8 changed files with 340 additions and 44 deletions

View File

@@ -91,6 +91,7 @@ import {
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "./history.js";
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
import { log } from "./logger.js";
import { buildEmbeddedMessageActionDiscoveryInput } from "./message-action-discovery-input.js";
import { buildModelAliasLines, resolveModelAsync } from "./model.js";
import { buildEmbeddedSandboxInfo } from "./sandbox-info.js";
import { prewarmSessionFile, trackSessionManagerAccess } from "./session-manager-cache.js";
@@ -116,7 +117,8 @@ export type CompactEmbeddedPiSessionParams = {
currentChannelId?: string;
currentThreadTs?: string;
currentMessageId?: string | number;
requesterSenderId?: string;
/** Trusted sender id from inbound context for scoped message-tool discovery. */
senderId?: string;
authProfileId?: string;
/** Group id for channel-level tool policy resolution. */
groupId?: string | null;
@@ -659,18 +661,20 @@ export async function compactEmbeddedPiSessionDirect(
});
// Resolve channel-specific message actions for system prompt
const channelActions = runtimeChannel
? listChannelSupportedActions({
cfg: params.config,
channel: runtimeChannel,
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
currentMessageId: params.currentMessageId,
accountId: params.agentAccountId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: sessionAgentId,
requesterSenderId: params.requesterSenderId,
})
? listChannelSupportedActions(
buildEmbeddedMessageActionDiscoveryInput({
cfg: params.config,
channel: runtimeChannel,
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
currentMessageId: params.currentMessageId,
accountId: params.agentAccountId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: sessionAgentId,
senderId: params.senderId,
}),
)
: undefined;
const messageToolHints = runtimeChannel
? resolveChannelMessageToolHints({

View File

@@ -0,0 +1,77 @@
import { describe, expect, it } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { buildEmbeddedCompactionRuntimeContext } from "./compaction-runtime-context.js";
describe("buildEmbeddedCompactionRuntimeContext", () => {
it("preserves sender and current message routing for compaction", () => {
expect(
buildEmbeddedCompactionRuntimeContext({
sessionKey: "agent:main:thread:1",
messageChannel: "slack",
messageProvider: "slack",
agentAccountId: "acct-1",
currentChannelId: "C123",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
authProfileId: "openai:p1",
workspaceDir: "/tmp/workspace",
agentDir: "/tmp/agent",
config: {} as OpenClawConfig,
senderIsOwner: true,
senderId: "user-123",
provider: "openai-codex",
modelId: "gpt-5.3-codex",
thinkLevel: "off",
reasoningLevel: "on",
extraSystemPrompt: "extra",
ownerNumbers: ["+15555550123"],
}),
).toMatchObject({
sessionKey: "agent:main:thread:1",
messageChannel: "slack",
messageProvider: "slack",
agentAccountId: "acct-1",
currentChannelId: "C123",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
authProfileId: "openai:p1",
workspaceDir: "/tmp/workspace",
agentDir: "/tmp/agent",
senderId: "user-123",
provider: "openai-codex",
model: "gpt-5.3-codex",
});
});
it("normalizes nullable compaction routing fields to undefined", () => {
expect(
buildEmbeddedCompactionRuntimeContext({
sessionKey: null,
messageChannel: null,
messageProvider: null,
agentAccountId: null,
currentChannelId: null,
currentThreadTs: null,
currentMessageId: null,
authProfileId: null,
workspaceDir: "/tmp/workspace",
agentDir: "/tmp/agent",
senderId: null,
provider: null,
modelId: null,
}),
).toMatchObject({
sessionKey: undefined,
messageChannel: undefined,
messageProvider: undefined,
agentAccountId: undefined,
currentChannelId: undefined,
currentThreadTs: undefined,
currentMessageId: undefined,
authProfileId: undefined,
senderId: undefined,
provider: undefined,
model: undefined,
});
});
});

View File

@@ -0,0 +1,76 @@
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { ExecElevatedDefaults } from "../bash-tools.js";
import type { SkillSnapshot } from "../skills.js";
export type EmbeddedCompactionRuntimeContext = {
sessionKey?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;
currentChannelId?: string;
currentThreadTs?: string;
currentMessageId?: string | number;
authProfileId?: string;
workspaceDir: string;
agentDir: string;
config?: OpenClawConfig;
skillsSnapshot?: SkillSnapshot;
senderIsOwner?: boolean;
senderId?: string;
provider?: string;
model?: string;
thinkLevel?: ThinkLevel;
reasoningLevel?: ReasoningLevel;
bashElevated?: ExecElevatedDefaults;
extraSystemPrompt?: string;
ownerNumbers?: string[];
};
export function buildEmbeddedCompactionRuntimeContext(params: {
sessionKey?: string | null;
messageChannel?: string | null;
messageProvider?: string | null;
agentAccountId?: string | null;
currentChannelId?: string | null;
currentThreadTs?: string | null;
currentMessageId?: string | number | null;
authProfileId?: string | null;
workspaceDir: string;
agentDir: string;
config?: OpenClawConfig;
skillsSnapshot?: SkillSnapshot;
senderIsOwner?: boolean;
senderId?: string | null;
provider?: string | null;
modelId?: string | null;
thinkLevel?: ThinkLevel;
reasoningLevel?: ReasoningLevel;
bashElevated?: ExecElevatedDefaults;
extraSystemPrompt?: string;
ownerNumbers?: string[];
}): EmbeddedCompactionRuntimeContext {
return {
sessionKey: params.sessionKey ?? undefined,
messageChannel: params.messageChannel ?? undefined,
messageProvider: params.messageProvider ?? undefined,
agentAccountId: params.agentAccountId ?? undefined,
currentChannelId: params.currentChannelId ?? undefined,
currentThreadTs: params.currentThreadTs ?? undefined,
currentMessageId: params.currentMessageId ?? undefined,
authProfileId: params.authProfileId ?? undefined,
workspaceDir: params.workspaceDir,
agentDir: params.agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
senderIsOwner: params.senderIsOwner,
senderId: params.senderId ?? undefined,
provider: params.provider ?? undefined,
model: params.modelId ?? undefined,
thinkLevel: params.thinkLevel,
reasoningLevel: params.reasoningLevel,
bashElevated: params.bashElevated,
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
};
}

View File

@@ -0,0 +1,58 @@
import { describe, expect, it } from "vitest";
import { buildEmbeddedMessageActionDiscoveryInput } from "./message-action-discovery-input.js";
describe("buildEmbeddedMessageActionDiscoveryInput", () => {
it("maps sender and routing scope into message-action discovery context", () => {
expect(
buildEmbeddedMessageActionDiscoveryInput({
channel: "telegram",
currentChannelId: "chat-1",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
accountId: "acct-1",
sessionKey: "agent:main:thread:1",
sessionId: "session-1",
agentId: "main",
senderId: "user-123",
}),
).toEqual({
cfg: undefined,
channel: "telegram",
currentChannelId: "chat-1",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
accountId: "acct-1",
sessionKey: "agent:main:thread:1",
sessionId: "session-1",
agentId: "main",
requesterSenderId: "user-123",
});
});
it("normalizes nullable routing fields to undefined", () => {
expect(
buildEmbeddedMessageActionDiscoveryInput({
channel: "slack",
currentChannelId: null,
currentThreadTs: null,
currentMessageId: null,
accountId: null,
sessionKey: null,
sessionId: null,
agentId: null,
senderId: null,
}),
).toEqual({
cfg: undefined,
channel: "slack",
currentChannelId: undefined,
currentThreadTs: undefined,
currentMessageId: undefined,
accountId: undefined,
sessionKey: undefined,
sessionId: undefined,
agentId: undefined,
requesterSenderId: undefined,
});
});
});

View File

@@ -0,0 +1,27 @@
import type { OpenClawConfig } from "../../config/config.js";
export function buildEmbeddedMessageActionDiscoveryInput(params: {
cfg?: OpenClawConfig;
channel: string;
currentChannelId?: string | null;
currentThreadTs?: string | null;
currentMessageId?: string | number | null;
accountId?: string | null;
sessionKey?: string | null;
sessionId?: string | null;
agentId?: string | null;
senderId?: string | null;
}) {
return {
cfg: params.cfg,
channel: params.channel,
currentChannelId: params.currentChannelId ?? undefined,
currentThreadTs: params.currentThreadTs ?? undefined,
currentMessageId: params.currentMessageId ?? undefined,
accountId: params.accountId ?? undefined,
sessionKey: params.sessionKey ?? undefined,
sessionId: params.sessionId ?? undefined,
agentId: params.agentId ?? undefined,
requesterSenderId: params.senderId ?? undefined,
};
}

View File

@@ -65,6 +65,7 @@ import {
import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js";
import { derivePromptTokens, normalizeUsage, type UsageLike } from "../usage.js";
import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js";
import { buildEmbeddedCompactionRuntimeContext } from "./compaction-runtime-context.js";
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
import { log } from "./logger.js";
import { resolveModelAsync } from "./model.js";
@@ -1141,24 +1142,30 @@ export async function runEmbeddedPiAgent(
force: true,
compactionTarget: "budget",
runtimeContext: {
sessionKey: params.sessionKey,
messageChannel: params.messageChannel,
messageProvider: params.messageProvider,
agentAccountId: params.agentAccountId,
authProfileId: lastProfileId,
workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
senderIsOwner: params.senderIsOwner,
provider,
model: modelId,
...buildEmbeddedCompactionRuntimeContext({
sessionKey: params.sessionKey,
messageChannel: params.messageChannel,
messageProvider: params.messageProvider,
agentAccountId: params.agentAccountId,
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
currentMessageId: params.currentMessageId,
authProfileId: lastProfileId,
workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
senderIsOwner: params.senderIsOwner,
senderId: params.senderId,
provider,
modelId,
thinkLevel,
reasoningLevel: params.reasoningLevel,
bashElevated: params.bashElevated,
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
}),
runId: params.runId,
thinkLevel,
reasoningLevel: params.reasoningLevel,
bashElevated: params.bashElevated,
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
trigger: "overflow",
...(observedOverflowTokens !== undefined
? { currentTokenCount: observedOverflowTokens }

View File

@@ -1249,4 +1249,38 @@ describe("buildAfterTurnRuntimeContext", () => {
agentDir: "/tmp/agent",
});
});
it("preserves sender and channel routing context for scoped compaction discovery", () => {
const legacy = buildAfterTurnRuntimeContext({
attempt: {
sessionKey: "agent:main:session:abc",
messageChannel: "slack",
messageProvider: "slack",
agentAccountId: "acct-1",
currentChannelId: "C123",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
authProfileId: "openai:p1",
config: {} as OpenClawConfig,
skillsSnapshot: undefined,
senderIsOwner: true,
senderId: "user-123",
provider: "openai-codex",
modelId: "gpt-5.3-codex",
thinkLevel: "off",
reasoningLevel: "on",
extraSystemPrompt: "extra",
ownerNumbers: ["+15555550123"],
},
workspaceDir: "/tmp/workspace",
agentDir: "/tmp/agent",
});
expect(legacy).toMatchObject({
senderId: "user-123",
currentChannelId: "C123",
currentThreadTs: "thread-9",
currentMessageId: "msg-42",
});
});
});

View File

@@ -101,6 +101,7 @@ import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
import { isRunnerAbortError } from "../abort.js";
import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.js";
import type { CompactEmbeddedPiSessionParams } from "../compact.js";
import { buildEmbeddedCompactionRuntimeContext } from "../compaction-runtime-context.js";
import { resolveCompactionTimeoutMs } from "../compaction-safety-timeout.js";
import { buildEmbeddedExtensionFactories } from "../extensions.js";
import { applyExtraParamsToAgent } from "../extra-params.js";
@@ -111,6 +112,7 @@ import {
} from "../google.js";
import { getDmHistoryLimitFromSessionKey, limitHistoryTurns } from "../history.js";
import { log } from "../logger.js";
import { buildEmbeddedMessageActionDiscoveryInput } from "../message-action-discovery-input.js";
import { buildModelAliasLines } from "../model.js";
import {
clearActiveEmbeddedRun,
@@ -1280,9 +1282,13 @@ export function buildAfterTurnRuntimeContext(params: {
| "messageChannel"
| "messageProvider"
| "agentAccountId"
| "currentChannelId"
| "currentThreadTs"
| "currentMessageId"
| "config"
| "skillsSnapshot"
| "senderIsOwner"
| "senderId"
| "provider"
| "modelId"
| "thinkLevel"
@@ -1295,25 +1301,29 @@ export function buildAfterTurnRuntimeContext(params: {
workspaceDir: string;
agentDir: string;
}): Partial<CompactEmbeddedPiSessionParams> {
return {
return buildEmbeddedCompactionRuntimeContext({
sessionKey: params.attempt.sessionKey,
messageChannel: params.attempt.messageChannel,
messageProvider: params.attempt.messageProvider,
agentAccountId: params.attempt.agentAccountId,
currentChannelId: params.attempt.currentChannelId,
currentThreadTs: params.attempt.currentThreadTs,
currentMessageId: params.attempt.currentMessageId,
authProfileId: params.attempt.authProfileId,
workspaceDir: params.workspaceDir,
agentDir: params.agentDir,
config: params.attempt.config,
skillsSnapshot: params.attempt.skillsSnapshot,
senderIsOwner: params.attempt.senderIsOwner,
senderId: params.attempt.senderId,
provider: params.attempt.provider,
model: params.attempt.modelId,
modelId: params.attempt.modelId,
thinkLevel: params.attempt.thinkLevel,
reasoningLevel: params.attempt.reasoningLevel,
bashElevated: params.attempt.bashElevated,
extraSystemPrompt: params.attempt.extraSystemPrompt,
ownerNumbers: params.attempt.ownerNumbers,
};
});
}
function summarizeMessagePayload(msg: AgentMessage): { textChars: number; imageBlocks: number } {
@@ -1620,17 +1630,20 @@ export async function runEmbeddedAttempt(
const reasoningTagHint = isReasoningTagProvider(params.provider);
// Resolve channel-specific message actions for system prompt
const channelActions = runtimeChannel
? listChannelSupportedActions({
cfg: params.config,
channel: runtimeChannel,
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
currentMessageId: params.currentMessageId,
accountId: params.agentAccountId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: sessionAgentId,
})
? listChannelSupportedActions(
buildEmbeddedMessageActionDiscoveryInput({
cfg: params.config,
channel: runtimeChannel,
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
currentMessageId: params.currentMessageId,
accountId: params.agentAccountId,
sessionKey: params.sessionKey,
sessionId: params.sessionId,
agentId: sessionAgentId,
senderId: params.senderId,
}),
)
: undefined;
const messageToolHints = runtimeChannel
? resolveChannelMessageToolHints({