refactor(auto-reply,telegram,config): extract guard and forum helpers

This commit is contained in:
Peter Steinberger
2026-03-02 23:47:14 +00:00
parent dc825e59f5
commit 32ecd6f579
7 changed files with 125 additions and 129 deletions

View File

@@ -0,0 +1,64 @@
import { loadCronStore, resolveCronStorePath } from "../../cron/store.js";
import type { ReplyPayload } from "../types.js";
export const UNSCHEDULED_REMINDER_NOTE =
"Note: I did not schedule a reminder in this turn, so this will not trigger automatically.";
const REMINDER_COMMITMENT_PATTERNS: RegExp[] = [
/\b(?:i\s*[']?ll|i will)\s+(?:make sure to\s+)?(?:remember|remind|ping|follow up|follow-up|check back|circle back)\b/i,
/\b(?:i\s*[']?ll|i will)\s+(?:set|create|schedule)\s+(?:a\s+)?reminder\b/i,
];
export function hasUnbackedReminderCommitment(text: string): boolean {
const normalized = text.toLowerCase();
if (!normalized.trim()) {
return false;
}
if (normalized.includes(UNSCHEDULED_REMINDER_NOTE.toLowerCase())) {
return false;
}
return REMINDER_COMMITMENT_PATTERNS.some((pattern) => pattern.test(text));
}
/**
* Returns true when the cron store has at least one enabled job that shares the
* current session key. Used to suppress the "no reminder scheduled" guard note
* when an existing cron (created in a prior turn) already covers the commitment.
*/
export async function hasSessionRelatedCronJobs(params: {
cronStorePath?: string;
sessionKey?: string;
}): Promise<boolean> {
try {
const storePath = resolveCronStorePath(params.cronStorePath);
const store = await loadCronStore(storePath);
if (store.jobs.length === 0) {
return false;
}
if (params.sessionKey) {
return store.jobs.some((job) => job.enabled && job.sessionKey === params.sessionKey);
}
return false;
} catch {
// If we cannot read the cron store, do not suppress the note.
return false;
}
}
export function appendUnscheduledReminderNote(payloads: ReplyPayload[]): ReplyPayload[] {
let appended = false;
return payloads.map((payload) => {
if (appended || payload.isError || typeof payload.text !== "string") {
return payload;
}
if (!hasUnbackedReminderCommitment(payload.text)) {
return payload;
}
appended = true;
const trimmed = payload.text.trimEnd();
return {
...payload,
text: `${trimmed}\n\n${UNSCHEDULED_REMINDER_NOTE}`,
};
});
}

View File

@@ -15,7 +15,6 @@ import {
updateSessionStoreEntry,
} from "../../config/sessions.js";
import type { TypingMode } from "../../config/types.js";
import { loadCronStore, resolveCronStorePath } from "../../cron/store.js";
import { emitAgentEvent } from "../../infra/agent-events.js";
import { emitDiagnosticEvent, isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
import { generateSecureUuid } from "../../infra/secure-random.js";
@@ -40,6 +39,11 @@ import {
} from "./agent-runner-helpers.js";
import { runMemoryFlushIfNeeded } from "./agent-runner-memory.js";
import { buildReplyPayloads } from "./agent-runner-payloads.js";
import {
appendUnscheduledReminderNote,
hasSessionRelatedCronJobs,
hasUnbackedReminderCommitment,
} from "./agent-runner-reminder-guard.js";
import { appendUsageLine, formatResponseUsageLine } from "./agent-runner-utils.js";
import { createAudioAsVoiceBuffer, createBlockReplyPipeline } from "./block-reply-pipeline.js";
import { resolveEffectiveBlockStreamingConfig } from "./block-streaming.js";
@@ -54,71 +58,6 @@ import { createTypingSignaler } from "./typing-mode.js";
import type { TypingController } from "./typing.js";
const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000;
const UNSCHEDULED_REMINDER_NOTE =
"Note: I did not schedule a reminder in this turn, so this will not trigger automatically.";
const REMINDER_COMMITMENT_PATTERNS: RegExp[] = [
/\b(?:i\s*[']?ll|i will)\s+(?:make sure to\s+)?(?:remember|remind|ping|follow up|follow-up|check back|circle back)\b/i,
/\b(?:i\s*[']?ll|i will)\s+(?:set|create|schedule)\s+(?:a\s+)?reminder\b/i,
];
function hasUnbackedReminderCommitment(text: string): boolean {
const normalized = text.toLowerCase();
if (!normalized.trim()) {
return false;
}
if (normalized.includes(UNSCHEDULED_REMINDER_NOTE.toLowerCase())) {
return false;
}
return REMINDER_COMMITMENT_PATTERNS.some((pattern) => pattern.test(text));
}
/**
* Returns true when the cron store has at least one enabled job that shares the
* current session key. Used to suppress the "no reminder scheduled" guard note
* when an existing cron (created in a prior turn) already covers the commitment.
*/
async function hasSessionRelatedCronJobs(params: {
cronStorePath?: string;
sessionKey?: string;
}): Promise<boolean> {
try {
const storePath = resolveCronStorePath(params.cronStorePath);
const store = await loadCronStore(storePath);
if (store.jobs.length === 0) {
return false;
}
// If we have a session key, only consider cron jobs from the same session.
// This avoids suppressing the note due to unrelated cron jobs.
if (params.sessionKey) {
return store.jobs.some((job) => job.enabled && job.sessionKey === params.sessionKey);
}
// No session key available — cannot scope the check, so do not suppress
// the note. Broadening to all enabled jobs could silently swallow the
// guard note due to unrelated sessions.
return false;
} catch {
// If we cannot read the cron store, do not suppress the note.
return false;
}
}
function appendUnscheduledReminderNote(payloads: ReplyPayload[]): ReplyPayload[] {
let appended = false;
return payloads.map((payload) => {
if (appended || payload.isError || typeof payload.text !== "string") {
return payload;
}
if (!hasUnbackedReminderCommitment(payload.text)) {
return payload;
}
appended = true;
const trimmed = payload.text.trimEnd();
return {
...payload,
text: `${trimmed}\n\n${UNSCHEDULED_REMINDER_NOTE}`,
};
});
}
export async function runReplyAgent(params: {
commandBody: string;