Skip to content

Commit afec76b

Browse files
daniel-rudaevclaude
andcommitted
fix: correct responsePrefix patch — use resolveIdentityNamePrefix, exact string matches
Previous patch failed: wrong function name (resolveIdentityName vs resolveIdentityNamePrefix), getReplyFromConfig import doesn't exist as top-level import on current upstream/main. Rewritten with exact string matching against current upstream source. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f2ae7df commit afec76b

1 file changed

Lines changed: 82 additions & 67 deletions

File tree

Lines changed: 82 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
#!/usr/bin/env bash
22
# Fix: responsePrefix {model} not interpolating in heartbeat replies (#43064)
3-
# Applies changes from upstream PR #46858 (unmerged as of 2026-04-09).
3+
# Based on upstream PR #46858 (unmerged as of 2026-04-09), adapted to current main.
44
#
5-
# Root cause: heartbeat runner calls resolveEffectiveMessagesConfig() to get
6-
# responsePrefix but never interpolates template variables like {model} and
7-
# {provider}. The prefix is used as-is, so "{model}" appears as literal text.
5+
# Root cause: heartbeat runner gets responsePrefix from resolveEffectiveMessagesConfig()
6+
# but never interpolates template variables like {model}/{provider}.
87
#
9-
# Fix: After the LLM responds, interpolate the prefix template using
10-
# resolveResponsePrefixTemplate() with model/provider context captured via
11-
# an onModelSelected callback passed to getReplyFromConfig().
8+
# Fix: Capture model/provider via onModelSelected callback, then interpolate after LLM responds.
129
set -euo pipefail
1310

