fix(plugin): a Pad MCP tool wakes the session whatever the server is called (TASK-3970)

Codex r3: the matcher required "pad" in the server part of the tool name,
so a Pad server registered under another name (work-tracker) was missed.
It now matches any mcp__<server>__pad_* tool. The tests table Pad tools
under four server names and four tools that are not Pad's, and add ./pad
and ../bin/pad to the Bash shapes.

Claude-Session: https://claude.ai/code/session_01EZ6hZzQiytCeBxXrtCWcVd
This commit is contained in:
xarmian
2026-10-20 00:10:46 +00:00
parent 857953fe80
commit 25c39f2aad
2 changed files with 25 additions and 2 deletions
+3 -1
View File
@@ -1105,7 +1105,9 @@ function wake($) {
// shape; the shapes are tabled in watch.test.ts rather than listed here. // shape; the shapes are tabled in watch.test.ts rather than listed here.
function usesPad(e) { function usesPad(e) {
if (e.tool === 'Bash') return /(^|[^\w.-])pad($|[^\w.-])/.test(String(e.command || '')) if (e.tool === 'Bash') return /(^|[^\w.-])pad($|[^\w.-])/.test(String(e.command || ''))
return /^mcp__.*pad.*__pad_/i.test(String(e.tool || '')) // Any server name: Pad's tools are pad_* whatever the person called the server
// (Codex r3: `.*pad.*` in the server part missed a server named otherwise).
return /^mcp__.+__pad_[a-z_]+$/i.test(String(e.tool || ''))
} }
// Silent by construction: every failure here leaves the session as it was. // Silent by construction: every failure here leaves the session as it was.
+22 -1
View File
@@ -114,7 +114,7 @@ const RUNS_PAD = [
'pad bootstrap', 'pad', ' pad item list', 'cd /w && pad item list', 'ls; pad project next', 'a || pad x', 'a | pad x', 'pad bootstrap', 'pad', ' pad item list', 'cd /w && pad item list', 'ls; pad project next', 'a || pad x', 'a | pad x',
'bash -c "pad item list"', `sh -c 'pad project next'`, 'bash -c "pad"', `sh -c 'pad'`, 'echo $(pad)', 'X="$(pad auth whoami)"', 'bash -c "pad item list"', `sh -c 'pad project next'`, 'bash -c "pad"', `sh -c 'pad'`, 'echo $(pad)', 'X="$(pad auth whoami)"',
'echo `pad auth whoami`', 'pad;ls', 'pad&&ls', '\\pad item list', 'PAD_AGENT=x pad bootstrap', '~/.local/bin/pad project next', 'echo `pad auth whoami`', 'pad;ls', 'pad&&ls', '\\pad item list', 'PAD_AGENT=x pad bootstrap', '~/.local/bin/pad project next',
'/usr/local/bin/pad item show TASK-5', 'env pad bootstrap', 'time pad x', '(pad x)', '{ pad x; }', 'pad\titem list', '/usr/local/bin/pad item show TASK-5', './pad item list', '../bin/pad x', 'env pad bootstrap', 'time pad x', '(pad x)', '{ pad x; }', 'pad\titem list',
] ]
const NOT_PAD = ['ls -la && git status', 'cat ~/.padrc', 'echo spad', 'padd --version', 'ls pad-monitor.sh', 'grep -r ipad .', 'cat notepad.txt', 'echo pad_x'] const NOT_PAD = ['ls -la && git status', 'cat ~/.padrc', 'echo spad', 'padd --version', 'ls pad-monitor.sh', 'grep -r ipad .', 'cat notepad.txt', 'echo pad_x']
@@ -143,6 +143,27 @@ test('a command that does not run pad wakes nothing', async ($, on) => {
expect(s.runs).toEqual([]) expect(s.runs).toEqual([])
}) })
// Pad MCP tools under any server name, and tools that are not Pad's.
const PAD_MCP = ['mcp__pad__pad_item', 'mcp__claude_ai_Pad__pad_project', 'mcp__plugin_pad_pad__pad_search', 'mcp__work-tracker__pad_collection']
const NOT_PAD_MCP = ['mcp__pad__other', 'mcp__ipad__notes', 'mcp__github__create_issue', 'mcp__notepad__pad']
test('a Pad MCP tool under any server name wakes the session; other tools do not', async ($, on) => {
mock.clock(on)
const s = stubs(on, { armed: false })
await $.session.start({ cwd: '/work/demo' })
for (const tool of NOT_PAD_MCP) await $.tool.call({ tool } as any)
await settle()
expect(s.runs).toEqual([])
for (const tool of PAD_MCP) {
const before = s.atCall.length
await $.tool.call({ tool } as any)
await settle()
expect([tool, s.atCall[before].includes('pad session register')]).toEqual([tool, true])
await $.session.end({ reason: 'prompt_input_exit' })
s.runs.length = 0
}
})
test('a Pad MCP tool call wakes the session too', async ($, on) => { test('a Pad MCP tool call wakes the session too', async ($, on) => {
mock.clock(on) mock.clock(on)
const s = stubs(on, { armed: true }) const s = stubs(on, { armed: true })