mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-06-04 10:14:47 +08:00
style: apply prettier formatting to v2 source files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -263,7 +263,10 @@ async function buildContainerArgs(
|
||||
// Override entrypoint: compile agent-runner source, run v2 entry point (no stdin)
|
||||
args.push('--entrypoint', 'bash');
|
||||
args.push(CONTAINER_IMAGE);
|
||||
args.push('-c', 'cd /app && npx tsc --outDir /tmp/dist 2>&1 >&2 && ln -sf /app/node_modules /tmp/dist/node_modules && node /tmp/dist/index-v2.js');
|
||||
args.push(
|
||||
'-c',
|
||||
'cd /app && npx tsc --outDir /tmp/dist 2>&1 >&2 && ln -sf /app/node_modules /tmp/dist/node_modules && node /tmp/dist/index-v2.js',
|
||||
);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
+15
-2
@@ -15,7 +15,13 @@ const ACTIVE_POLL_MS = 1000;
|
||||
const SWEEP_POLL_MS = 60_000;
|
||||
|
||||
export interface ChannelDeliveryAdapter {
|
||||
deliver(channelType: string, platformId: string, threadId: string | null, kind: string, content: string): Promise<void>;
|
||||
deliver(
|
||||
channelType: string,
|
||||
platformId: string,
|
||||
threadId: string | null,
|
||||
kind: string,
|
||||
content: string,
|
||||
): Promise<void>;
|
||||
setTyping?(channelType: string, platformId: string, threadId: string | null): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -116,7 +122,14 @@ async function deliverSessionMessages(session: Session): Promise<void> {
|
||||
}
|
||||
|
||||
async function deliverMessage(
|
||||
msg: { id: string; kind: string; platform_id: string | null; channel_type: string | null; thread_id: string | null; content: string },
|
||||
msg: {
|
||||
id: string;
|
||||
kind: string;
|
||||
platform_id: string | null;
|
||||
channel_type: string | null;
|
||||
thread_id: string | null;
|
||||
content: string;
|
||||
},
|
||||
session: Session,
|
||||
): Promise<void> {
|
||||
if (!deliveryAdapter) {
|
||||
|
||||
+96
-12
@@ -8,8 +8,22 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
|
||||
import { initTestDb, closeDb, runMigrations, createAgentGroup, createMessagingGroup, createMessagingGroupAgent } from './db/index.js';
|
||||
import { resolveSession, writeSessionMessage, initSessionFolder, sessionDir, sessionDbPath, sessionsBaseDir } from './session-manager.js';
|
||||
import {
|
||||
initTestDb,
|
||||
closeDb,
|
||||
runMigrations,
|
||||
createAgentGroup,
|
||||
createMessagingGroup,
|
||||
createMessagingGroupAgent,
|
||||
} from './db/index.js';
|
||||
import {
|
||||
resolveSession,
|
||||
writeSessionMessage,
|
||||
initSessionFolder,
|
||||
sessionDir,
|
||||
sessionDbPath,
|
||||
sessionsBaseDir,
|
||||
} from './session-manager.js';
|
||||
import { getSession, findSession } from './db/sessions.js';
|
||||
import type { InboundEvent } from './router-v2.js';
|
||||
|
||||
@@ -50,8 +64,24 @@ afterEach(() => {
|
||||
|
||||
describe('session manager', () => {
|
||||
beforeEach(() => {
|
||||
createAgentGroup({ id: 'ag-1', name: 'Test Agent', folder: 'test-agent', is_admin: 0, agent_provider: null, container_config: null, created_at: now() });
|
||||
createMessagingGroup({ id: 'mg-1', channel_type: 'discord', platform_id: 'chan-123', name: 'General', is_group: 1, admin_user_id: null, created_at: now() });
|
||||
createAgentGroup({
|
||||
id: 'ag-1',
|
||||
name: 'Test Agent',
|
||||
folder: 'test-agent',
|
||||
is_admin: 0,
|
||||
agent_provider: null,
|
||||
container_config: null,
|
||||
created_at: now(),
|
||||
});
|
||||
createMessagingGroup({
|
||||
id: 'mg-1',
|
||||
channel_type: 'discord',
|
||||
platform_id: 'chan-123',
|
||||
name: 'General',
|
||||
is_group: 1,
|
||||
admin_user_id: null,
|
||||
created_at: now(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should create session folder and DB', () => {
|
||||
@@ -110,7 +140,12 @@ describe('session manager', () => {
|
||||
// Read from the session DB
|
||||
const dbPath = sessionDbPath('ag-1', session.id);
|
||||
const db = new Database(dbPath);
|
||||
const rows = db.prepare('SELECT * FROM messages_in').all() as Array<{ id: string; kind: string; status: string; content: string }>;
|
||||
const rows = db.prepare('SELECT * FROM messages_in').all() as Array<{
|
||||
id: string;
|
||||
kind: string;
|
||||
status: string;
|
||||
content: string;
|
||||
}>;
|
||||
db.close();
|
||||
|
||||
expect(rows).toHaveLength(1);
|
||||
@@ -136,9 +171,34 @@ describe('session manager', () => {
|
||||
|
||||
describe('router', () => {
|
||||
beforeEach(() => {
|
||||
createAgentGroup({ id: 'ag-1', name: 'Test Agent', folder: 'test-agent', is_admin: 0, agent_provider: null, container_config: null, created_at: now() });
|
||||
createMessagingGroup({ id: 'mg-1', channel_type: 'discord', platform_id: 'chan-123', name: 'General', is_group: 1, admin_user_id: null, created_at: now() });
|
||||
createMessagingGroupAgent({ id: 'mga-1', messaging_group_id: 'mg-1', agent_group_id: 'ag-1', trigger_rules: null, response_scope: 'all', session_mode: 'shared', priority: 0, created_at: now() });
|
||||
createAgentGroup({
|
||||
id: 'ag-1',
|
||||
name: 'Test Agent',
|
||||
folder: 'test-agent',
|
||||
is_admin: 0,
|
||||
agent_provider: null,
|
||||
container_config: null,
|
||||
created_at: now(),
|
||||
});
|
||||
createMessagingGroup({
|
||||
id: 'mg-1',
|
||||
channel_type: 'discord',
|
||||
platform_id: 'chan-123',
|
||||
name: 'General',
|
||||
is_group: 1,
|
||||
admin_user_id: null,
|
||||
created_at: now(),
|
||||
});
|
||||
createMessagingGroupAgent({
|
||||
id: 'mga-1',
|
||||
messaging_group_id: 'mg-1',
|
||||
agent_group_id: 'ag-1',
|
||||
trigger_rules: null,
|
||||
response_scope: 'all',
|
||||
session_mode: 'shared',
|
||||
priority: 0,
|
||||
created_at: now(),
|
||||
});
|
||||
});
|
||||
|
||||
it('should route a message end-to-end', async () => {
|
||||
@@ -215,7 +275,12 @@ describe('router', () => {
|
||||
channelType: 'discord',
|
||||
platformId: 'chan-123',
|
||||
threadId: null,
|
||||
message: { id: 'msg-b', kind: 'chat', content: JSON.stringify({ sender: 'B', text: 'Second' }), timestamp: now() },
|
||||
message: {
|
||||
id: 'msg-b',
|
||||
kind: 'chat',
|
||||
content: JSON.stringify({ sender: 'B', text: 'Second' }),
|
||||
timestamp: now(),
|
||||
},
|
||||
});
|
||||
|
||||
// Both should be in the same session
|
||||
@@ -231,8 +296,24 @@ describe('router', () => {
|
||||
|
||||
describe('delivery', () => {
|
||||
it('should detect undelivered messages in session DB', () => {
|
||||
createAgentGroup({ id: 'ag-1', name: 'Agent', folder: 'agent', is_admin: 0, agent_provider: null, container_config: null, created_at: now() });
|
||||
createMessagingGroup({ id: 'mg-test', channel_type: 'discord', platform_id: 'chan-test', name: 'Test', is_group: 0, admin_user_id: null, created_at: now() });
|
||||
createAgentGroup({
|
||||
id: 'ag-1',
|
||||
name: 'Agent',
|
||||
folder: 'agent',
|
||||
is_admin: 0,
|
||||
agent_provider: null,
|
||||
container_config: null,
|
||||
created_at: now(),
|
||||
});
|
||||
createMessagingGroup({
|
||||
id: 'mg-test',
|
||||
channel_type: 'discord',
|
||||
platform_id: 'chan-test',
|
||||
name: 'Test',
|
||||
is_group: 0,
|
||||
admin_user_id: null,
|
||||
created_at: now(),
|
||||
});
|
||||
|
||||
const { session } = resolveSession('ag-1', 'mg-test', null, 'shared');
|
||||
|
||||
@@ -245,7 +326,10 @@ describe('delivery', () => {
|
||||
VALUES ('out-1', datetime('now'), 0, 'chat', 'chan-123', 'discord', ?)`,
|
||||
).run(JSON.stringify({ text: 'Agent response' }));
|
||||
|
||||
const undelivered = db.prepare("SELECT * FROM messages_out WHERE delivered = 0").all() as Array<{ id: string; content: string }>;
|
||||
const undelivered = db.prepare('SELECT * FROM messages_out WHERE delivered = 0').all() as Array<{
|
||||
id: string;
|
||||
content: string;
|
||||
}>;
|
||||
db.close();
|
||||
|
||||
expect(undelivered).toHaveLength(1);
|
||||
|
||||
+17
-4
@@ -89,12 +89,16 @@ async function sweepSession(session: Session): Promise<void> {
|
||||
|
||||
for (const msg of staleMessages) {
|
||||
if (msg.tries >= MAX_TRIES) {
|
||||
db.prepare("UPDATE messages_in SET status = 'failed', status_changed = datetime('now') WHERE id = ?").run(msg.id);
|
||||
db.prepare("UPDATE messages_in SET status = 'failed', status_changed = datetime('now') WHERE id = ?").run(
|
||||
msg.id,
|
||||
);
|
||||
log.warn('Message marked as failed after max retries', { messageId: msg.id, sessionId: session.id });
|
||||
} else {
|
||||
const backoffMs = BACKOFF_BASE_MS * Math.pow(2, msg.tries);
|
||||
const backoffSec = Math.floor(backoffMs / 1000);
|
||||
db.prepare(`UPDATE messages_in SET status = 'pending', status_changed = datetime('now'), process_after = datetime('now', '+${backoffSec} seconds') WHERE id = ?`).run(msg.id);
|
||||
db.prepare(
|
||||
`UPDATE messages_in SET status = 'pending', status_changed = datetime('now'), process_after = datetime('now', '+${backoffSec} seconds') WHERE id = ?`,
|
||||
).run(msg.id);
|
||||
log.info('Reset stale message with backoff', { messageId: msg.id, tries: msg.tries, backoffMs });
|
||||
}
|
||||
}
|
||||
@@ -102,7 +106,16 @@ async function sweepSession(session: Session): Promise<void> {
|
||||
// 3. Handle recurrence for completed messages
|
||||
const completedRecurring = db
|
||||
.prepare("SELECT * FROM messages_in WHERE status = 'completed' AND recurrence IS NOT NULL")
|
||||
.all() as Array<{ id: string; kind: string; content: string; recurrence: string; process_after: string | null; platform_id: string | null; channel_type: string | null; thread_id: string | null }>;
|
||||
.all() as Array<{
|
||||
id: string;
|
||||
kind: string;
|
||||
content: string;
|
||||
recurrence: string;
|
||||
process_after: string | null;
|
||||
platform_id: string | null;
|
||||
channel_type: string | null;
|
||||
thread_id: string | null;
|
||||
}>;
|
||||
|
||||
for (const msg of completedRecurring) {
|
||||
try {
|
||||
@@ -118,7 +131,7 @@ async function sweepSession(session: Session): Promise<void> {
|
||||
).run(newId, msg.kind, nextRun, msg.recurrence, msg.platform_id, msg.channel_type, msg.thread_id, msg.content);
|
||||
|
||||
// Remove recurrence from the completed message so it doesn't spawn again
|
||||
db.prepare("UPDATE messages_in SET recurrence = NULL WHERE id = ?").run(msg.id);
|
||||
db.prepare('UPDATE messages_in SET recurrence = NULL WHERE id = ?').run(msg.id);
|
||||
|
||||
log.info('Inserted next recurrence', { originalId: msg.id, newId, nextRun });
|
||||
} catch (err) {
|
||||
|
||||
+15
-3
@@ -48,13 +48,20 @@ export async function routeInbound(event: InboundEvent): Promise<void> {
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
createMessagingGroup(mg);
|
||||
log.info('Auto-created messaging group', { id: mgId, channelType: event.channelType, platformId: event.platformId });
|
||||
log.info('Auto-created messaging group', {
|
||||
id: mgId,
|
||||
channelType: event.channelType,
|
||||
platformId: event.platformId,
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Resolve agent group via messaging_group_agents
|
||||
const agents = getMessagingGroupAgents(mg.id);
|
||||
if (agents.length === 0) {
|
||||
log.warn('No agent groups configured for messaging group', { messagingGroupId: mg.id, platformId: event.platformId });
|
||||
log.warn('No agent groups configured for messaging group', {
|
||||
messagingGroupId: mg.id,
|
||||
platformId: event.platformId,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +86,12 @@ export async function routeInbound(event: InboundEvent): Promise<void> {
|
||||
content: event.message.content,
|
||||
});
|
||||
|
||||
log.info('Message routed', { sessionId: session.id, agentGroup: match.agent_group_id, kind: event.message.kind, created });
|
||||
log.info('Message routed', {
|
||||
sessionId: session.id,
|
||||
agentGroup: match.agent_group_id,
|
||||
kind: event.message.kind,
|
||||
created,
|
||||
});
|
||||
|
||||
// 5. Wake container
|
||||
const freshSession = getSession(session.id);
|
||||
|
||||
+21
-12
@@ -35,7 +35,12 @@ function generateId(): string {
|
||||
* Find or create a session for a messaging group + thread.
|
||||
* Returns the session and whether it was newly created.
|
||||
*/
|
||||
export function resolveSession(agentGroupId: string, messagingGroupId: string, threadId: string | null, sessionMode: 'shared' | 'per-thread'): { session: Session; created: boolean } {
|
||||
export function resolveSession(
|
||||
agentGroupId: string,
|
||||
messagingGroupId: string,
|
||||
threadId: string | null,
|
||||
sessionMode: 'shared' | 'per-thread',
|
||||
): { session: Session; created: boolean } {
|
||||
// For shared mode, look for any active session with this messaging group (threadId ignored)
|
||||
// For per-thread mode, look for an active session with this specific thread
|
||||
const lookupThreadId = sessionMode === 'shared' ? null : threadId;
|
||||
@@ -83,17 +88,21 @@ export function initSessionFolder(agentGroupId: string, sessionId: string): void
|
||||
}
|
||||
|
||||
/** Write a message to a session's messages_in table. */
|
||||
export function writeSessionMessage(agentGroupId: string, sessionId: string, message: {
|
||||
id: string;
|
||||
kind: string;
|
||||
timestamp: string;
|
||||
platformId?: string | null;
|
||||
channelType?: string | null;
|
||||
threadId?: string | null;
|
||||
content: string;
|
||||
processAfter?: string | null;
|
||||
recurrence?: string | null;
|
||||
}): void {
|
||||
export function writeSessionMessage(
|
||||
agentGroupId: string,
|
||||
sessionId: string,
|
||||
message: {
|
||||
id: string;
|
||||
kind: string;
|
||||
timestamp: string;
|
||||
platformId?: string | null;
|
||||
channelType?: string | null;
|
||||
threadId?: string | null;
|
||||
content: string;
|
||||
processAfter?: string | null;
|
||||
recurrence?: string | null;
|
||||
},
|
||||
): void {
|
||||
const dbPath = sessionDbPath(agentGroupId, sessionId);
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
|
||||
Reference in New Issue
Block a user