Reduce swarm local persistence
This commit is contained in:
@@ -66,7 +66,7 @@ export type TeamFile = {
|
||||
description?: string
|
||||
createdAt: number
|
||||
leadAgentId: string
|
||||
leadSessionId?: string // Actual session UUID of the leader (for discovery)
|
||||
leadSessionId?: string // Legacy field; stripped from persisted configs
|
||||
hiddenPaneIds?: string[] // Pane IDs that are currently hidden from the UI
|
||||
teamAllowedPaths?: TeamAllowedPath[] // Paths all teammates can edit without asking
|
||||
members: Array<{
|
||||
@@ -74,15 +74,15 @@ export type TeamFile = {
|
||||
name: string
|
||||
agentType?: string
|
||||
model?: string
|
||||
prompt?: string
|
||||
prompt?: string // Legacy field; stripped from persisted configs
|
||||
color?: string
|
||||
planModeRequired?: boolean
|
||||
joinedAt: number
|
||||
tmuxPaneId: string
|
||||
cwd: string
|
||||
worktreePath?: string
|
||||
sessionId?: string
|
||||
subscriptions: string[]
|
||||
sessionId?: string // Legacy field; stripped from persisted configs
|
||||
subscriptions?: string[] // Legacy field; stripped from persisted configs
|
||||
backendType?: BackendType
|
||||
isActive?: boolean // false when idle, undefined/true when active
|
||||
mode?: PermissionMode // Current permission mode for this teammate
|
||||
@@ -123,6 +123,42 @@ export function getTeamFilePath(teamName: string): string {
|
||||
return join(getTeamDir(teamName), 'config.json')
|
||||
}
|
||||
|
||||
function sanitizeTeamFileForPersistence(teamFile: TeamFile): TeamFile {
|
||||
return {
|
||||
name: teamFile.name,
|
||||
...(teamFile.description ? { description: teamFile.description } : {}),
|
||||
createdAt: teamFile.createdAt,
|
||||
leadAgentId: teamFile.leadAgentId,
|
||||
...(teamFile.hiddenPaneIds && teamFile.hiddenPaneIds.length > 0
|
||||
? { hiddenPaneIds: [...teamFile.hiddenPaneIds] }
|
||||
: {}),
|
||||
...(teamFile.teamAllowedPaths && teamFile.teamAllowedPaths.length > 0
|
||||
? {
|
||||
teamAllowedPaths: teamFile.teamAllowedPaths.map(path => ({
|
||||
...path,
|
||||
})),
|
||||
}
|
||||
: {}),
|
||||
members: teamFile.members.map(member => ({
|
||||
agentId: member.agentId,
|
||||
name: member.name,
|
||||
...(member.agentType ? { agentType: member.agentType } : {}),
|
||||
...(member.model ? { model: member.model } : {}),
|
||||
...(member.color ? { color: member.color } : {}),
|
||||
...(member.planModeRequired !== undefined
|
||||
? { planModeRequired: member.planModeRequired }
|
||||
: {}),
|
||||
joinedAt: member.joinedAt,
|
||||
tmuxPaneId: member.tmuxPaneId,
|
||||
cwd: member.cwd,
|
||||
...(member.worktreePath ? { worktreePath: member.worktreePath } : {}),
|
||||
...(member.backendType ? { backendType: member.backendType } : {}),
|
||||
...(member.isActive !== undefined ? { isActive: member.isActive } : {}),
|
||||
...(member.mode ? { mode: member.mode } : {}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a team file by name (sync — for sync contexts like React render paths)
|
||||
* @internal Exported for team discovery UI
|
||||
@@ -131,7 +167,7 @@ export function getTeamFilePath(teamName: string): string {
|
||||
export function readTeamFile(teamName: string): TeamFile | null {
|
||||
try {
|
||||
const content = readFileSync(getTeamFilePath(teamName), 'utf-8')
|
||||
return jsonParse(content) as TeamFile
|
||||
return sanitizeTeamFileForPersistence(jsonParse(content) as TeamFile)
|
||||
} catch (e) {
|
||||
if (getErrnoCode(e) === 'ENOENT') return null
|
||||
logForDebugging(
|
||||
@@ -149,7 +185,7 @@ export async function readTeamFileAsync(
|
||||
): Promise<TeamFile | null> {
|
||||
try {
|
||||
const content = await readFile(getTeamFilePath(teamName), 'utf-8')
|
||||
return jsonParse(content) as TeamFile
|
||||
return sanitizeTeamFileForPersistence(jsonParse(content) as TeamFile)
|
||||
} catch (e) {
|
||||
if (getErrnoCode(e) === 'ENOENT') return null
|
||||
logForDebugging(
|
||||
@@ -166,7 +202,10 @@ export async function readTeamFileAsync(
|
||||
function writeTeamFile(teamName: string, teamFile: TeamFile): void {
|
||||
const teamDir = getTeamDir(teamName)
|
||||
mkdirSync(teamDir, { recursive: true })
|
||||
writeFileSync(getTeamFilePath(teamName), jsonStringify(teamFile, null, 2))
|
||||
writeFileSync(
|
||||
getTeamFilePath(teamName),
|
||||
jsonStringify(sanitizeTeamFileForPersistence(teamFile), null, 2),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -178,7 +217,10 @@ export async function writeTeamFileAsync(
|
||||
): Promise<void> {
|
||||
const teamDir = getTeamDir(teamName)
|
||||
await mkdir(teamDir, { recursive: true })
|
||||
await writeFile(getTeamFilePath(teamName), jsonStringify(teamFile, null, 2))
|
||||
await writeFile(
|
||||
getTeamFilePath(teamName),
|
||||
jsonStringify(sanitizeTeamFileForPersistence(teamFile), null, 2),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,6 @@ import { PermissionModeSchema } from '../entrypoints/sdk/coreSchemas.js'
|
||||
import { SEND_MESSAGE_TOOL_NAME } from '../tools/SendMessageTool/constants.js'
|
||||
import type { Message } from '../types/message.js'
|
||||
import { generateRequestId } from './agentId.js'
|
||||
import { count } from './array.js'
|
||||
import { logForDebugging } from './debug.js'
|
||||
import { getTeamsDir } from './envUtils.js'
|
||||
import { getErrnoCode } from './errors.js'
|
||||
@@ -192,8 +191,8 @@ export async function writeToMailbox(
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a specific message in a teammate's inbox as read by index
|
||||
* Uses file locking to prevent race conditions
|
||||
* Remove a specific processed message from a teammate's inbox by index.
|
||||
* Uses file locking to prevent race conditions.
|
||||
* @param agentName - The agent name to mark message as read for
|
||||
* @param teamName - Optional team name
|
||||
* @param messageIndex - Index of the message to mark as read
|
||||
@@ -242,11 +241,17 @@ export async function markMessageAsReadByIndex(
|
||||
return
|
||||
}
|
||||
|
||||
messages[messageIndex] = { ...message, read: true }
|
||||
const updatedMessages = messages.filter(
|
||||
(currentMessage, index) => index !== messageIndex && !currentMessage.read,
|
||||
)
|
||||
|
||||
await writeFile(inboxPath, jsonStringify(messages, null, 2), 'utf-8')
|
||||
await writeFile(
|
||||
inboxPath,
|
||||
jsonStringify(updatedMessages, null, 2),
|
||||
'utf-8',
|
||||
)
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessageAsReadByIndex: marked message at index ${messageIndex} as read`,
|
||||
`[TeammateMailbox] markMessageAsReadByIndex: removed message at index ${messageIndex} from inbox`,
|
||||
)
|
||||
} catch (error) {
|
||||
const code = getErrnoCode(error)
|
||||
@@ -270,77 +275,6 @@ export async function markMessageAsReadByIndex(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all messages in a teammate's inbox as read
|
||||
* Uses file locking to prevent race conditions
|
||||
* @param agentName - The agent name to mark messages as read for
|
||||
* @param teamName - Optional team name
|
||||
*/
|
||||
export async function markMessagesAsRead(
|
||||
agentName: string,
|
||||
teamName?: string,
|
||||
): Promise<void> {
|
||||
const inboxPath = getInboxPath(agentName, teamName)
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead called: agentName=${agentName}, teamName=${teamName}, path=${inboxPath}`,
|
||||
)
|
||||
|
||||
const lockFilePath = `${inboxPath}.lock`
|
||||
|
||||
let release: (() => Promise<void>) | undefined
|
||||
try {
|
||||
logForDebugging(`[TeammateMailbox] markMessagesAsRead: acquiring lock...`)
|
||||
release = await lockfile.lock(inboxPath, {
|
||||
lockfilePath: lockFilePath,
|
||||
...LOCK_OPTIONS,
|
||||
})
|
||||
logForDebugging(`[TeammateMailbox] markMessagesAsRead: lock acquired`)
|
||||
|
||||
// Re-read messages after acquiring lock to get the latest state
|
||||
const messages = await readMailbox(agentName, teamName)
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead: read ${messages.length} messages after lock`,
|
||||
)
|
||||
|
||||
if (messages.length === 0) {
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead: no messages to mark`,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const unreadCount = count(messages, m => !m.read)
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead: ${unreadCount} unread of ${messages.length} total`,
|
||||
)
|
||||
|
||||
// messages comes from jsonParse — fresh, unshared objects safe to mutate
|
||||
for (const m of messages) m.read = true
|
||||
|
||||
await writeFile(inboxPath, jsonStringify(messages, null, 2), 'utf-8')
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead: WROTE ${unreadCount} message(s) as read to ${inboxPath}`,
|
||||
)
|
||||
} catch (error) {
|
||||
const code = getErrnoCode(error)
|
||||
if (code === 'ENOENT') {
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead: file does not exist at ${inboxPath}`,
|
||||
)
|
||||
return
|
||||
}
|
||||
logForDebugging(
|
||||
`[TeammateMailbox] markMessagesAsRead FAILED for ${agentName}: ${error}`,
|
||||
)
|
||||
logError(error)
|
||||
} finally {
|
||||
if (release) {
|
||||
await release()
|
||||
logForDebugging(`[TeammateMailbox] markMessagesAsRead: lock released`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a teammate's inbox (delete all messages)
|
||||
* @param agentName - The agent name to clear inbox for
|
||||
@@ -1095,8 +1029,8 @@ export function isStructuredProtocolMessage(messageText: string): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks only messages matching a predicate as read, leaving others unread.
|
||||
* Uses the same file-locking mechanism as markMessagesAsRead.
|
||||
* Removes only messages matching a predicate, leaving the rest unread.
|
||||
* Uses the same file-locking mechanism as the other mailbox update helpers.
|
||||
*/
|
||||
export async function markMessagesAsReadByPredicate(
|
||||
agentName: string,
|
||||
@@ -1119,8 +1053,8 @@ export async function markMessagesAsReadByPredicate(
|
||||
return
|
||||
}
|
||||
|
||||
const updatedMessages = messages.map(m =>
|
||||
!m.read && predicate(m) ? { ...m, read: true } : m,
|
||||
const updatedMessages = messages.filter(
|
||||
m => !m.read && !predicate(m),
|
||||
)
|
||||
|
||||
await writeFile(inboxPath, jsonStringify(updatedMessages, null, 2), 'utf-8')
|
||||
|
||||
Reference in New Issue
Block a user