feat(platform): align conversation semantics across surfaces
This commit is contained in:
parent
b7b3d323b8
commit
f0f6055031
4 changed files with 50 additions and 23 deletions
|
|
@ -107,12 +107,15 @@ export function registerHubIpcHandlers(): void {
|
|||
ipcMain.handle('hub:init', async () => {
|
||||
await initializeHub()
|
||||
const h = getHub()
|
||||
const defaultConversationId = defaultAgentId
|
||||
? (h.getAgentMainConversationId(defaultAgentId) ?? defaultAgentId)
|
||||
: null
|
||||
return {
|
||||
hubId: h.hubId,
|
||||
url: h.url,
|
||||
connectionState: h.connectionState,
|
||||
defaultAgentId,
|
||||
defaultConversationId: defaultAgentId,
|
||||
defaultConversationId,
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -144,7 +147,7 @@ export function registerHubIpcHandlers(): void {
|
|||
gatewayUrl: h.url,
|
||||
defaultAgent: agent
|
||||
? {
|
||||
agentId: agent.sessionId,
|
||||
agentId: defaultAgentId ?? agent.sessionId,
|
||||
status: agent.closed ? 'closed' : 'idle',
|
||||
}
|
||||
: null,
|
||||
|
|
@ -160,7 +163,7 @@ export function registerHubIpcHandlers(): void {
|
|||
return null
|
||||
}
|
||||
return {
|
||||
agentId: agent.sessionId,
|
||||
agentId: defaultAgentId ?? agent.sessionId,
|
||||
status: agent.closed ? 'closed' : 'idle',
|
||||
}
|
||||
})
|
||||
|
|
@ -301,16 +304,18 @@ export function registerHubIpcHandlers(): void {
|
|||
*/
|
||||
ipcMain.handle('localChat:subscribe', async (_event, agentId: string) => {
|
||||
const h = getHub()
|
||||
const agent = h.getAgent(agentId)
|
||||
if (!agent) {
|
||||
return { error: `Agent not found: ${agentId}` }
|
||||
const conversationId = agentId
|
||||
const conversation = h.getConversation(conversationId)
|
||||
if (!conversation) {
|
||||
return { error: `Agent not found: ${conversationId}` }
|
||||
}
|
||||
if (agent.closed) {
|
||||
return { error: `Agent is closed: ${agentId}` }
|
||||
if (conversation.closed) {
|
||||
return { error: `Agent is closed: ${conversationId}` }
|
||||
}
|
||||
const logicalAgentId = h.getConversationAgentId(conversationId) ?? conversationId
|
||||
|
||||
// Already subscribed?
|
||||
if (ipcAgentSubscriptions.has(agentId)) {
|
||||
if (ipcAgentSubscriptions.has(conversationId)) {
|
||||
return { ok: true, alreadySubscribed: true }
|
||||
}
|
||||
|
||||
|
|
@ -318,7 +323,7 @@ export function registerHubIpcHandlers(): void {
|
|||
let currentStreamId: string | null = null
|
||||
|
||||
// Subscribe to agent events using the multi-subscriber mechanism
|
||||
const unsubscribe = agent.subscribe((event) => {
|
||||
const unsubscribe = conversation.subscribe((event) => {
|
||||
if (!mainWindowRef || mainWindowRef.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
|
|
@ -329,8 +334,8 @@ export function registerHubIpcHandlers(): void {
|
|||
if (isPassthroughEvent) {
|
||||
safeLog(`[IPC] Sending ${event.type} event to renderer`)
|
||||
mainWindowRef.webContents.send('localChat:event', {
|
||||
agentId,
|
||||
conversationId: agentId,
|
||||
agentId: logicalAgentId,
|
||||
conversationId,
|
||||
streamId: null,
|
||||
event,
|
||||
})
|
||||
|
|
@ -358,8 +363,8 @@ export function registerHubIpcHandlers(): void {
|
|||
|
||||
safeLog(`[IPC] Sending event to renderer: ${event.type}, streamId: ${currentStreamId}`)
|
||||
mainWindowRef.webContents.send('localChat:event', {
|
||||
agentId,
|
||||
conversationId: agentId,
|
||||
agentId: logicalAgentId,
|
||||
conversationId,
|
||||
streamId: currentStreamId,
|
||||
event,
|
||||
})
|
||||
|
|
@ -370,16 +375,16 @@ export function registerHubIpcHandlers(): void {
|
|||
}
|
||||
})
|
||||
|
||||
ipcAgentSubscriptions.set(agentId, unsubscribe)
|
||||
ipcAgentSubscriptions.set(conversationId, unsubscribe)
|
||||
|
||||
// Register local approval handler so exec approval requests route via IPC
|
||||
h.setLocalApprovalHandler(agentId, (payload) => {
|
||||
h.setLocalApprovalHandler(conversationId, (payload) => {
|
||||
if (!mainWindowRef || mainWindowRef.isDestroyed()) return
|
||||
safeLog(`[IPC] Sending approval request to renderer: ${payload.approvalId}`)
|
||||
mainWindowRef.webContents.send('localChat:approval', payload)
|
||||
})
|
||||
|
||||
safeLog(`[IPC] Local chat subscribed to agent: ${agentId}`)
|
||||
safeLog(`[IPC] Local chat subscribed to conversation: ${conversationId}`)
|
||||
|
||||
return { ok: true }
|
||||
})
|
||||
|
|
@ -457,7 +462,7 @@ export function registerHubIpcHandlers(): void {
|
|||
const source = { type: 'local' as const }
|
||||
// Broadcast as local source (for consistency, though UI already knows)
|
||||
h.broadcastInbound({
|
||||
agentId,
|
||||
agentId: h.getConversationAgentId(resolvedConversationId) ?? agentId,
|
||||
conversationId: resolvedConversationId,
|
||||
content,
|
||||
source,
|
||||
|
|
|
|||
|
|
@ -721,13 +721,13 @@ export class TelegramService implements OnModuleInit, OnModuleDestroy {
|
|||
return message.includes("METHOD_NOT_FOUND") || message.includes("Unknown RPC method");
|
||||
}
|
||||
|
||||
private async createConversationViaRpc(deviceId: string, hubId: string): Promise<{ id: string }> {
|
||||
private async createConversationViaRpc(deviceId: string, hubId: string, agentId?: string): Promise<{ id: string }> {
|
||||
try {
|
||||
const created = await this.sendRpc<Record<string, never>, CreateConversationResult>(
|
||||
const created = await this.sendRpc<{ agentId?: string }, CreateConversationResult>(
|
||||
deviceId,
|
||||
hubId,
|
||||
"createConversation",
|
||||
{},
|
||||
agentId ? { agentId } : {},
|
||||
VERIFY_TIMEOUT_MS,
|
||||
"Create session request timed out",
|
||||
);
|
||||
|
|
@ -792,7 +792,7 @@ export class TelegramService implements OnModuleInit, OnModuleDestroy {
|
|||
}
|
||||
|
||||
try {
|
||||
const created = await this.createConversationViaRpc(user.deviceId, user.hubId);
|
||||
const created = await this.createConversationViaRpc(user.deviceId, user.hubId, user.agentId);
|
||||
|
||||
await this.userStore.upsert({
|
||||
telegramUserId: user.telegramUserId,
|
||||
|
|
|
|||
|
|
@ -52,4 +52,24 @@ export class AppController {
|
|||
const ok = this.hub.closeAgent(id);
|
||||
return { ok };
|
||||
}
|
||||
|
||||
@Get("conversations")
|
||||
listConversations() {
|
||||
return this.hub.listConversations().map((id) => {
|
||||
const conversation = this.hub.getConversation(id);
|
||||
return { id, closed: conversation?.closed ?? true };
|
||||
});
|
||||
}
|
||||
|
||||
@Post("conversations")
|
||||
createConversation(@Body() body?: { id?: string; agentId?: string }) {
|
||||
const conversation = this.hub.createConversation(body?.id, { agentId: body?.agentId });
|
||||
return { id: conversation.sessionId };
|
||||
}
|
||||
|
||||
@Delete("conversations/:id")
|
||||
deleteConversation(@Param("id") id: string) {
|
||||
const ok = this.hub.closeConversation(id);
|
||||
return { ok };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,11 +60,13 @@ pnpm dev:local:archive
|
|||
|
||||
Compatibility behavior:
|
||||
|
||||
- If only `agentId` is provided, the runtime resolves `conversationId = agentId`.
|
||||
- If only `agentId` is provided, runtime resolves to that agent's `mainConversationId`.
|
||||
- Legacy fallback is still supported: when no mapping exists, `conversationId = agentId`.
|
||||
- New integrations should pass `conversationId` explicitly.
|
||||
- Hub RPC supports both naming sets:
|
||||
- Legacy: `createAgent/listAgents/deleteAgent`
|
||||
- Conversation-first aliases: `createConversation/listConversations/deleteConversation`
|
||||
- `createConversation` supports optional `agentId` to create a new thread under a specific agent.
|
||||
|
||||
Telegram behavior:
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue