style: prettier formatting fixes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,7 @@ import { getAgentGroup } from './db/agent-groups.js';
|
|||||||
import { getMessagingGroup } from './db/messaging-groups.js';
|
import { getMessagingGroup } from './db/messaging-groups.js';
|
||||||
import { log } from './log.js';
|
import { log } from './log.js';
|
||||||
import { validateAdditionalMounts } from './mount-security.js';
|
import { validateAdditionalMounts } from './mount-security.js';
|
||||||
import {
|
import { markContainerIdle, markContainerRunning, markContainerStopped, sessionDir } from './session-manager.js';
|
||||||
markContainerIdle,
|
|
||||||
markContainerRunning,
|
|
||||||
markContainerStopped,
|
|
||||||
sessionDir,
|
|
||||||
} from './session-manager.js';
|
|
||||||
import type { AgentGroup, Session } from './types.js';
|
import type { AgentGroup, Session } from './types.js';
|
||||||
|
|
||||||
const onecli = new OneCLI({ url: ONECLI_URL });
|
const onecli = new OneCLI({ url: ONECLI_URL });
|
||||||
|
|||||||
@@ -100,10 +100,10 @@ describe('cleanupOrphans', () => {
|
|||||||
expect(mockExecSync).toHaveBeenNthCalledWith(3, `${CONTAINER_RUNTIME_BIN} stop -t 1 nanoclaw-group2-222`, {
|
expect(mockExecSync).toHaveBeenNthCalledWith(3, `${CONTAINER_RUNTIME_BIN} stop -t 1 nanoclaw-group2-222`, {
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
});
|
});
|
||||||
expect(log.info).toHaveBeenCalledWith(
|
expect(log.info).toHaveBeenCalledWith('Stopped orphaned containers', {
|
||||||
'Stopped orphaned containers',
|
count: 2,
|
||||||
{ count: 2, names: ['nanoclaw-group1-111', 'nanoclaw-group2-222'] },
|
names: ['nanoclaw-group1-111', 'nanoclaw-group2-222'],
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does nothing when no orphans exist', () => {
|
it('does nothing when no orphans exist', () => {
|
||||||
@@ -140,9 +140,9 @@ describe('cleanupOrphans', () => {
|
|||||||
cleanupOrphans(); // should not throw
|
cleanupOrphans(); // should not throw
|
||||||
|
|
||||||
expect(mockExecSync).toHaveBeenCalledTimes(3);
|
expect(mockExecSync).toHaveBeenCalledTimes(3);
|
||||||
expect(log.info).toHaveBeenCalledWith(
|
expect(log.info).toHaveBeenCalledWith('Stopped orphaned containers', {
|
||||||
'Stopped orphaned containers',
|
count: 2,
|
||||||
{ count: 2, names: ['nanoclaw-a-1', 'nanoclaw-b-2'] },
|
names: ['nanoclaw-a-1', 'nanoclaw-b-2'],
|
||||||
);
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -131,9 +131,9 @@ async function deliverSessionMessages(session: Session): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await deliverMessage(msg, session, inDb);
|
await deliverMessage(msg, session, inDb);
|
||||||
// Track delivery in inbound.db (host-owned) — not outbound.db
|
// Track delivery in inbound.db (host-owned) — not outbound.db
|
||||||
inDb.prepare("INSERT OR IGNORE INTO delivered (message_out_id, delivered_at) VALUES (?, datetime('now'))").run(
|
inDb
|
||||||
msg.id,
|
.prepare("INSERT OR IGNORE INTO delivered (message_out_id, delivered_at) VALUES (?, datetime('now'))")
|
||||||
);
|
.run(msg.id);
|
||||||
resetContainerIdleTimer(session.id);
|
resetContainerIdleTimer(session.id);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Failed to deliver message', { messageId: msg.id, sessionId: session.id, err });
|
log.error('Failed to deliver message', { messageId: msg.id, sessionId: session.id, err });
|
||||||
@@ -249,9 +249,7 @@ async function handleSystemAction(
|
|||||||
const recurrence = (content.recurrence as string) || null;
|
const recurrence = (content.recurrence as string) || null;
|
||||||
|
|
||||||
// Compute next even seq for host-owned inbound.db
|
// Compute next even seq for host-owned inbound.db
|
||||||
const maxSeq = (
|
const maxSeq = (inDb.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }).m;
|
||||||
inDb.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }
|
|
||||||
).m;
|
|
||||||
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2);
|
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2);
|
||||||
|
|
||||||
inDb
|
inDb
|
||||||
@@ -276,7 +274,9 @@ async function handleSystemAction(
|
|||||||
case 'cancel_task': {
|
case 'cancel_task': {
|
||||||
const taskId = content.taskId as string;
|
const taskId = content.taskId as string;
|
||||||
inDb
|
inDb
|
||||||
.prepare("UPDATE messages_in SET status = 'completed' WHERE id = ? AND kind = 'task' AND status IN ('pending', 'paused')")
|
.prepare(
|
||||||
|
"UPDATE messages_in SET status = 'completed' WHERE id = ? AND kind = 'task' AND status IN ('pending', 'paused')",
|
||||||
|
)
|
||||||
.run(taskId);
|
.run(taskId);
|
||||||
log.info('Task cancelled', { taskId });
|
log.info('Task cancelled', { taskId });
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -104,7 +104,9 @@ describe('session manager', () => {
|
|||||||
const outPath = outboundDbPath('ag-1', 'sess-test');
|
const outPath = outboundDbPath('ag-1', 'sess-test');
|
||||||
expect(fs.existsSync(outPath)).toBe(true);
|
expect(fs.existsSync(outPath)).toBe(true);
|
||||||
const outDb = new Database(outPath);
|
const outDb = new Database(outPath);
|
||||||
const outTables = outDb.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{ name: string }>;
|
const outTables = outDb.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{
|
||||||
|
name: string;
|
||||||
|
}>;
|
||||||
expect(outTables.map((t) => t.name)).toContain('messages_out');
|
expect(outTables.map((t) => t.name)).toContain('messages_out');
|
||||||
expect(outTables.map((t) => t.name)).toContain('processing_ack');
|
expect(outTables.map((t) => t.name)).toContain('processing_ack');
|
||||||
outDb.close();
|
outDb.close();
|
||||||
|
|||||||
@@ -115,9 +115,7 @@ function syncProcessingAcks(inDb: Database.Database, outDb: Database.Database):
|
|||||||
if (completed.length === 0) return;
|
if (completed.length === 0) return;
|
||||||
|
|
||||||
// Batch-update messages_in status for completed/failed messages
|
// Batch-update messages_in status for completed/failed messages
|
||||||
const updateStmt = inDb.prepare(
|
const updateStmt = inDb.prepare("UPDATE messages_in SET status = 'completed' WHERE id = ? AND status != 'completed'");
|
||||||
"UPDATE messages_in SET status = 'completed' WHERE id = ? AND status != 'completed'",
|
|
||||||
);
|
|
||||||
inDb.transaction(() => {
|
inDb.transaction(() => {
|
||||||
for (const { message_id } of completed) {
|
for (const { message_id } of completed) {
|
||||||
updateStmt.run(message_id);
|
updateStmt.run(message_id);
|
||||||
@@ -148,9 +146,9 @@ function detectStaleContainers(
|
|||||||
if (heartbeatAge < STALE_THRESHOLD_MS) return; // Container is alive
|
if (heartbeatAge < STALE_THRESHOLD_MS) return; // Container is alive
|
||||||
|
|
||||||
// Heartbeat is stale — check for stuck processing entries
|
// Heartbeat is stale — check for stuck processing entries
|
||||||
const processing = outDb
|
const processing = outDb.prepare("SELECT message_id FROM processing_ack WHERE status = 'processing'").all() as Array<{
|
||||||
.prepare("SELECT message_id FROM processing_ack WHERE status = 'processing'")
|
message_id: string;
|
||||||
.all() as Array<{ message_id: string }>;
|
}>;
|
||||||
|
|
||||||
if (processing.length === 0) return;
|
if (processing.length === 0) return;
|
||||||
|
|
||||||
@@ -200,9 +198,7 @@ async function handleRecurrence(inDb: Database.Database, session: Session): Prom
|
|||||||
const newId = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
const newId = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||||
|
|
||||||
// Host uses even seq numbers
|
// Host uses even seq numbers
|
||||||
const maxSeq = (
|
const maxSeq = (inDb.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }).m;
|
||||||
inDb.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }
|
|
||||||
).m;
|
|
||||||
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2);
|
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2);
|
||||||
|
|
||||||
inDb
|
inDb
|
||||||
@@ -210,7 +206,17 @@ async function handleRecurrence(inDb: Database.Database, session: Session): Prom
|
|||||||
`INSERT INTO messages_in (id, seq, kind, timestamp, status, process_after, recurrence, platform_id, channel_type, thread_id, content)
|
`INSERT INTO messages_in (id, seq, kind, timestamp, status, process_after, recurrence, platform_id, channel_type, thread_id, content)
|
||||||
VALUES (?, ?, ?, datetime('now'), 'pending', ?, ?, ?, ?, ?, ?)`,
|
VALUES (?, ?, ?, datetime('now'), 'pending', ?, ?, ?, ?, ?, ?)`,
|
||||||
)
|
)
|
||||||
.run(newId, nextSeq, msg.kind, nextRun, msg.recurrence, msg.platform_id, msg.channel_type, msg.thread_id, msg.content);
|
.run(
|
||||||
|
newId,
|
||||||
|
nextSeq,
|
||||||
|
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
|
// Remove recurrence from the completed message so it doesn't spawn again
|
||||||
inDb.prepare('UPDATE messages_in SET recurrence = NULL WHERE id = ?').run(msg.id);
|
inDb.prepare('UPDATE messages_in SET recurrence = NULL WHERE id = ?').run(msg.id);
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ export function loadMountAllowlist(): MountAllowlist | null {
|
|||||||
if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
|
if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
|
||||||
// Do NOT cache this as an error — file may be created later without restart.
|
// Do NOT cache this as an error — file may be created later without restart.
|
||||||
// Only parse/structural errors are permanently cached.
|
// Only parse/structural errors are permanently cached.
|
||||||
log.warn('Mount allowlist not found - additional mounts will be BLOCKED. Create the file to enable additional mounts.', { path: MOUNT_ALLOWLIST_PATH });
|
log.warn(
|
||||||
|
'Mount allowlist not found - additional mounts will be BLOCKED. Create the file to enable additional mounts.',
|
||||||
|
{ path: MOUNT_ALLOWLIST_PATH },
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,12 +104,19 @@ export function loadMountAllowlist(): MountAllowlist | null {
|
|||||||
allowlist.blockedPatterns = mergedBlockedPatterns;
|
allowlist.blockedPatterns = mergedBlockedPatterns;
|
||||||
|
|
||||||
cachedAllowlist = allowlist;
|
cachedAllowlist = allowlist;
|
||||||
log.info('Mount allowlist loaded successfully', { path: MOUNT_ALLOWLIST_PATH, allowedRoots: allowlist.allowedRoots.length, blockedPatterns: allowlist.blockedPatterns.length });
|
log.info('Mount allowlist loaded successfully', {
|
||||||
|
path: MOUNT_ALLOWLIST_PATH,
|
||||||
|
allowedRoots: allowlist.allowedRoots.length,
|
||||||
|
blockedPatterns: allowlist.blockedPatterns.length,
|
||||||
|
});
|
||||||
|
|
||||||
return cachedAllowlist;
|
return cachedAllowlist;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
allowlistLoadError = err instanceof Error ? err.message : String(err);
|
allowlistLoadError = err instanceof Error ? err.message : String(err);
|
||||||
log.error('Failed to load mount allowlist - additional mounts will be BLOCKED', { path: MOUNT_ALLOWLIST_PATH, error: allowlistLoadError });
|
log.error('Failed to load mount allowlist - additional mounts will be BLOCKED', {
|
||||||
|
path: MOUNT_ALLOWLIST_PATH,
|
||||||
|
error: allowlistLoadError,
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +297,10 @@ export function validateMount(mount: AdditionalMount, isMain: boolean): MountVal
|
|||||||
} else if (!allowedRoot.allowReadWrite) {
|
} else if (!allowedRoot.allowReadWrite) {
|
||||||
// Root doesn't allow read-write
|
// Root doesn't allow read-write
|
||||||
effectiveReadonly = true;
|
effectiveReadonly = true;
|
||||||
log.info('Mount forced to read-only - root does not allow read-write', { mount: mount.hostPath, root: allowedRoot.path });
|
log.info('Mount forced to read-only - root does not allow read-write', {
|
||||||
|
mount: mount.hostPath,
|
||||||
|
root: allowedRoot.path,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Read-write allowed
|
// Read-write allowed
|
||||||
effectiveReadonly = false;
|
effectiveReadonly = false;
|
||||||
@@ -333,9 +346,20 @@ export function validateAdditionalMounts(
|
|||||||
readonly: result.effectiveReadonly!,
|
readonly: result.effectiveReadonly!,
|
||||||
});
|
});
|
||||||
|
|
||||||
log.debug('Mount validated successfully', { group: groupName, hostPath: result.realHostPath, containerPath: result.resolvedContainerPath, readonly: result.effectiveReadonly, reason: result.reason });
|
log.debug('Mount validated successfully', {
|
||||||
|
group: groupName,
|
||||||
|
hostPath: result.realHostPath,
|
||||||
|
containerPath: result.resolvedContainerPath,
|
||||||
|
readonly: result.effectiveReadonly,
|
||||||
|
reason: result.reason,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
log.warn('Additional mount REJECTED', { group: groupName, requestedPath: mount.hostPath, containerPath: mount.containerPath, reason: result.reason });
|
log.warn('Additional mount REJECTED', {
|
||||||
|
group: groupName,
|
||||||
|
requestedPath: mount.hostPath,
|
||||||
|
containerPath: mount.containerPath,
|
||||||
|
reason: result.reason,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,9 +139,7 @@ export function writeSessionMessage(
|
|||||||
try {
|
try {
|
||||||
// Host uses even seq numbers, container uses odd — prevents collisions
|
// Host uses even seq numbers, container uses odd — prevents collisions
|
||||||
// across the two-DB boundary without cross-DB coordination.
|
// across the two-DB boundary without cross-DB coordination.
|
||||||
const maxSeq = (
|
const maxSeq = (db.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }).m;
|
||||||
db.prepare('SELECT COALESCE(MAX(seq), 0) AS m FROM messages_in').get() as { m: number }
|
|
||||||
).m;
|
|
||||||
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2); // next even
|
const nextSeq = maxSeq < 2 ? 2 : maxSeq + 2 - (maxSeq % 2); // next even
|
||||||
|
|
||||||
db.prepare(
|
db.prepare(
|
||||||
|
|||||||
Reference in New Issue
Block a user