diff --git a/CHANGELOG.md b/CHANGELOG.md index f01a287419e..dbfb7a3aebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ Docs: https://docs.openclaw.ai ### Changes +- Agents/compaction: add `agents.defaults.compaction.notifyUser` so the `🧹 Compacting context...` start notice is opt-in instead of always being shown. (#54251) Thanks @oguricap0327. + ### Fixes - Matrix/multi-account: keep room-level `account` scoping, inherited room overrides, and implicit account selection consistent across top-level default auth, named accounts, and cached-credential env setups. (#58449) thanks @Daanvdplas and @gumadeiras. diff --git a/docs/.generated/config-baseline.json b/docs/.generated/config-baseline.json index 02fffc85fdc..d7ad0637844 100644 --- a/docs/.generated/config-baseline.json +++ b/docs/.generated/config-baseline.json @@ -2200,6 +2200,16 @@ "tags": [], "hasChildren": false }, + { + "path": "agents.defaults.memorySearch.notifyUser", + "kind": "core", + "type": "boolean", + "required": false, + "deprecated": false, + "sensitive": false, + "tags": [], + "hasChildren": false + }, { "path": "agents.defaults.memorySearch.outputDimensionality", "kind": "core", @@ -10199,7 +10209,10 @@ { "path": "channels.discord.accounts.*.allowFrom.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -10449,7 +10462,10 @@ { "path": "channels.discord.accounts.*.dm.allowFrom.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -10479,7 +10495,10 @@ { "path": "channels.discord.accounts.*.dm.groupChannels.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -10701,7 +10720,10 @@ { "path": "channels.discord.accounts.*.execApprovals.approvers.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -10925,7 +10947,10 @@ { "path": "channels.discord.accounts.*.guilds.*.channels.*.roles.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -11125,7 +11150,10 @@ { "path": "channels.discord.accounts.*.guilds.*.channels.*.users.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -11181,7 +11209,10 @@ { "path": "channels.discord.accounts.*.guilds.*.roles.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -11361,7 +11392,10 @@ { "path": "channels.discord.accounts.*.guilds.*.users.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -12601,7 +12635,10 @@ { "path": "channels.discord.allowFrom.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -12909,7 +12946,10 @@ { "path": "channels.discord.dm.allowFrom.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -12939,7 +12979,10 @@ { "path": "channels.discord.dm.groupChannels.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -13207,7 +13250,10 @@ { "path": "channels.discord.execApprovals.approvers.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -13431,7 +13477,10 @@ { "path": "channels.discord.guilds.*.channels.*.roles.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -13631,7 +13680,10 @@ { "path": "channels.discord.guilds.*.channels.*.users.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -13687,7 +13739,10 @@ { "path": "channels.discord.guilds.*.roles.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, @@ -13867,7 +13922,10 @@ { "path": "channels.discord.guilds.*.users.*", "kind": "channel", - "type": "string", + "type": [ + "number", + "string" + ], "required": false, "deprecated": false, "sensitive": false, diff --git a/docs/.generated/config-baseline.jsonl b/docs/.generated/config-baseline.jsonl index 62dfe1aeee0..461e87941ec 100644 --- a/docs/.generated/config-baseline.jsonl +++ b/docs/.generated/config-baseline.jsonl @@ -1,4 +1,4 @@ -{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5765} +{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5766} {"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true} {"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -185,6 +185,7 @@ {"recordType":"path","path":"agents.defaults.memorySearch.multimodal.maxFileBytes","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Memory Search Multimodal Max File Bytes","help":"Sets the maximum bytes allowed per multimodal file before it is skipped during memory indexing. Use this to cap upload cost and indexing latency, or raise it for short high-quality audio clips.","hasChildren":false} {"recordType":"path","path":"agents.defaults.memorySearch.multimodal.modalities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Multimodal Modalities","help":"Selects which multimodal file types are indexed from extraPaths: \"image\", \"audio\", or \"all\". Keep this narrow to avoid indexing large binary corpora unintentionally.","hasChildren":true} {"recordType":"path","path":"agents.defaults.memorySearch.multimodal.modalities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"agents.defaults.memorySearch.notifyUser","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"agents.defaults.memorySearch.outputDimensionality","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Output Dimensionality","help":"Gemini embedding-2 only: chooses the output vector size for memory embeddings. Use 768, 1536, or 3072 (default), and expect a full reindex when you change it because stored vector dimensions must stay consistent.","hasChildren":false} {"recordType":"path","path":"agents.defaults.memorySearch.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search Provider","help":"Selects the embedding backend used to build/query memory vectors: \"openai\", \"gemini\", \"voyage\", \"mistral\", \"ollama\", or \"local\". Keep your most reliable provider here and configure fallback for resilience.","hasChildren":false} {"recordType":"path","path":"agents.defaults.memorySearch.qmd","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search QMD Collections","help":"Use this when one agent should query another agent's transcript collections; QMD-specific extra collections let you opt into cross-agent memory search without flattening everything into one shared namespace.","hasChildren":true} @@ -895,7 +896,7 @@ {"recordType":"path","path":"channels.discord.accounts.*.agentComponents.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.autoPresence","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.autoPresence.degradedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.autoPresence.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -919,10 +920,10 @@ {"recordType":"path","path":"channels.discord.accounts.*.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.dm.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.dm.groupChannels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -943,7 +944,7 @@ {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.approvers.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.cleanupAfterResolve","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -963,7 +964,7 @@ {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.includeThreadStarter","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.roles.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -983,12 +984,12 @@ {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.users.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.roles.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.slug","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1006,7 +1007,7 @@ {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.guilds.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.users.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.accounts.*.guilds.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.accounts.*.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.accounts.*.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1120,7 +1121,7 @@ {"recordType":"path","path":"channels.discord.agentComponents.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Discord Allow Bot Messages","help":"Allow bot-authored messages to trigger Discord replies (default: false). Set \"mentions\" to only accept bot messages that mention the bot.","hasChildren":false} {"recordType":"path","path":"channels.discord.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.autoPresence","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.autoPresence.degradedText","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Auto Presence Degraded Text","help":"Optional custom status text while runtime/model availability is degraded or unknown (idle).","hasChildren":false} {"recordType":"path","path":"channels.discord.autoPresence.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Discord Auto Presence Enabled","help":"Enable automatic Discord bot presence updates based on runtime/model availability signals. When enabled: healthy=>online, degraded/unknown=>idle, exhausted/unavailable=>dnd.","hasChildren":false} @@ -1145,10 +1146,10 @@ {"recordType":"path","path":"channels.discord.defaultTo","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.dm","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.dm.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.dm.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.dm.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.dm.groupChannels","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.dm.groupChannels.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.dm.groupChannels.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.dm.groupEnabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.dm.policy","kind":"channel","type":"string","required":false,"enumValues":["pairing","allowlist","open","disabled"],"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Discord DM Policy","help":"Direct message access control (\"pairing\" recommended). \"open\" requires channels.discord.allowFrom=[\"*\"] (legacy: channels.discord.dm.allowFrom).","hasChildren":false} {"recordType":"path","path":"channels.discord.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -1169,7 +1170,7 @@ {"recordType":"path","path":"channels.discord.execApprovals.agentFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.execApprovals.agentFilter.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.execApprovals.approvers","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.execApprovals.approvers.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.execApprovals.approvers.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.execApprovals.cleanupAfterResolve","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.execApprovals.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.execApprovals.sessionFilter","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1189,7 +1190,7 @@ {"recordType":"path","path":"channels.discord.guilds.*.channels.*.includeThreadStarter","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.guilds.*.channels.*.roles.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.skills","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.skills.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} @@ -1209,12 +1210,12 @@ {"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.channels.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.guilds.*.channels.*.users.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.channels.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.ignoreOtherMentions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.reactionNotifications","kind":"channel","type":"string","required":false,"enumValues":["off","own","all","allowlist"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.roles","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.guilds.*.roles.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.roles.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.slug","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.tools","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.guilds.*.tools.allow","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} @@ -1232,7 +1233,7 @@ {"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.deny","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.guilds.*.toolsBySender.*.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.guilds.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} -{"recordType":"path","path":"channels.discord.guilds.*.users.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} +{"recordType":"path","path":"channels.discord.guilds.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.healthMonitor","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} {"recordType":"path","path":"channels.discord.healthMonitor.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false} {"recordType":"path","path":"channels.discord.heartbeat","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true} diff --git a/src/auto-reply/reply/agent-runner-execution.test.ts b/src/auto-reply/reply/agent-runner-execution.test.ts index 688ec45fe1b..801c805e334 100644 --- a/src/auto-reply/reply/agent-runner-execution.test.ts +++ b/src/auto-reply/reply/agent-runner-execution.test.ts @@ -116,6 +116,10 @@ type FallbackRunnerParams = { type EmbeddedAgentParams = { onToolResult?: (payload: { text?: string; mediaUrls?: string[] }) => Promise | void; + onAgentEvent?: (payload: { + stream: string; + data: { phase?: string; completed?: boolean }; + }) => Promise | void; }; function createMockTypingSignaler(): TypingSignaler { @@ -229,6 +233,145 @@ describe("runAgentTurnWithFallback", () => { expect(onToolResult.mock.calls[0]?.[0]?.text).toBeUndefined(); }); + it("keeps compaction start notices silent by default", async () => { + const onBlockReply = vi.fn(); + state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { + await params.onAgentEvent?.({ stream: "compaction", data: { phase: "start" } }); + return { payloads: [{ text: "final" }], meta: {} }; + }); + + const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); + const result = await runAgentTurnWithFallback({ + commandBody: "hello", + followupRun: createFollowupRun(), + sessionCtx: { + Provider: "whatsapp", + MessageSid: "msg", + } as unknown as TemplateContext, + opts: { onBlockReply }, + typingSignals: createMockTypingSignaler(), + blockReplyPipeline: null, + blockStreamingEnabled: false, + resolvedBlockStreamingBreak: "message_end", + applyReplyToMode: (payload) => payload, + shouldEmitToolResult: () => true, + shouldEmitToolOutput: () => false, + pendingToolTasks: new Set(), + resetSessionAfterCompactionFailure: async () => false, + resetSessionAfterRoleOrderingConflict: async () => false, + isHeartbeat: false, + sessionKey: "main", + getActiveSessionEntry: () => undefined, + resolvedVerboseLevel: "off", + }); + + expect(result.kind).toBe("success"); + expect(onBlockReply).not.toHaveBeenCalled(); + }); + + it("keeps compaction callbacks active when notices are silent by default", async () => { + const onBlockReply = vi.fn(); + const onCompactionStart = vi.fn(); + const onCompactionEnd = vi.fn(); + state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { + await params.onAgentEvent?.({ stream: "compaction", data: { phase: "start" } }); + await params.onAgentEvent?.({ + stream: "compaction", + data: { phase: "end", completed: true }, + }); + return { payloads: [{ text: "final" }], meta: {} }; + }); + + const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); + const result = await runAgentTurnWithFallback({ + commandBody: "hello", + followupRun: createFollowupRun(), + sessionCtx: { + Provider: "whatsapp", + MessageSid: "msg", + } as unknown as TemplateContext, + opts: { + onBlockReply, + onCompactionStart, + onCompactionEnd, + }, + typingSignals: createMockTypingSignaler(), + blockReplyPipeline: null, + blockStreamingEnabled: false, + resolvedBlockStreamingBreak: "message_end", + applyReplyToMode: (payload) => payload, + shouldEmitToolResult: () => true, + shouldEmitToolOutput: () => false, + pendingToolTasks: new Set(), + resetSessionAfterCompactionFailure: async () => false, + resetSessionAfterRoleOrderingConflict: async () => false, + isHeartbeat: false, + sessionKey: "main", + getActiveSessionEntry: () => undefined, + resolvedVerboseLevel: "off", + }); + + expect(result.kind).toBe("success"); + expect(onCompactionStart).toHaveBeenCalledTimes(1); + expect(onCompactionEnd).toHaveBeenCalledTimes(1); + expect(onBlockReply).not.toHaveBeenCalled(); + }); + + it("emits a compaction start notice when notifyUser is enabled", async () => { + const onBlockReply = vi.fn(); + state.runEmbeddedPiAgentMock.mockImplementationOnce(async (params: EmbeddedAgentParams) => { + await params.onAgentEvent?.({ stream: "compaction", data: { phase: "start" } }); + return { payloads: [{ text: "final" }], meta: {} }; + }); + + const followupRun = createFollowupRun(); + followupRun.run.config = { + agents: { + defaults: { + compaction: { + notifyUser: true, + }, + }, + }, + }; + + const runAgentTurnWithFallback = await getRunAgentTurnWithFallback(); + const result = await runAgentTurnWithFallback({ + commandBody: "hello", + followupRun, + sessionCtx: { + Provider: "whatsapp", + MessageSid: "msg", + } as unknown as TemplateContext, + opts: { onBlockReply }, + typingSignals: createMockTypingSignaler(), + blockReplyPipeline: null, + blockStreamingEnabled: false, + resolvedBlockStreamingBreak: "message_end", + applyReplyToMode: (payload) => payload, + shouldEmitToolResult: () => true, + shouldEmitToolOutput: () => false, + pendingToolTasks: new Set(), + resetSessionAfterCompactionFailure: async () => false, + resetSessionAfterRoleOrderingConflict: async () => false, + isHeartbeat: false, + sessionKey: "main", + getActiveSessionEntry: () => undefined, + resolvedVerboseLevel: "off", + }); + + expect(result.kind).toBe("success"); + expect(onBlockReply).toHaveBeenCalledTimes(1); + expect(onBlockReply).toHaveBeenCalledWith( + expect.objectContaining({ + text: "🧹 Compacting context...", + replyToId: "msg", + replyToCurrent: true, + isCompactionNotice: true, + }), + ); + }); + it("does not show a rate-limit countdown for mixed-cause fallback exhaustion", async () => { state.runWithModelFallbackMock.mockRejectedValueOnce( Object.assign( diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 455ce16ff67..13d162665ed 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -495,9 +495,14 @@ export async function runAgentTurnWithFallback(params: { if (evt.stream === "compaction") { const phase = typeof evt.data.phase === "string" ? evt.data.phase : ""; if (phase === "start") { + // Keep custom compaction callbacks active, but gate the + // fallback user-facing notice behind explicit opt-in. + const notifyUser = + params.followupRun.run.config.agents?.defaults?.compaction?.notifyUser === + true; if (params.opts?.onCompactionStart) { await params.opts.onCompactionStart(); - } else if (params.opts?.onBlockReply) { + } else if (notifyUser && params.opts?.onBlockReply) { // Send directly via opts.onBlockReply (bypassing the // pipeline) so the notice does not cause final payloads // to be discarded on non-streaming model paths. diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index 7bf96c5631e..26088fb6aa8 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -2491,6 +2491,9 @@ export const GENERATED_BASE_CONFIG_SCHEMA = { }, additionalProperties: false, }, + notifyUser: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -13863,6 +13866,11 @@ export const GENERATED_BASE_CONFIG_SCHEMA = { help: "When enabled, rewrites the session JSONL file after compaction to remove entries that were summarized. Prevents unbounded file growth in long-running sessions with many compaction cycles. Default: false.", tags: ["advanced"], }, + "agents.defaults.compaction.notifyUser": { + label: "Compaction Notify User", + help: "When enabled, sends a brief compaction notice to the user (e.g. '🧹 Compacting context...') when compaction starts. Disabled by default to keep compaction silent and non-intrusive.", + tags: ["advanced"], + }, "agents.defaults.compaction.memoryFlush": { label: "Compaction Memory Flush", help: "Pre-compaction memory flush settings that run an agentic memory write before heavy compaction. Keep enabled for long sessions so salient context is persisted before aggressive trimming.", diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index dd6ed19cee6..668a4a8e831 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1116,6 +1116,8 @@ export const FIELD_HELP: Record = { "Optional provider/model override used only for compaction summarization. Set this when you want compaction to run on a different model than the session default, and leave it unset to keep using the primary agent model.", "agents.defaults.compaction.truncateAfterCompaction": "When enabled, rewrites the session JSONL file after compaction to remove entries that were summarized. Prevents unbounded file growth in long-running sessions with many compaction cycles. Default: false.", + "agents.defaults.compaction.notifyUser": + "When enabled, sends a brief compaction notice to the user (e.g. '🧹 Compacting context...') when compaction starts. Disabled by default to keep compaction silent and non-intrusive.", "agents.defaults.compaction.memoryFlush": "Pre-compaction memory flush settings that run an agentic memory write before heavy compaction. Keep enabled for long sessions so salient context is persisted before aggressive trimming.", "agents.defaults.compaction.memoryFlush.enabled": diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index c848908f474..ffb914b2878 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -508,6 +508,7 @@ export const FIELD_LABELS: Record = { "agents.defaults.compaction.timeoutSeconds": "Compaction Timeout (Seconds)", "agents.defaults.compaction.model": "Compaction Model Override", "agents.defaults.compaction.truncateAfterCompaction": "Truncate After Compaction", + "agents.defaults.compaction.notifyUser": "Compaction Notify User", "agents.defaults.compaction.memoryFlush": "Compaction Memory Flush", "agents.defaults.compaction.memoryFlush.enabled": "Compaction Memory Flush Enabled", "agents.defaults.compaction.memoryFlush.softThresholdTokens": diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index 9b44b256790..46220d8718d 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -354,6 +354,11 @@ export type AgentCompactionConfig = { * Default: false (existing behavior preserved). */ truncateAfterCompaction?: boolean; + /** + * Send a "🧹 Compacting context..." notice to the user when compaction starts. + * Default: false (silent by default). + */ + notifyUser?: boolean; }; export type AgentCompactionMemoryFlushConfig = { diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index 0bed15a1760..99777b7bf8a 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -141,6 +141,7 @@ export const AgentDefaultsSchema = z }) .strict() .optional(), + notifyUser: z.boolean().optional(), }) .strict() .optional(),