fix: use _ctx.workspaceDir and strip channel metadata

- Hook callbacks now get workspace from _ctx.workspaceDir (the hook
  context parameter) instead of api.runtime.workspaceDir which is
  undefined in gateway mode. This was the root cause of the plugin
  silently doing nothing on all remote/gateway deployments.

- extractStateFromMessages now strips Telegram/Discord channel
  metadata ("Conversation info (untrusted metadata)") from user
  messages before saving to CURRENT_STATE.md, preventing garbage
  data in the Objective field.

Tested on: macOS local (GPT-5.4), Oracle Cloud (MiniMax M2.5)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 14:44:12 +10:00
co-authored by Claude Opus 4.6
parent 86d820af81
commit 53c75986fa
+13 -7
View File
@@ -106,7 +106,14 @@ function extractStateFromMessages(messages) {
? msg.content.filter(b => b?.type === "text").map(b => b.text).join("\n") ? msg.content.filter(b => b?.type === "text").map(b => b.text).join("\n")
: null; : null;
if (!content) continue; if (!content) continue;
if (role === "user") userMessages.push(content); // Strip channel metadata (Telegram, Discord, etc.) from user messages
const cleaned = role === "user"
? content
.replace(/^Conversation info \(untrusted metadata\):[\s\S]*?\n\n/m, "")
.replace(/^Sender \(untrusted metadata\):[\s\S]*?\n\n/m, "")
.trim()
: content;
if (role === "user" && cleaned) userMessages.push(cleaned);
if (role === "assistant") assistantMessages.push(content); if (role === "assistant") assistantMessages.push(content);
} }
@@ -152,14 +159,13 @@ const plugin = {
register(api) { register(api) {
const log = api.logger || console; const log = api.logger || console;
const getWorkspace = () => api.runtime?.workspaceDir;
const getConfig = () => api.pluginConfig || {}; const getConfig = () => api.pluginConfig || {};
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// HOOK 1: before_agent_start — inject recovered state into context // HOOK 1: before_agent_start — inject recovered state into context
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.on("before_agent_start", async (_event, _ctx) => { api.on("before_agent_start", async (_event, _ctx) => {
const ws = getWorkspace(); const ws = _ctx?.workspaceDir;
const statePath = resolveStatePath(ws); const statePath = resolveStatePath(ws);
if (!statePath) return; if (!statePath) return;
@@ -184,7 +190,7 @@ const plugin = {
// HOOK 2: before_compaction — inject state so it survives compaction // HOOK 2: before_compaction — inject state so it survives compaction
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.on("before_compaction", async (_event, _ctx) => { api.on("before_compaction", async (_event, _ctx) => {
const ws = getWorkspace(); const ws = _ctx?.workspaceDir;
const statePath = resolveStatePath(ws); const statePath = resolveStatePath(ws);
if (!statePath) return; if (!statePath) return;
@@ -205,7 +211,7 @@ const plugin = {
// HOOK 3: before_reset (/new) — archive current state // HOOK 3: before_reset (/new) — archive current state
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.on("before_reset", async (_event, _ctx) => { api.on("before_reset", async (_event, _ctx) => {
const ws = getWorkspace(); const ws = _ctx?.workspaceDir;
const config = getConfig(); const config = getConfig();
if (!ws || config.archiveOnNew === false) return; if (!ws || config.archiveOnNew === false) return;
@@ -225,7 +231,7 @@ const plugin = {
// HOOK 4: agent_end — extract and save working state // HOOK 4: agent_end — extract and save working state
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.on("agent_end", async (event, _ctx) => { api.on("agent_end", async (event, _ctx) => {
const ws = getWorkspace(); const ws = _ctx?.workspaceDir;
const config = getConfig(); const config = getConfig();
if (!ws || config.autoExtract === false) return; if (!ws || config.autoExtract === false) return;
@@ -252,7 +258,7 @@ const plugin = {
// HOOK 5: session_end — ensure state file exists // HOOK 5: session_end — ensure state file exists
// ------------------------------------------------------------------ // ------------------------------------------------------------------
api.on("session_end", async (_event, _ctx) => { api.on("session_end", async (_event, _ctx) => {
const ws = getWorkspace(); const ws = _ctx?.workspaceDir;
const statePath = resolveStatePath(ws); const statePath = resolveStatePath(ws);
if (!statePath) return; if (!statePath) return;