mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 02:29:33 +01:00
Merge pull request #709: fix(agent): COD-549 preserve worker IDs after readiness failures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
bd3320ff90
5 files changed
+120
-17
No files matched your search
@@ -30,6 +30,7 @@ import {
|
||||
buildSendBody,
|
||||
deleteRefusal,
|
||||
describeFailure,
|
||||
describeRequestError,
|
||||
httpRequest,
|
||||
inboxCell,
|
||||
inputRefusal,
|
||||
@@ -548,6 +549,75 @@ describe('agent spawn', () => {
|
||||
expect(deps.calls).toHaveLength(1);
|
||||
expect(deps.err.join('')).toMatch(/SESSION_BUSY/);
|
||||
});
|
||||
|
||||
it.each([false, true])('preserves the created id when readiness throws (json=%s)', async (json) => {
|
||||
const deps = fakeDeps((options) => {
|
||||
if (options.path === '/api/v1/quick-start') return ok({ sessionId: OTHER, caseName: 'c' });
|
||||
throw new Error('readiness connection reset');
|
||||
}, json);
|
||||
const exit = await agentSpawn(deps, { caseName: 'c', mode: 'claude', ready: true, timeoutMs: 1000 }).catch(
|
||||
() => undefined
|
||||
);
|
||||
|
||||
if (json) {
|
||||
expect(deps.out).toHaveLength(1);
|
||||
expect(JSON.parse(deps.out[0])).toEqual({
|
||||
sessionId: OTHER,
|
||||
caseName: 'c',
|
||||
readinessError: 'readiness connection reset',
|
||||
});
|
||||
expect(deps.err).toEqual([]);
|
||||
} else {
|
||||
expect(deps.out).toEqual([OTHER]);
|
||||
expect(deps.err.join('')).toContain('readiness check failed: readiness connection reset');
|
||||
expect(deps.err.join('')).not.toMatch(/composer not seen|startup dialog/);
|
||||
}
|
||||
expect(exit).toBe(EXIT.error);
|
||||
expect(deps.calls.map(({ method, path }) => ({ method, path }))).toEqual([
|
||||
{ method: 'POST', path: '/api/v1/quick-start' },
|
||||
{ method: 'GET', path: `/api/v1/sessions/${OTHER}/wait-output` },
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ error: new Error(''), kind: 'Error' },
|
||||
{ error: '', kind: 'string' },
|
||||
])('reports a readiness failure when the thrown $kind has no message', async ({ error }) => {
|
||||
const deps = fakeDeps((options) => {
|
||||
if (options.path === '/api/v1/quick-start') return ok({ sessionId: OTHER });
|
||||
throw error;
|
||||
}, true);
|
||||
|
||||
expect(await agentSpawn(deps, { caseName: 'c', mode: 'claude', ready: true, timeoutMs: 1000 })).toBe(EXIT.error);
|
||||
expect(deps.out).toHaveLength(1);
|
||||
expect(JSON.parse(deps.out[0])).toEqual({ sessionId: OTHER, readinessError: 'request failed without a message' });
|
||||
expect(deps.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('keeps the error code when a refused connection rejects with an empty AggregateError', async () => {
|
||||
// Node 22 on a host resolving to ::1 and 127.0.0.1: message '', code ECONNREFUSED.
|
||||
const refused = Object.assign(new AggregateError([new Error('connect ECONNREFUSED ::1:3000')], ''), {
|
||||
code: 'ECONNREFUSED',
|
||||
});
|
||||
const deps = fakeDeps((options) => {
|
||||
if (options.path === '/api/v1/quick-start') return ok({ sessionId: OTHER });
|
||||
throw refused;
|
||||
}, true);
|
||||
|
||||
expect(await agentSpawn(deps, { caseName: 'c', mode: 'claude', ready: true, timeoutMs: 1000 })).toBe(EXIT.error);
|
||||
expect(JSON.parse(deps.out[0])).toEqual({ sessionId: OTHER, readinessError: 'ECONNREFUSED' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('describeRequestError', () => {
|
||||
it('prefers the message, then the code, then a generic line', () => {
|
||||
expect(describeRequestError(new Error('socket hang up'))).toBe('socket hang up');
|
||||
expect(describeRequestError(Object.assign(new AggregateError([], ''), { code: 'ECONNREFUSED' }))).toBe(
|
||||
'ECONNREFUSED'
|
||||
);
|
||||
expect(describeRequestError(new Error(''))).toBe('request failed without a message');
|
||||
expect(describeRequestError('')).toBe('request failed without a message');
|
||||
});
|
||||
});
|
||||
|
||||
describe('session id prefixes', () => {
|
||||
|
||||
@@ -248,6 +248,17 @@ describe('protocol: both eras', () => {
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('CODEMAN_API_URL');
|
||||
});
|
||||
|
||||
it('a refused connection with an empty AggregateError still names the error code', async () => {
|
||||
const refused: ApiRequest = async () => {
|
||||
throw Object.assign(new AggregateError([new Error('connect ECONNREFUSED ::1:9')], ''), { code: 'ECONNREFUSED' });
|
||||
};
|
||||
const { out, send } = server(config(), { request: refused, calls: [] });
|
||||
await send({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'list_sessions', arguments: {} } });
|
||||
const result = (out[0] as { result: { isError: boolean; content: Array<{ text: string }> } }).result;
|
||||
expect(result.isError).toBe(true);
|
||||
expect(result.content[0].text).toContain('Cannot reach Codeman at http://127.0.0.1:9: ECONNREFUSED.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('tool definitions', () => {
|
||||
|
||||
Reference in new issue
Block a user