sidecar self-registration: sidecars connect to API server instead of vice versa
Flips the connection model so sidecars register themselves with the API server via WebSocket at /api/sidecar/register, enabling dynamic discovery, location independence, and automatic reconnection from either side. - Add registration protocol types and PTY command/event types - Create sidecar-registry.ts (replaces sidecar-client.ts) as passive registry - Create sidecar connector (connect.ts) with exponential backoff reconnect - Convert process sidecar from WS server to WS client - Convert PTY sidecar from WS server to multiplexed WS client - Simplify terminal bridge to thin adapter using registry - Add PTY sidecar as PM2-managed process - Update all consumer imports Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-awai
|
||||
import { consumePairingCode } from '../pairing';
|
||||
import { chunkMessage } from './chunker';
|
||||
import { listPiModels } from '@@/api/pi/list-models';
|
||||
import { enqueueJob } from '../../sidecar-client';
|
||||
import { enqueueJob } from '../../sidecar-registry';
|
||||
import { readJob } from '@@/queue/storage';
|
||||
import { openEmailDb } from '@@/api/email/email-db';
|
||||
import type { ModelInfo } from '@@/api/pi/types';
|
||||
@@ -95,9 +95,9 @@ async function handleEmailSync(ctx: CommandContext): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
const newest = db.query(
|
||||
'SELECT from_name, from_address, subject FROM emails WHERE deleted = 0 ORDER BY date DESC LIMIT ?',
|
||||
).all(Math.min(newCount, 20)) as Array<{ from_name: string | null; from_address: string; subject: string }>;
|
||||
const newest = db
|
||||
.query('SELECT from_name, from_address, subject FROM emails WHERE deleted = 0 ORDER BY date DESC LIMIT ?')
|
||||
.all(Math.min(newCount, 20)) as Array<{ from_name: string | null; from_address: string; subject: string }>;
|
||||
db.close();
|
||||
|
||||
const lines = newest.map((e) => {
|
||||
@@ -128,9 +128,7 @@ async function handleCommand(ctx: CommandContext): Promise<boolean> {
|
||||
'`!model` — show current model\n' +
|
||||
'`!model <id>` — switch model\n' +
|
||||
'`!models` — list available models',
|
||||
email:
|
||||
'**Email:**\n' +
|
||||
'`!email sync` — sync Gmail and show new emails',
|
||||
email: '**Email:**\n' + '`!email sync` — sync Gmail and show new emails',
|
||||
};
|
||||
|
||||
if (lower === '!help' || lower.startsWith('!help ')) {
|
||||
@@ -140,7 +138,11 @@ async function handleCommand(ctx: CommandContext): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
if (topic) {
|
||||
await channel.send(`Unknown topic: \`${topic}\`\nAvailable: ${Object.keys(helpSections).map((k) => `\`${k}\``).join(', ')}`);
|
||||
await channel.send(
|
||||
`Unknown topic: \`${topic}\`\nAvailable: ${Object.keys(helpSections)
|
||||
.map((k) => `\`${k}\``)
|
||||
.join(', ')}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
const full = Object.values(helpSections).join('\n\n');
|
||||
@@ -172,7 +174,11 @@ async function handleCommand(ctx: CommandContext): Promise<boolean> {
|
||||
|
||||
if (lower === '!model') {
|
||||
const current = getSessionModel('discord', ctx.userId, discordId);
|
||||
await channel.send(current ? `Current model: **${current}**` : 'No active session yet — the default model will be used on your next message.');
|
||||
await channel.send(
|
||||
current
|
||||
? `Current model: **${current}**`
|
||||
: 'No active session yet — the default model will be used on your next message.',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -229,17 +235,23 @@ export async function handleDiscordMessage(message: DiscordMessage): Promise<voi
|
||||
}
|
||||
|
||||
await channel.send(
|
||||
'I don\'t recognize your Discord account. To link it:\n' +
|
||||
'1. Go to Officer Settings → Integrations → Discord\n' +
|
||||
'2. Click "Link Discord" to get a pairing code\n' +
|
||||
'3. Send the 6-character code to me here',
|
||||
"I don't recognize your Discord account. To link it:\n" +
|
||||
'1. Go to Officer Settings → Integrations → Discord\n' +
|
||||
'2. Click "Link Discord" to get a pairing code\n' +
|
||||
'3. Send the 6-character code to me here',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle commands
|
||||
if (content.startsWith('!')) {
|
||||
const handled = await handleCommand({ content, channel, userId: linked.user.id, email: linked.user.email, discordId });
|
||||
const handled = await handleCommand({
|
||||
content,
|
||||
channel,
|
||||
userId: linked.user.id,
|
||||
email: linked.user.email,
|
||||
discordId,
|
||||
});
|
||||
if (handled) return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { logger } from '@@/api/pi/logger';
|
||||
import type { MessageCost, PiEvent } from '@@/api/pi/types';
|
||||
import * as sidecar from '@@/sidecar-client';
|
||||
import * as sidecar from '@@/sidecar-registry';
|
||||
|
||||
type ClaudeCodeParams = {
|
||||
userId: number;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { consumePairingCode } from '../pairing';
|
||||
import { chunkMessage } from './chunker';
|
||||
import { getTelegramBot } from './bot';
|
||||
import { listPiModels } from '@@/api/pi/list-models';
|
||||
import { enqueueJob } from '../../sidecar-client';
|
||||
import { enqueueJob } from '../../sidecar-registry';
|
||||
import { readJob } from '@@/queue/storage';
|
||||
import { openEmailDb } from '@@/api/email/email-db';
|
||||
import type { ModelInfo } from '@@/api/pi/types';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sendAndAwait, getSessionModel, setSessionModel } from '../send-and-awai
|
||||
import { consumePairingCode } from '../pairing';
|
||||
import { getWhatsAppClient } from './bot';
|
||||
import { listPiModels } from '@@/api/pi/list-models';
|
||||
import { enqueueJob } from '../../sidecar-client';
|
||||
import { enqueueJob } from '../../sidecar-registry';
|
||||
import { readJob } from '@@/queue/storage';
|
||||
import { openEmailDb } from '@@/api/email/email-db';
|
||||
import type { ModelInfo } from '@@/api/pi/types';
|
||||
|
||||
Reference in New Issue
Block a user