v2 phase 5: scheduling fixes, media handling, command processing
- Host sweep: fix DELETE journal mode, busy_timeout, seq in recurrence INSERT - Outbound files: delivery reads from outbox dir, passes buffers to adapter, cleans up after delivery. Chat SDK bridge sends files via postMessage. - Inbound attachments: formatter includes attachment info in prompts - Commands: categorize /commands as admin, filtered, or passthrough. Admin commands check sender against NANOCLAW_ADMIN_USER_ID. Filtered commands silently dropped. Passthrough sent raw to agent. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,10 +34,17 @@ export interface InboundMessage {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/** A file attachment to deliver alongside a message. */
|
||||
export interface OutboundFile {
|
||||
filename: string;
|
||||
data: Buffer;
|
||||
}
|
||||
|
||||
/** Outbound message from host to adapter. */
|
||||
export interface OutboundMessage {
|
||||
kind: string;
|
||||
content: unknown; // parsed JSON from messages_out
|
||||
files?: OutboundFile[]; // file attachments from the session outbox
|
||||
}
|
||||
|
||||
/** Discovered conversation info (from syncConversations). */
|
||||
|
||||
@@ -104,31 +104,33 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
if (gatewayAbort?.signal.aborted) return;
|
||||
// Capture the long-running listener promise via waitUntil
|
||||
let listenerPromise: Promise<unknown> | undefined;
|
||||
adapter
|
||||
.startGatewayListener!(
|
||||
{ waitUntil: (p: Promise<unknown>) => { listenerPromise = p; } },
|
||||
24 * 60 * 60 * 1000,
|
||||
gatewayAbort!.signal,
|
||||
)
|
||||
.then(() => {
|
||||
// startGatewayListener resolves immediately with a Response;
|
||||
// the actual work is in the listenerPromise passed to waitUntil
|
||||
if (listenerPromise) {
|
||||
listenerPromise
|
||||
.then(() => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.info('Gateway listener expired, restarting', { adapter: adapter.name });
|
||||
startGateway();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.error('Gateway listener error, restarting in 5s', { adapter: adapter.name, err });
|
||||
setTimeout(startGateway, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
adapter.startGatewayListener!(
|
||||
{
|
||||
waitUntil: (p: Promise<unknown>) => {
|
||||
listenerPromise = p;
|
||||
},
|
||||
},
|
||||
24 * 60 * 60 * 1000,
|
||||
gatewayAbort!.signal,
|
||||
).then(() => {
|
||||
// startGatewayListener resolves immediately with a Response;
|
||||
// the actual work is in the listenerPromise passed to waitUntil
|
||||
if (listenerPromise) {
|
||||
listenerPromise
|
||||
.then(() => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.info('Gateway listener expired, restarting', { adapter: adapter.name });
|
||||
startGateway();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.error('Gateway listener error, restarting in 5s', { adapter: adapter.name, err });
|
||||
setTimeout(startGateway, 5000);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
startGateway();
|
||||
log.info('Gateway listener started', { adapter: adapter.name });
|
||||
@@ -156,7 +158,17 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
// Normal message
|
||||
const text = (content.markdown as string) || (content.text as string);
|
||||
if (text) {
|
||||
await adapter.postMessage(tid, { markdown: text });
|
||||
// Attach files if present (FileUpload format: { data, filename })
|
||||
const fileUploads = message.files?.map((f) => ({ data: f.data, filename: f.filename }));
|
||||
if (fileUploads && fileUploads.length > 0) {
|
||||
await adapter.postMessage(tid, { markdown: text, files: fileUploads });
|
||||
} else {
|
||||
await adapter.postMessage(tid, { markdown: text });
|
||||
}
|
||||
} else if (message.files && message.files.length > 0) {
|
||||
// Files only, no text
|
||||
const fileUploads = message.files.map((f) => ({ data: f.data, filename: f.filename }));
|
||||
await adapter.postMessage(tid, { markdown: '', files: fileUploads });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user