refactor(channels): share reset-command normalization utility
This commit is contained in:
@@ -19,13 +19,14 @@ Scope: Production-risk-first audit of bugs, code improvements, and feature oppor
|
||||
- ✅ F-003 addressed: tool execution now has an `AbortSignal` contract, executor triggers abort on timeout, high-risk tools (`shell.exec`, sandbox docker exec, `process.start`, browser tools, `web.fetch`, `web.search`) respond to cancellation, and executor regression tests verify cancellable tools do not apply side effects after timeout.
|
||||
- ✅ F-015 addressed: retry defaults no longer classify timeout-style failures as non-retryable, improving resilience for transient timeout conditions.
|
||||
- ✅ F-011 addressed: Slack user-name resolution now uses bounded TTL+LRU caching to prevent unbounded growth.
|
||||
- ◑ F-013 partially addressed: reset-command normalization is now shared across Discord/Slack/WhatsApp adapters via `src/channels/utils.ts`, reducing duplicated command-parsing logic.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Current health snapshot:
|
||||
- `pnpm typecheck`: passing
|
||||
- `pnpm build`: passing
|
||||
- `pnpm test:run`: passing (`140/140` files, `1770/1770` tests)
|
||||
- `pnpm test:run`: passing (`140/140` files, `1773/1773` tests)
|
||||
- `pnpm lint`: failing (`148 errors`, `530 warnings`)
|
||||
|
||||
Top conclusions:
|
||||
@@ -260,6 +261,9 @@ Remediation update (2026-02-16):
|
||||
- Recommended fix:
|
||||
- Extract shared middleware utilities for common inbound/outbound behaviors.
|
||||
|
||||
Remediation update (2026-02-16):
|
||||
- Added shared `normalizeResetCommandText()` utility and migrated Discord/Slack/WhatsApp adapters to use it, reducing repeated reset-command parsing logic.
|
||||
|
||||
### F-014 Low: ModelRouter listener API has destructive setter footgun
|
||||
|
||||
- Severity: Low
|
||||
|
||||
+16
-1
@@ -2613,10 +2613,25 @@
|
||||
"docs/plans/analysis/2026-02-16-codebase-audit-report.md"
|
||||
],
|
||||
"test_status": "pnpm test:run src/channels/slack/adapter.test.ts + pnpm typecheck passing"
|
||||
},
|
||||
"audit-followup-channel-reset-command-dedup": {
|
||||
"status": "in_progress",
|
||||
"date": "2026-02-16",
|
||||
"updated": "2026-02-16",
|
||||
"summary": "Started reducing channel adapter duplication by extracting shared reset-command normalization and migrating Discord/Slack/WhatsApp adapters to use it.",
|
||||
"files_modified": [
|
||||
"src/channels/utils.ts",
|
||||
"src/channels/utils.test.ts",
|
||||
"src/channels/discord/adapter.ts",
|
||||
"src/channels/slack/adapter.ts",
|
||||
"src/channels/whatsapp/adapter.ts",
|
||||
"docs/plans/analysis/2026-02-16-codebase-audit-report.md"
|
||||
],
|
||||
"test_status": "pnpm test:run src/channels/utils.test.ts src/channels/discord/adapter.test.ts src/channels/slack/adapter.test.ts src/channels/whatsapp/adapter.test.ts + pnpm typecheck passing"
|
||||
}
|
||||
},
|
||||
"overall_progress": {
|
||||
"total_test_count": 1770,
|
||||
"total_test_count": 1773,
|
||||
"all_tests_passing": true,
|
||||
"p0_completion": "3/3 (100%)",
|
||||
"p1_completion": "4/4 (100%)",
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
ChannelAdapter,
|
||||
ChannelStatus,
|
||||
} from '../types.js';
|
||||
import { splitMessage } from '../utils.js';
|
||||
import { normalizeResetCommandText, splitMessage } from '../utils.js';
|
||||
import type { PairingManager } from '../pairing.js';
|
||||
|
||||
/** Configuration for the Discord channel adapter. */
|
||||
@@ -221,7 +221,8 @@ export class DiscordAdapter implements ChannelAdapter {
|
||||
} catch { /* ignore typing errors */ }
|
||||
|
||||
// Strip bot mention from the message text
|
||||
const text = message.content.replace(/<@!?\d+>/g, '').trim();
|
||||
const rawText = message.content.replace(/<@!?\d+>/g, '').trim();
|
||||
const text = normalizeResetCommandText(rawText);
|
||||
|
||||
// ── Extract media attachments ──
|
||||
const attachments: Attachment[] = [];
|
||||
@@ -240,7 +241,7 @@ export class DiscordAdapter implements ChannelAdapter {
|
||||
}
|
||||
|
||||
// ── Reset command ──
|
||||
if (text === '!reset' || text === 'reset') {
|
||||
if (text === '!reset') {
|
||||
this.messageHandler({
|
||||
id: message.id,
|
||||
channel: 'discord',
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
ChannelAdapter,
|
||||
ChannelStatus,
|
||||
} from '../types.js';
|
||||
import { splitMessage } from '../utils.js';
|
||||
import { normalizeResetCommandText, splitMessage } from '../utils.js';
|
||||
import type { PairingManager } from '../pairing.js';
|
||||
|
||||
/** Configuration for the Slack channel adapter. */
|
||||
@@ -344,7 +344,8 @@ export class SlackAdapter implements ChannelAdapter {
|
||||
const peerId = `${channelId}:${threadTs}`;
|
||||
|
||||
// Strip bot mentions: <@U\w+> pattern
|
||||
let text = (message.text ?? '').replace(/<@U\w+>/g, '').trim();
|
||||
const rawText = (message.text ?? '').replace(/<@U\w+>/g, '').trim();
|
||||
const text = normalizeResetCommandText(rawText);
|
||||
|
||||
// Resolve display name from Slack user ID
|
||||
const senderName = message.user
|
||||
@@ -355,7 +356,7 @@ export class SlackAdapter implements ChannelAdapter {
|
||||
const attachments = await this.extractMediaAttachments(message.files);
|
||||
|
||||
// Detect reset command
|
||||
if (text === '!reset' || text === 'reset') {
|
||||
if (text === '!reset') {
|
||||
this.messageHandler({
|
||||
id: message.ts ?? '',
|
||||
channel: 'slack',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { splitMessage } from './utils.js';
|
||||
import { normalizeResetCommandText, splitMessage } from './utils.js';
|
||||
|
||||
describe('splitMessage', () => {
|
||||
it('returns single chunk for empty string', () => {
|
||||
@@ -84,3 +84,17 @@ describe('splitMessage', () => {
|
||||
expect(result[1]).toBe('klmnopqrst');
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeResetCommandText', () => {
|
||||
it('normalizes reset to !reset', () => {
|
||||
expect(normalizeResetCommandText('reset')).toBe('!reset');
|
||||
});
|
||||
|
||||
it('keeps !reset unchanged', () => {
|
||||
expect(normalizeResetCommandText('!reset')).toBe('!reset');
|
||||
});
|
||||
|
||||
it('does not change non-command text', () => {
|
||||
expect(normalizeResetCommandText('hello')).toBe('hello');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -31,3 +31,14 @@ export function splitMessage(text: string, maxLength: number): string[] {
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize reset command variants to a canonical text form.
|
||||
* Returns '!reset' for recognized variants, otherwise returns the original text.
|
||||
*/
|
||||
export function normalizeResetCommandText(text: string): string {
|
||||
if (text === '!reset' || text === 'reset') {
|
||||
return '!reset';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
ChannelAdapter,
|
||||
ChannelStatus,
|
||||
} from '../types.js';
|
||||
import { splitMessage } from '../utils.js';
|
||||
import { normalizeResetCommandText, splitMessage } from '../utils.js';
|
||||
import type { PairingManager } from '../pairing.js';
|
||||
|
||||
/** Configuration for the WhatsApp channel adapter. */
|
||||
@@ -309,8 +309,10 @@ export class WhatsAppAdapter implements ChannelAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
text = normalizeResetCommandText(text);
|
||||
|
||||
// Detect reset command
|
||||
if (text === '!reset' || text === 'reset') {
|
||||
if (text === '!reset') {
|
||||
this.messageHandler({
|
||||
id: message.id.id,
|
||||
channel: 'whatsapp',
|
||||
|
||||
Reference in New Issue
Block a user