This commit is contained in:
@@ -192,6 +192,16 @@ export interface InstanceData {
|
|||||||
agentModelSelections: AgentModelSelection
|
agentModelSelections: AgentModelSelection
|
||||||
sessionTasks?: SessionTasks // Multi-task chat support: tasks per session
|
sessionTasks?: SessionTasks // Multi-task chat support: tasks per session
|
||||||
sessionSkills?: Record<string, SkillSelection[]> // Selected skills 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<{
|
customAgents?: Array<{
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
|
|||||||
@@ -26,6 +26,20 @@ const InstanceDataSchema = z.object({
|
|||||||
messageHistory: z.array(z.string()).default([]),
|
messageHistory: z.array(z.string()).default([]),
|
||||||
agentModelSelections: z.record(z.string(), ModelPreferenceSchema).default({}),
|
agentModelSelections: z.record(z.string(), ModelPreferenceSchema).default({}),
|
||||||
sessionTasks: z.record(z.string(), z.array(TaskSchema)).optional(),
|
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
|
sessionSkills: z
|
||||||
.record(
|
.record(
|
||||||
z.string(),
|
z.string(),
|
||||||
@@ -47,6 +61,7 @@ const EMPTY_INSTANCE_DATA: InstanceData = {
|
|||||||
messageHistory: [],
|
messageHistory: [],
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
sessionTasks: {},
|
sessionTasks: {},
|
||||||
|
sessionMessages: {},
|
||||||
sessionSkills: {},
|
sessionSkills: {},
|
||||||
customAgents: [],
|
customAgents: [],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
|||||||
messageHistory: [],
|
messageHistory: [],
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
sessionTasks: {},
|
sessionTasks: {},
|
||||||
|
sessionMessages: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InstanceStore {
|
export class InstanceStore {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
|||||||
messageHistory: [],
|
messageHistory: [],
|
||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
sessionTasks: {},
|
sessionTasks: {},
|
||||||
|
sessionMessages: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDeepEqual(a: unknown, b: unknown): boolean {
|
function isDeepEqual(a: unknown, b: unknown): boolean {
|
||||||
@@ -157,11 +158,13 @@ export class ServerStorage {
|
|||||||
const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : []
|
const messageHistory = Array.isArray(source.messageHistory) ? [...source.messageHistory] : []
|
||||||
const agentModelSelections = { ...(source.agentModelSelections ?? {}) }
|
const agentModelSelections = { ...(source.agentModelSelections ?? {}) }
|
||||||
const sessionTasks = { ...(source.sessionTasks ?? {}) }
|
const sessionTasks = { ...(source.sessionTasks ?? {}) }
|
||||||
|
const sessionMessages = { ...(source.sessionMessages ?? {}) }
|
||||||
return {
|
return {
|
||||||
...source,
|
...source,
|
||||||
messageHistory,
|
messageHistory,
|
||||||
agentModelSelections,
|
agentModelSelections,
|
||||||
sessionTasks,
|
sessionTasks,
|
||||||
|
sessionMessages,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const DEFAULT_INSTANCE_DATA: InstanceData = {
|
|||||||
agentModelSelections: {},
|
agentModelSelections: {},
|
||||||
sessionTasks: {},
|
sessionTasks: {},
|
||||||
sessionSkills: {},
|
sessionSkills: {},
|
||||||
|
sessionMessages: {},
|
||||||
customAgents: [],
|
customAgents: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ function cloneInstanceData(data?: InstanceData | null): InstanceData {
|
|||||||
agentModelSelections: { ...(source.agentModelSelections ?? {}) },
|
agentModelSelections: { ...(source.agentModelSelections ?? {}) },
|
||||||
sessionTasks: { ...(source.sessionTasks ?? {}) },
|
sessionTasks: { ...(source.sessionTasks ?? {}) },
|
||||||
sessionSkills: { ...(source.sessionSkills ?? {}) },
|
sessionSkills: { ...(source.sessionSkills ?? {}) },
|
||||||
|
sessionMessages: { ...(source.sessionMessages ?? {}) },
|
||||||
customAgents: Array.isArray(source.customAgents) ? [...source.customAgents] : [],
|
customAgents: Array.isArray(source.customAgents) ? [...source.customAgents] : [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { getUserScopedKey } from "../lib/user-storage"
|
|||||||
import { loadSkillDetails } from "./skills"
|
import { loadSkillDetails } from "./skills"
|
||||||
import { serverApi } from "../lib/api-client"
|
import { serverApi } from "../lib/api-client"
|
||||||
import { nativeSessionApi } from "../lib/lite-mode"
|
import { nativeSessionApi } from "../lib/lite-mode"
|
||||||
|
import { ensureInstanceConfigLoaded, updateInstanceConfig, getInstanceConfig } from "./instance-config"
|
||||||
import type { Session } from "../types/session"
|
import type { Session } from "../types/session"
|
||||||
|
|
||||||
const log = getLogger("actions")
|
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(
|
async function sendMessage(
|
||||||
instanceId: string,
|
instanceId: string,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
@@ -1296,6 +1328,7 @@ async function sendMessage(
|
|||||||
throw new Error("Instance not ready")
|
throw new Error("Instance not ready")
|
||||||
}
|
}
|
||||||
const isNative = instance.binaryPath === "__nomadarch_native__"
|
const isNative = instance.binaryPath === "__nomadarch_native__"
|
||||||
|
const isSdk = !isNative
|
||||||
|
|
||||||
const instanceSessions = sessions().get(instanceId)
|
const instanceSessions = sessions().get(instanceId)
|
||||||
const session = instanceSessions?.get(sessionId)
|
const session = instanceSessions?.get(sessionId)
|
||||||
@@ -1587,6 +1620,24 @@ async function sendMessage(
|
|||||||
updatedAt: completedAt,
|
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
|
return messageId
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -486,26 +486,45 @@ async function fetchSessions(instanceId: string): Promise<void> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNative) {
|
const updates: Promise<unknown>[] = []
|
||||||
const updates: Promise<unknown>[] = []
|
for (const [parentId, tasks] of Object.entries(sessionTasks)) {
|
||||||
for (const [parentId, tasks] of Object.entries(sessionTasks)) {
|
if (!Array.isArray(tasks)) continue
|
||||||
if (!Array.isArray(tasks)) continue
|
for (const task of tasks as Array<{ taskSessionId?: string }>) {
|
||||||
for (const task of tasks as Array<{ taskSessionId?: string }>) {
|
const childId = task?.taskSessionId
|
||||||
const childId = task?.taskSessionId
|
if (!childId) continue
|
||||||
if (!childId) continue
|
const childSession = sessionMap.get(childId)
|
||||||
const childSession = sessionMap.get(childId)
|
if (!childSession) continue
|
||||||
if (!childSession) continue
|
if (childSession.parentId === parentId) continue
|
||||||
if (childSession.parentId === parentId) continue
|
sessionMap.set(childId, {
|
||||||
sessionMap.set(childId, {
|
...childSession,
|
||||||
...childSession,
|
parentId,
|
||||||
parentId,
|
})
|
||||||
})
|
if (isNative) {
|
||||||
updates.push(nativeSessionApi.updateSession(instanceId, childId, { parentId }).catch(() => undefined))
|
updates.push(nativeSessionApi.updateSession(instanceId, childId, { parentId }).catch(() => undefined))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (updates.length > 0) {
|
}
|
||||||
await Promise.allSettled(updates)
|
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())
|
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
|
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 messagesInfo = new Map<string, any>()
|
||||||
const messages: Message[] = apiMessages.map((apiMessage: any) => {
|
const messages: Message[] = apiMessages.map((apiMessage: any) => {
|
||||||
const info = apiMessage.info || apiMessage
|
const info = apiMessage.info || apiMessage
|
||||||
|
|||||||
Reference in New Issue
Block a user