feat: per-group queue, SQLite state, graceful shutdown

Add per-group container locking with global concurrency limit to prevent
concurrent containers for the same group (#89) and cap total containers.
Fix message batching bug where lastAgentTimestamp advanced to trigger
message instead of latest in batch, causing redundant re-processing.
Move router state, sessions, and registered groups from JSON files to
SQLite with automatic one-time migration. Add SIGTERM/SIGINT handlers
with graceful shutdown (SIGTERM -> grace period -> SIGKILL). Add startup
recovery for messages missed during crash. Remove dead code: utils.ts,
Session type, isScheduledTask flag, ContainerConfig.env, getTaskRunLogs,
GroupQueue.isActive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-02-06 07:38:07 +02:00
parent db216a459e
commit eac9a6acfd
8 changed files with 591 additions and 115 deletions

View File

@@ -2,7 +2,7 @@
* Container Runner for NanoClaw
* Spawns agent execution in Apple Container and handles IPC
*/
import { exec, spawn } from 'child_process';
import { ChildProcess, exec, spawn } from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
@@ -38,7 +38,6 @@ export interface ContainerInput {
groupFolder: string;
chatJid: string;
isMain: boolean;
isScheduledTask?: boolean;
}
export interface ContainerOutput {
@@ -51,7 +50,7 @@ export interface ContainerOutput {
interface VolumeMount {
hostPath: string;
containerPath: string;
readonly?: boolean;
readonly: boolean;
}
function buildVolumeMounts(
@@ -185,6 +184,7 @@ function buildContainerArgs(mounts: VolumeMount[], containerName: string): strin
export async function runContainerAgent(
group: RegisteredGroup,
input: ContainerInput,
onProcess: (proc: ChildProcess) => void,
): Promise<ContainerOutput> {
const startTime = Date.now();
@@ -227,6 +227,8 @@ export async function runContainerAgent(
stdio: ['pipe', 'pipe', 'pipe'],
});
onProcess(container);
let stdout = '';
let stderr = '';
let stdoutTruncated = false;