1411
echo "[patch] Applying responsePrefix interpolation fix (#43064, PR #46858)..."
@@ -22,63 +19,69 @@ fi
2219
node -e '
2320
const fs = require("fs");
2421
let code = fs.readFileSync(process.argv[1], "utf8");
22+
let changes = 0;
2523
26-
// 1. Add import for resolveIdentityName (already importing resolveEffectiveMessagesConfig)
27-
code = code.replace(
28-
/import \{ resolveEffectiveMessagesConfig \} from "\.\.\/agents\/identity\.js";/,
29-
`import { resolveEffectiveMessagesConfig, resolveIdentityName } from "../agents/identity.js";`
30-
);
24+
// 1. Add resolveIdentityNamePrefix to identity import
25+
const oldIdentityImport = `import { resolveEffectiveMessagesConfig } from "../agents/identity.js";`;
26+
const newIdentityImport = `import { resolveEffectiveMessagesConfig, resolveIdentityNamePrefix } from "../agents/identity.js";`;
27+
if (code.includes(oldIdentityImport)) {
28+
code = code.replace(oldIdentityImport, newIdentityImport);
29+
changes++;
30+
console.log("[patch] 1. Added resolveIdentityNamePrefix import");
31+
} else {
32+
console.error("[patch] ERROR: Could not find identity import");
33+
process.exit(1);
34+
}
3135
32-
// 2. Add imports for response-prefix-template
33-
const prefixImport = `import {
36+
// 2. Add response-prefix-template imports after HEARTBEAT_TOKEN import
37+
const tokenImport = `import { HEARTBEAT_TOKEN } from "../auto-reply/tokens.js";`;
38+
const newImports = `import { HEARTBEAT_TOKEN } from "../auto-reply/tokens.js";
39+
import {
3440
extractShortModelName,
3541
resolveResponsePrefixTemplate,
3642
type ResponsePrefixContext,
3743
} from "../auto-reply/reply/response-prefix-template.js";`;
44+
if (code.includes(tokenImport)) {
45+
code = code.replace(tokenImport, newImports);
46+
changes++;
47+
console.log("[patch] 2. Added response-prefix-template imports");
48+
} else {
49+
console.error("[patch] ERROR: Could not find HEARTBEAT_TOKEN import");
50+
process.exit(1);
51+
}
3852
39-
// Insert after the getReplyFromConfig import
40-
code = code.replace(
41-
/import \{ getReplyFromConfig \} from "\.\.\/auto-reply\/reply\.js";/,
42-
`import { getReplyFromConfig } from "../auto-reply/reply.js";\n${prefixImport}`
43-
);
44-
45-
// 3. Replace responsePrefix resolution with template-aware version
46-
code = code.replace(
47-
/const responsePrefix = resolveEffectiveMessagesConfig\(cfg, agentId, \{\n\s+channel: delivery\.channel !== "none" \? delivery\.channel : undefined,\n\s+accountId: delivery\.accountId,\n\s+\}\)\.responsePrefix;/,
48-
`const responsePrefixTemplate = resolveEffectiveMessagesConfig(cfg, agentId, {
53+
// 3. Replace responsePrefix with responsePrefixTemplate + context
54+
const oldPrefixBlock = ` const responsePrefix = resolveEffectiveMessagesConfig(cfg, agentId, {
55+
channel: delivery.channel !== "none" ? delivery.channel : undefined,
56+
accountId: delivery.accountId,
57+
}).responsePrefix;`;
58+
const newPrefixBlock = ` const responsePrefixTemplate = resolveEffectiveMessagesConfig(cfg, agentId, {
4959
channel: delivery.channel !== "none" ? delivery.channel : undefined,
5060
accountId: delivery.accountId,
5161
}).responsePrefix;
5262
const responsePrefixContext: ResponsePrefixContext = {
53-
identityName: resolveIdentityName(cfg, agentId),
63+
identityName: resolveIdentityNamePrefix(cfg, agentId),
5464
};
55-
let responsePrefix = responsePrefixTemplate;`
56-
);
57-
58-
// 4. Move heartbeatOkText inside the sendOk closure (before LLM runs, model unknown)
59-
code = code.replace(
60-
/const heartbeatOkText = responsePrefix \? `\$\{responsePrefix\} \$\{HEARTBEAT_TOKEN\}` : HEARTBEAT_TOKEN;\n\s+const outboundSession/,
61-
`const outboundSession`
62-
);
63-
64-
// Insert heartbeatOkText inside the sendOk function, before the heartbeatPlugin check
65-
code = code.replace(
66-
/if \(!canAttemptHeartbeatOk \|\| delivery\.channel === "none" \|\| !delivery\.to\) \{\n\s+return false;\n\s+\}/,
67-
`if (!canAttemptHeartbeatOk || delivery.channel === "none" || !delivery.to) {
68-
return false;
69-
}
70-
const heartbeatOkText = responsePrefix
71-
? \`\${responsePrefix} \${HEARTBEAT_TOKEN}\`
72-
: HEARTBEAT_TOKEN;`
73-
);
65+
let responsePrefix = responsePrefixTemplate;`;
66+
if (code.includes(oldPrefixBlock)) {
67+
code = code.replace(oldPrefixBlock, newPrefixBlock);
68+
changes++;
69+
console.log("[patch] 3. Replaced responsePrefix with template + context");
70+
} else {
71+
console.error("[patch] ERROR: Could not find responsePrefix block");
72+
process.exit(1);
73+
}
7474
75-
// Remove the duplicate heartbeatOkText declaration if the original pattern was slightly different
76-
// (safety — no-op if already removed above)
77-
78-
// 5. Add onModelSelected callback before replyOpts
79-
code = code.replace(
80-
/const replyOpts = heartbeatModelOverride\n?\s+\?/,
81-
`const onModelSelected = (selection: {
75+
// 4. Add onModelSelected callback and pass it to replyOpts
76+
const oldReplyOpts = ` const replyOpts = heartbeatModelOverride
77+
? {
78+
isHeartbeat: true,
79+
heartbeatModelOverride,
80+
suppressToolErrorWarnings,
81+
bootstrapContextMode,
82+
}
83+
: { isHeartbeat: true, suppressToolErrorWarnings, bootstrapContextMode };`;
84+
const newReplyOpts = ` const onModelSelected = (selection: {
8285
provider: string;
8386
model: string;
8487
thinkLevel?: string;
@@ -89,29 +92,41 @@ code = code.replace(
8992
responsePrefixContext.thinkingLevel = selection.thinkLevel ?? "off";
9093
};
9194
const replyOpts = heartbeatModelOverride
92-
?`
93-
);
94-
95-
// 6. Add onModelSelected to both branches of replyOpts
96-
code = code.replace(
97-
/suppressToolErrorWarnings,\n\s+bootstrapContextMode,\n\s+\}\n\s+: \{ isHeartbeat: true, suppressToolErrorWarnings, bootstrapContextMode \};/,
98-
`suppressToolErrorWarnings,
95+
? {
96+
isHeartbeat: true,
97+
heartbeatModelOverride,
98+
suppressToolErrorWarnings,
9999
bootstrapContextMode,
100100
onModelSelected,
101101
}
102-
: { isHeartbeat: true, suppressToolErrorWarnings, bootstrapContextMode, onModelSelected };`
103-
);
102+
: { isHeartbeat: true, suppressToolErrorWarnings, bootstrapContextMode, onModelSelected };`;
103+
if (code.includes(oldReplyOpts)) {
104+
code = code.replace(oldReplyOpts, newReplyOpts);
105+
changes++;
106+
console.log("[patch] 4. Added onModelSelected + updated replyOpts");
107+
} else {
108+
console.error("[patch] ERROR: Could not find replyOpts block");
109+
process.exit(1);
110+
}
104111
105-
// 7. Add responsePrefix interpolation after getReplyFromConfig
106-
code = code.replace(
107-
/const replyResult = await getReplyFromConfig\(ctx, replyOpts, cfg\);\n\s+const replyPayload = resolveHeartbeatReplyPayload\(replyResult\);/,
108-
`const replyResult = await getReplyFromConfig(ctx, replyOpts, cfg);
112+
// 5. Interpolate prefix after LLM responds (before normalizeHeartbeatReply)
113+
const oldAckMaxChars = ` const ackMaxChars = resolveHeartbeatAckMaxChars(cfg, heartbeat);
114+
const normalized = normalizeHeartbeatReply(replyPayload, responsePrefix, ackMaxChars);`;
115+
const newAckMaxChars = ` const ackMaxChars = resolveHeartbeatAckMaxChars(cfg, heartbeat);
116+
// Interpolate template variables (e.g. {model}, {provider}) now that the LLM has responded.
109117
responsePrefix = resolveResponsePrefixTemplate(responsePrefixTemplate, responsePrefixContext);
110-
const replyPayload = resolveHeartbeatReplyPayload(replyResult);`
111-
);
118+
const normalized = normalizeHeartbeatReply(replyPayload, responsePrefix, ackMaxChars);`;
119+
if (code.includes(oldAckMaxChars)) {
120+
code = code.replace(oldAckMaxChars, newAckMaxChars);
121+
changes++;
122+
console.log("[patch] 5. Added prefix interpolation before normalizeHeartbeatReply");
123+
} else {
124+
console.error("[patch] ERROR: Could not find ackMaxChars + normalizeHeartbeatReply block");
125+
process.exit(1);
126+
}
112127
113128
fs.writeFileSync(process.argv[1], code, "utf8");
129+
console.log(`[patch] All ${changes} changes applied successfully.`);
114130
' "$FILE"
115131

116-
echo "[patch] Fixed $FILE"
117132
echo "[patch] responsePrefix interpolation fix applied."

0 commit comments

Comments
 (0)