This commit is contained in:
@@ -192,6 +192,16 @@ export interface InstanceData {
|
||||
agentModelSelections: AgentModelSelection
|
||||
sessionTasks?: SessionTasks // Multi-task chat support: tasks per session
|
||||
sessionSkills?: Record<string, SkillSelection[]> // Selected skills per session
|
||||
sessionMessages?: Record<
|
||||
string,
|
||||
Array<{
|
||||
id: string
|
||||
role: "user" | "assistant" | "system" | "tool"
|
||||
content?: string
|
||||
createdAt?: number
|
||||
updatedAt?: number
|
||||
}>
|
||||
>
|
||||
customAgents?: Array<{
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
@@ -26,6 +26,20 @@ const InstanceDataSchema = z.object({
|
||||
messageHistory: z.array(z.string()).default([]),
|
||||
agentModelSelections: z.record(z.string(), ModelPreferenceSchema).default({}),
|
||||
sessionTasks: z.record(z.string(), z.array(TaskSchema)).optional(),
|
||||
sessionMessages: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
role: z.enum(["user", "assistant", "system", "tool"]),
|
||||
content: z.string().optional(),
|
||||
createdAt: z.number().optional(),
|
||||
updatedAt: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.optional(),
|
||||
sessionSkills: z
|
||||
.record(
|
||||
z.string(),
|
||||
@@ -47,6 +61,7 @@ const EMPTY_INSTANCE_DATA: InstanceData = {
|
||||
messageHistory: [],
|
||||
agentModelSelections: {},
|
||||
sessionTasks: {},
|
||||
sessionMessages: {},
|
||||
sessionSkills: {},
|
||||
customAgents: [],
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
||||
messageHistory: [],
|
||||
agentModelSelections: {},
|
||||
sessionTasks: {},
|
||||
sessionMessages: {},
|
||||
}
|
||||
|
||||
export class InstanceStore {
|
||||
|
||||
@@ -11,6 +11,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
||||
messageHistory: [],
|
||||
agentModelSelections: {},
|
||||
sessionTasks: {},
|
||||
sessionMessages: {},
|
||||
}
|
||||
|
||||
function isDeepEqual(a: unknown, b: unknown): boolean {
|
||||
@@ -157,11 +158,13 @@ export class ServerStorage {
|
||||
const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : []
|
||||
const agentModelSelections = { ...(source.agentModelSelections ?? {}) }
|
||||
const sessionTasks = { ...(source.sessionTasks ?? {}) }
|
||||
const sessionMessages = { ...(source.sessionMessages ?? {}) }
|
||||
return {
|
||||
...source,
|
||||
messageHistory,
|
||||
agentModelSelections,
|
||||
sessionTasks,
|
||||
sessionMessages,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
||||
agentModelSelections: {},
|
||||
sessionTasks: {},
|
||||
sessionSkills: {},
|
||||
sessionMessages: {},
|
||||
customAgents: [],
|
||||
}
|
||||
|
||||
@@ -25,6 +26,7 @@ function cloneInstanceData(data?: InstanceData | null): InstanceData {
|
||||
agentModelSelections: { ...(source.agentModelSelections ?? {}) },
|
||||
sessionTasks: { ...(source.sessionTasks ?? {}) },
|
||||
sessionSkills: { ...(source.sessionSkills ?? {}) },
|
||||
sessionMessages: { ...(source.sessionMessages ?? {}) },
|
||||
customAgents: Array.isArray(source.customAgents) ? [...source.customAgents] : [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { getUserScopedKey } from "../lib/user-storage"
|
||||
import { loadSkillDetails } from "./skills"
|
||||
import { serverApi } from "../lib/api-client"
|
||||
import { nativeSessionApi } from "../lib/lite-mode"
|
||||
import { ensureInstanceConfigLoaded, updateInstanceConfig, getInstanceConfig } from "./instance-config"
|
||||
import type { Session } from "../types/session"
|
||||
|
||||
const log = getLogger("actions")
|
||||
@@ -1284,6 +1285,37 @@ async function persistNativeMessages(
|
||||
}
|
||||
}
|
||||
|
||||
async function persistSdkMessages(
|
||||
instanceId: string,
|
||||
sessionId: string,
|
||||
messages: Array<{
|
||||
id: string
|
||||
role: "user" | "assistant" | "system" | "tool"
|
||||
content?: string
|
||||
createdAt?: number
|
||||
updatedAt?: number
|
||||
}>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await ensureInstanceConfigLoaded(instanceId)
|
||||
const existing = getInstanceConfig(instanceId).sessionMessages ?? {}
|
||||
const current = existing[sessionId] ?? []
|
||||
const merged = [...current]
|
||||
for (const message of messages) {
|
||||
if (!merged.some((entry) => entry.id === message.id)) {
|
||||
merged.push(message)
|
||||
}
|
||||
}
|
||||
merged.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0))
|
||||
const trimmed = merged.length > 200 ? merged.slice(-200) : merged
|
||||
await updateInstanceConfig(instanceId, (draft) => {
|
||||
draft.sessionMessages = { ...(draft.sessionMessages ?? {}), [sessionId]: trimmed }
|
||||
})
|
||||
} catch (error) {
|
||||
log.warn("Failed to persist SDK messages", { instanceId, sessionId, error })
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage(
|
||||
instanceId: string,
|
||||
sessionId: string,
|
||||
@@ -1296,6 +1328,7 @@ async function sendMessage(
|
||||
throw new Error("Instance not ready")
|
||||
}
|
||||
const isNative = instance.binaryPath === "__nomadarch_native__"
|
||||
const isSdk = !isNative
|
||||
|
||||
const instanceSessions = sessions().get(instanceId)
|
||||
const session = instanceSessions?.get(sessionId)
|
||||
@@ -1587,6 +1620,24 @@ async function sendMessage(
|
||||
updatedAt: completedAt,
|
||||
},
|
||||
])
|
||||
} else if (isSdk) {
|
||||
const completedAt = Date.now()
|
||||
await persistSdkMessages(instanceId, sessionId, [
|
||||
{
|
||||
id: messageId,
|
||||
role: "user",
|
||||
content: resolvedPrompt,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
{
|
||||
id: assistantMessageId,
|
||||
role: "assistant",
|
||||
content: assistantText,
|
||||
createdAt: now,
|
||||
updatedAt: completedAt,
|
||||
},
|
||||
])
|
||||
}
|
||||
return messageId
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -486,7 +486,6 @@ async function fetchSessions(instanceId: string): Promise<void> {
|
||||
})
|
||||
}
|
||||
|
||||
if (isNative) {
|
||||
const updates: Promise<unknown>[] = []
|
||||
for (const [parentId, tasks] of Object.entries(sessionTasks)) {
|
||||
if (!Array.isArray(tasks)) continue
|
||||
@@ -500,12 +499,32 @@ async function fetchSessions(instanceId: string): Promise<void> {
|
||||
...childSession,
|
||||
parentId,
|
||||
})
|
||||
if (isNative) {
|
||||
updates.push(nativeSessionApi.updateSession(instanceId, childId, { parentId }).catch(() => undefined))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (updates.length > 0) {
|
||||
await Promise.allSettled(updates)
|
||||
}
|
||||
|
||||
for (const [sessionId, tasks] of Object.entries(sessionTasks)) {
|
||||
if (sessionMap.has(sessionId)) continue
|
||||
if (!Array.isArray(tasks) || tasks.length === 0) continue
|
||||
const existingSession = existingSessions?.get(sessionId)
|
||||
sessionMap.set(sessionId, {
|
||||
id: sessionId,
|
||||
instanceId,
|
||||
title: existingSession?.title ?? "Untitled",
|
||||
parentId: existingSession?.parentId ?? null,
|
||||
agent: existingSession?.agent ?? "",
|
||||
model: existingSession?.model ?? { providerId: "", modelId: "" },
|
||||
skills: existingSession?.skills ?? [],
|
||||
version: existingSession?.version ?? "0",
|
||||
time: existingSession?.time ?? { created: Date.now(), updated: Date.now() },
|
||||
revert: existingSession?.revert,
|
||||
tasks: tasks as any[],
|
||||
})
|
||||
}
|
||||
|
||||
const validSessionIds = new Set(sessionMap.keys())
|
||||
@@ -1069,6 +1088,45 @@ async function loadMessages(instanceId: string, sessionId: string, force = false
|
||||
apiMessagesInfo = (response as any).info || {} // Assuming 'info' might be on the response object itself for some cases
|
||||
}
|
||||
|
||||
if (!isNative) {
|
||||
await ensureInstanceConfigLoaded(instanceId)
|
||||
const cachedMessages = getInstanceConfig(instanceId).sessionMessages?.[sessionId] ?? []
|
||||
if (cachedMessages.length > 0) {
|
||||
const existingIds = new Set<string>()
|
||||
for (const apiMessage of apiMessages) {
|
||||
const info = apiMessage.info || apiMessage
|
||||
if (info?.id) {
|
||||
existingIds.add(info.id)
|
||||
}
|
||||
}
|
||||
for (const cached of cachedMessages) {
|
||||
if (!cached?.id || existingIds.has(cached.id)) continue
|
||||
apiMessages.push({
|
||||
id: cached.id,
|
||||
role: cached.role,
|
||||
content: cached.content,
|
||||
createdAt: cached.createdAt,
|
||||
info: {
|
||||
id: cached.id,
|
||||
role: cached.role,
|
||||
time: { created: cached.createdAt ?? Date.now() },
|
||||
},
|
||||
parts: cached.content
|
||||
? [{ id: `part-${cached.id}`, type: "text", text: cached.content }]
|
||||
: [],
|
||||
})
|
||||
existingIds.add(cached.id)
|
||||
}
|
||||
apiMessages.sort((a, b) => {
|
||||
const aInfo = a.info || a
|
||||
const bInfo = b.info || b
|
||||
const aTime = aInfo.time?.created ?? aInfo.createdAt ?? 0
|
||||
const bTime = bInfo.time?.created ?? bInfo.createdAt ?? 0
|
||||
return aTime - bTime
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const messagesInfo = new Map<string, any>()
|
||||
const messages: Message[] = apiMessages.map((apiMessage: any) => {
|
||||
const info = apiMessage.info || apiMessage
|
||||
|
||||
Reference in New Issue
Block a user