feat: remove container layer, run agents as direct host processes
- Rewrite container-runner.ts to spawn node processes directly instead of Docker/Apple Container - Runner paths configurable via env vars (NANOCLAW_GROUP_DIR, etc.) with container-path defaults for backwards compat - Pass real credentials directly (no credential proxy needed) - Remove ensureContainerSystemRunning() and startCredentialProxy() from startup - Add build:runners script for building agent-runner and codex-runner - Fix TS strict mode errors in agent-runner ipc-mcp-stdio.ts
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { extractSessionCommand, handleSessionCommand, isSessionCommandAllowed } from './session-commands.js';
|
||||
import {
|
||||
extractSessionCommand,
|
||||
handleSessionCommand,
|
||||
isSessionCommandAllowed,
|
||||
} from './session-commands.js';
|
||||
import type { NewMessage } from './types.js';
|
||||
import type { SessionCommandDeps } from './session-commands.js';
|
||||
|
||||
@@ -23,7 +27,9 @@ describe('extractSessionCommand', () => {
|
||||
});
|
||||
|
||||
it('rejects regular messages', () => {
|
||||
expect(extractSessionCommand('please compact the conversation', trigger)).toBeNull();
|
||||
expect(
|
||||
extractSessionCommand('please compact the conversation', trigger),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('handles whitespace', () => {
|
||||
@@ -53,7 +59,10 @@ describe('isSessionCommandAllowed', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function makeMsg(content: string, overrides: Partial<NewMessage> = {}): NewMessage {
|
||||
function makeMsg(
|
||||
content: string,
|
||||
overrides: Partial<NewMessage> = {},
|
||||
): NewMessage {
|
||||
return {
|
||||
id: 'msg-1',
|
||||
chat_jid: 'group@test',
|
||||
@@ -65,7 +74,9 @@ function makeMsg(content: string, overrides: Partial<NewMessage> = {}): NewMessa
|
||||
};
|
||||
}
|
||||
|
||||
function makeDeps(overrides: Partial<SessionCommandDeps> = {}): SessionCommandDeps {
|
||||
function makeDeps(
|
||||
overrides: Partial<SessionCommandDeps> = {},
|
||||
): SessionCommandDeps {
|
||||
return {
|
||||
sendMessage: vi.fn().mockResolvedValue(undefined),
|
||||
setTyping: vi.fn().mockResolvedValue(undefined),
|
||||
@@ -105,7 +116,10 @@ describe('handleSessionCommand', () => {
|
||||
deps,
|
||||
});
|
||||
expect(result).toEqual({ handled: true, success: true });
|
||||
expect(deps.runAgent).toHaveBeenCalledWith('/compact', expect.any(Function));
|
||||
expect(deps.runAgent).toHaveBeenCalledWith(
|
||||
'/compact',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(deps.advanceCursor).toHaveBeenCalledWith('100');
|
||||
});
|
||||
|
||||
@@ -120,13 +134,17 @@ describe('handleSessionCommand', () => {
|
||||
deps,
|
||||
});
|
||||
expect(result).toEqual({ handled: true, success: true });
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith('Session commands require admin access.');
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith(
|
||||
'Session commands require admin access.',
|
||||
);
|
||||
expect(deps.runAgent).not.toHaveBeenCalled();
|
||||
expect(deps.advanceCursor).toHaveBeenCalledWith('100');
|
||||
});
|
||||
|
||||
it('silently consumes denied command when sender cannot interact', async () => {
|
||||
const deps = makeDeps({ canSenderInteract: vi.fn().mockReturnValue(false) });
|
||||
const deps = makeDeps({
|
||||
canSenderInteract: vi.fn().mockReturnValue(false),
|
||||
});
|
||||
const result = await handleSessionCommand({
|
||||
missedMessages: [makeMsg('/compact', { is_from_me: false })],
|
||||
isMainGroup: false,
|
||||
@@ -158,8 +176,14 @@ describe('handleSessionCommand', () => {
|
||||
expect(deps.formatMessages).toHaveBeenCalledWith([msgs[0]], 'UTC');
|
||||
// Two runAgent calls: pre-compact + /compact
|
||||
expect(deps.runAgent).toHaveBeenCalledTimes(2);
|
||||
expect(deps.runAgent).toHaveBeenCalledWith('<formatted>', expect.any(Function));
|
||||
expect(deps.runAgent).toHaveBeenCalledWith('/compact', expect.any(Function));
|
||||
expect(deps.runAgent).toHaveBeenCalledWith(
|
||||
'<formatted>',
|
||||
expect.any(Function),
|
||||
);
|
||||
expect(deps.runAgent).toHaveBeenCalledWith(
|
||||
'/compact',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('allows is_from_me sender in non-main group', async () => {
|
||||
@@ -173,15 +197,20 @@ describe('handleSessionCommand', () => {
|
||||
deps,
|
||||
});
|
||||
expect(result).toEqual({ handled: true, success: true });
|
||||
expect(deps.runAgent).toHaveBeenCalledWith('/compact', expect.any(Function));
|
||||
expect(deps.runAgent).toHaveBeenCalledWith(
|
||||
'/compact',
|
||||
expect.any(Function),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports failure when command-stage runAgent returns error without streamed status', async () => {
|
||||
// runAgent resolves 'error' but callback never gets status: 'error'
|
||||
const deps = makeDeps({ runAgent: vi.fn().mockImplementation(async (prompt, onOutput) => {
|
||||
await onOutput({ status: 'success', result: null });
|
||||
return 'error';
|
||||
})});
|
||||
const deps = makeDeps({
|
||||
runAgent: vi.fn().mockImplementation(async (prompt, onOutput) => {
|
||||
await onOutput({ status: 'success', result: null });
|
||||
return 'error';
|
||||
}),
|
||||
});
|
||||
const result = await handleSessionCommand({
|
||||
missedMessages: [makeMsg('/compact')],
|
||||
isMainGroup: true,
|
||||
@@ -191,7 +220,9 @@ describe('handleSessionCommand', () => {
|
||||
deps,
|
||||
});
|
||||
expect(result).toEqual({ handled: true, success: true });
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith(expect.stringContaining('failed'));
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith(
|
||||
expect.stringContaining('failed'),
|
||||
);
|
||||
});
|
||||
|
||||
it('returns success:false on pre-compact failure with no output', async () => {
|
||||
@@ -209,6 +240,8 @@ describe('handleSessionCommand', () => {
|
||||
deps,
|
||||
});
|
||||
expect(result).toEqual({ handled: true, success: false });
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith(expect.stringContaining('Failed to process'));
|
||||
expect(deps.sendMessage).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Failed to process'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user