mirror of
https://github.com/Ryder-Sun/Meldwork.git
synced 2026-11-07 06:56:03 -10:00
The ACP transport failed the whole turn on anything it did not recognise: a banner line on stdout, a notification with an unknown method, a session update kind added by a newer Agent, or an unknown request. Every Agent upgrade that adds a message therefore broke Meldwork until it was updated, and a single large tool result (over 1 MiB on one line) failed the turn. - stdout lines that are not JSON objects are ignored - unknown notifications and session updates that fail validation are dropped before they reach the SDK, which logs rejected messages with their params - unknown requests are forwarded with their params stripped, so the Agent gets a proper 'method not found' answer instead of waiting, and nothing secret-bearing is logged - the per-line limit is 4 MiB (still bounded; total input and reply limits are unchanged) - truncated JSON that starts like a message, an invalid response payload and invalid permission requests still fail closed The Kimi 'unsafe protocol input' test described the old behaviour; it now checks that malformed JSON still fails closed and that unknown traffic carrying a secret neither fails the turn nor reaches a log. The new tolerance test fails on the previous commit (LOCAL_AGENT_PROCESS_FAILED). Checked with: cli-adapters-agent-protocols (69 pass), cli-native-acp-lifecycle (15), cli-acp-tolerance, cli-adapters (64).
73 lines
3.1 KiB
JavaScript
73 lines
3.1 KiB
JavaScript
const test = require('node:test')
|
|
const assert = require('node:assert/strict')
|
|
const fs = require('node:fs')
|
|
const os = require('node:os')
|
|
const path = require('node:path')
|
|
|
|
const adapters = require('../../../src/agents/cli/cli-adapters.cjs')
|
|
const {
|
|
executable,
|
|
readJsonWhenReady,
|
|
within,
|
|
} = require('../../support/cli-adapters-test-helpers.cjs')
|
|
|
|
function noisyHermes(directory) {
|
|
return executable(directory, 'hermes-noisy-acp.cjs', `
|
|
const fs = require('node:fs')
|
|
const readline = require('node:readline')
|
|
if (process.argv[2] !== 'acp') {
|
|
process.stdout.write('legacy fallback')
|
|
process.exit(0)
|
|
}
|
|
const input = readline.createInterface({ input: process.stdin })
|
|
const send = value => process.stdout.write(JSON.stringify(value) + '\\n')
|
|
process.stdout.write('Hermes 9.9.9 starting up...\\n')
|
|
input.on('line', (line) => {
|
|
let message
|
|
try { message = JSON.parse(line) } catch { return }
|
|
const sessionId = message.params?.sessionId || 'noisy-session'
|
|
if (message.id === 9001 && !message.method) {
|
|
fs.writeFileSync(process.env.MELDWORK_TEST_UNKNOWN_RESPONSE, JSON.stringify(message))
|
|
return
|
|
}
|
|
if (message.method === 'initialize') {
|
|
send({ jsonrpc: '2.0', id: message.id, result: { protocolVersion: 1 } })
|
|
} else if (message.method === 'session/new') {
|
|
send({ jsonrpc: '2.0', id: message.id, result: { sessionId } })
|
|
} else if (message.method === 'session/set_mode') {
|
|
send({ jsonrpc: '2.0', id: message.id, result: {} })
|
|
} else if (message.method === 'session/prompt') {
|
|
process.stdout.write('WARNING: update available (not protocol traffic)\\n')
|
|
send({ jsonrpc: '2.0', method: '_vendor/progress', params: { done: 1 } })
|
|
send({ jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: { sessionUpdate: 'brand_new_update_kind', payload: 1 } } })
|
|
send({ jsonrpc: '2.0', id: 9001, method: 'vendor/unknown_request', params: { sessionId } })
|
|
send({ jsonrpc: '2.0', method: 'session/update', params: { sessionId, update: {
|
|
sessionUpdate: 'agent_message_chunk', content: { type: 'text', text: 'acp answer despite the noise' },
|
|
} } })
|
|
setTimeout(() => send({ jsonrpc: '2.0', id: message.id, result: { stopReason: 'end_turn' } }), 50)
|
|
}
|
|
})
|
|
`)
|
|
}
|
|
|
|
test('ACP turns survive banners, unknown notifications, new update kinds and unknown requests', async (t) => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-acp-tolerance-'))
|
|
t.after(async () => {
|
|
await adapters.shutdownAcpSessionRuntime?.()
|
|
fs.rmSync(directory, { recursive: true, force: true })
|
|
})
|
|
const unknownResponse = path.join(directory, 'unknown-response.json')
|
|
const agent = { kind: 'hermes', executable: noisyHermes(directory), name: 'Hermes' }
|
|
|
|
const result = await within(adapters.runAgent(agent, 'say something', directory, {
|
|
acpPersistenceKey: 'workspace:hermes:tolerance',
|
|
env: { MELDWORK_TEST_UNKNOWN_RESPONSE: unknownResponse },
|
|
}), 10000)
|
|
|
|
assert.equal(result.text, 'acp answer despite the noise')
|
|
// The Agent is told the method is unknown instead of being left waiting.
|
|
const response = await readJsonWhenReady(unknownResponse)
|
|
assert.equal(response.id, 9001)
|
|
assert.equal(response.error?.code, -32601)
|
|
})
|