mirror of
https://github.com/qwibitai/nanoclaw.git
synced 2026-07-06 18:52:03 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3f38dbed9 | |||
| d8a6b99f0e | |||
| 8ca7564fbc | |||
| b42329551d |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nanoclaw",
|
||||
"version": "2.1.32",
|
||||
"version": "2.1.33",
|
||||
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.33.0",
|
||||
|
||||
@@ -16,7 +16,17 @@ vi.mock('./config.js', async () => {
|
||||
return { ...actual, DATA_DIR: '/tmp/nanoclaw-test-write-outbound' };
|
||||
});
|
||||
|
||||
import { initSessionFolder, outboundDbPath, writeOutboundDirect } from './session-manager.js';
|
||||
import {
|
||||
initSessionFolder,
|
||||
inboundDbPath,
|
||||
outboundDbPath,
|
||||
sessionDir,
|
||||
writeOutboundDirect,
|
||||
writeSessionMessage,
|
||||
} from './session-manager.js';
|
||||
import { initTestDb, closeDb, runMigrations, createAgentGroup } from './db/index.js';
|
||||
import { createSession } from './db/sessions.js';
|
||||
import type { Session } from './types.js';
|
||||
|
||||
const TEST_DIR = '/tmp/nanoclaw-test-write-outbound';
|
||||
const AG = 'ag-test';
|
||||
@@ -98,3 +108,71 @@ describe('writeOutboundDirect', () => {
|
||||
expect(rows.map((r) => r.seq)).toEqual([2, 4]);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The `/debug` skill tells operators to `rm -rf` a session folder to reset a
|
||||
* stuck session. The sessions row survives, so the next message takes the
|
||||
* existing-session path and lands in `writeSessionMessage` with a missing
|
||||
* inbound.db. Without re-provisioning, better-sqlite3 throws on open and the
|
||||
* message is logged-and-dropped forever — the reset silently kills the chat.
|
||||
*/
|
||||
describe('writeSessionMessage re-provisions a deleted session folder', () => {
|
||||
beforeEach(() => {
|
||||
const db = initTestDb();
|
||||
runMigrations(db);
|
||||
createAgentGroup({
|
||||
id: AG,
|
||||
name: 'Reset',
|
||||
folder: 'reset',
|
||||
agent_provider: null,
|
||||
created_at: new Date().toISOString(),
|
||||
});
|
||||
const sess: Session = {
|
||||
id: SESS,
|
||||
agent_group_id: AG,
|
||||
messaging_group_id: null,
|
||||
thread_id: null,
|
||||
agent_provider: null,
|
||||
status: 'active',
|
||||
container_status: 'stopped',
|
||||
last_active: null,
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
createSession(sess);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
closeDb();
|
||||
});
|
||||
|
||||
it('re-creates the folder + inbound.db and does not throw when the row still exists', () => {
|
||||
// Operator resets a stuck session by deleting its folder; the row survives.
|
||||
fs.rmSync(sessionDir(AG, SESS), { recursive: true, force: true });
|
||||
expect(fs.existsSync(inboundDbPath(AG, SESS))).toBe(false);
|
||||
|
||||
expect(() =>
|
||||
writeSessionMessage(AG, SESS, {
|
||||
id: 'after-reset-1',
|
||||
kind: 'chat',
|
||||
timestamp: new Date().toISOString(),
|
||||
platformId: 'slack:C1',
|
||||
channelType: 'slack',
|
||||
threadId: null,
|
||||
content: JSON.stringify({ text: 'still here?' }),
|
||||
}),
|
||||
).not.toThrow();
|
||||
|
||||
// The folder + inbound.db are back and the message landed.
|
||||
expect(fs.existsSync(inboundDbPath(AG, SESS))).toBe(true);
|
||||
const db = new Database(inboundDbPath(AG, SESS), { readonly: true });
|
||||
try {
|
||||
const row = db.prepare('SELECT id, content FROM messages_in WHERE id = ?').get('after-reset-1') as
|
||||
| { id: string; content: string }
|
||||
| undefined;
|
||||
expect(row?.id).toBe('after-reset-1');
|
||||
expect(JSON.parse(row!.content).text).toBe('still here?');
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -219,6 +219,16 @@ export function writeSessionMessage(
|
||||
onWake?: 0 | 1;
|
||||
},
|
||||
): void {
|
||||
// Documented reset: operators `rm -rf` a session folder to clear a stuck
|
||||
// session. The sessions row survives, so the next message takes the
|
||||
// existing-session path and lands here with a missing inbound.db — the open
|
||||
// below would throw and the message would be logged-and-dropped forever.
|
||||
// Re-provision the folder + DBs (initSessionFolder is idempotent) so the
|
||||
// documented reset actually re-provisions instead of killing the chat.
|
||||
if (!fs.existsSync(inboundDbPath(agentGroupId, sessionId))) {
|
||||
initSessionFolder(agentGroupId, sessionId);
|
||||
}
|
||||
|
||||
// Extract base64 attachment data, save to inbox, replace with file paths
|
||||
const content = extractAttachmentFiles(agentGroupId, sessionId, message.id, message.content);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user