feat: expand secure assistant workflows

Harden runtime execution and add local knowledge, Smart Heartbeat, usage visibility, responsive product surfaces, and cross-platform packaging support.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-08-02 10:04:59 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent 6ef1795b81
commit b3fdf96962
82 changed files with 17608 additions and 825 deletions
+70 -1
View File
@@ -25,13 +25,19 @@ import type {
AssistantArtifact,
AssistantMemory,
AssistantSchedule,
AssistantHeartbeatConfig,
AssistantHeartbeatEntry,
AssistantHeartbeatRun,
AssistantExpert,
AssistantTask,
TokenUsageSummary,
ConversationSnapshot,
WorkspaceChanges,
ProjectCreateInput,
MemoryCreateInput,
ScheduleCreateInput,
HeartbeatCreateInput,
HeartbeatUpdateInput,
ExpertCreateInput
} from '../shared/assistant-contracts'
@@ -44,6 +50,9 @@ const desktopApi: DesktopApi = {
hide: async () => {
await ipcRenderer.invoke(ipcChannels.appHide)
},
clearLocalData: async () => {
await ipcRenderer.invoke(ipcChannels.appClearLocalData)
},
onNewConversation: (listener) => {
const handler = (): void => listener()
ipcRenderer.on(ipcChannels.conversationNew, handler)
@@ -154,7 +163,22 @@ const desktopApi: DesktopApi = {
},
tasks: {
list: () =>
ipcRenderer.invoke(ipcChannels.tasksList) as Promise<AssistantTask[]>
ipcRenderer.invoke(ipcChannels.tasksList) as Promise<AssistantTask[]>,
setStatus: async (
taskId: string,
status: 'completed' | 'cancelled'
) => {
await ipcRenderer.invoke(ipcChannels.tasksSetStatus, {
taskId,
status
})
}
},
usage: {
getTokenSummary: () =>
ipcRenderer.invoke(
ipcChannels.tokenUsageSummary
) as Promise<TokenUsageSummary>
},
artifacts: {
list: (projectId?: string) =>
@@ -162,6 +186,11 @@ const desktopApi: DesktopApi = {
ipcChannels.artifactsList,
projectId
) as Promise<AssistantArtifact[]>,
get: (artifactId: string) =>
ipcRenderer.invoke(
ipcChannels.artifactsGet,
artifactId
) as Promise<AssistantArtifact>,
importFiles: (projectId?: string) =>
ipcRenderer.invoke(
ipcChannels.artifactsImportFiles,
@@ -216,6 +245,46 @@ const desktopApi: DesktopApi = {
await ipcRenderer.invoke(ipcChannels.schedulesRunNow, scheduleId)
}
},
heartbeats: {
list: (projectId?: string) =>
ipcRenderer.invoke(ipcChannels.heartbeatsList, {
projectId
}) as Promise<AssistantHeartbeatConfig[]>,
create: (input: HeartbeatCreateInput) =>
ipcRenderer.invoke(
ipcChannels.heartbeatsCreate,
input
) as Promise<AssistantHeartbeatConfig>,
update: (heartbeatId: string, input: HeartbeatUpdateInput) =>
ipcRenderer.invoke(ipcChannels.heartbeatsUpdate, {
id: heartbeatId,
config: input
}) as Promise<AssistantHeartbeatConfig>,
setPaused: async (heartbeatId: string, paused: boolean) => {
await ipcRenderer.invoke(ipcChannels.heartbeatsSetPaused, {
id: heartbeatId,
paused
})
},
remove: async (heartbeatId: string) => {
await ipcRenderer.invoke(ipcChannels.heartbeatsRemove, {
id: heartbeatId
})
},
runNow: (heartbeatId: string) =>
ipcRenderer.invoke(ipcChannels.heartbeatsRunNow, {
id: heartbeatId,
idempotencyKey: crypto.randomUUID()
}) as Promise<AssistantHeartbeatRun>,
history: (heartbeatId?: string) =>
ipcRenderer.invoke(ipcChannels.heartbeatsHistory, {
configId: heartbeatId,
limit: 200
}) as Promise<{
runs: AssistantHeartbeatRun[]
entries: AssistantHeartbeatEntry[]
}>
},
experts: {
list: () =>
ipcRenderer.invoke(
+14
View File
@@ -0,0 +1,14 @@
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { describe, expect, it } from 'vitest'
describe('sandboxed preload', () => {
it('does not import Node built-ins unavailable in Electron sandbox', () => {
const source = readFileSync(
join(process.cwd(), 'src', 'preload', 'index.ts'),
'utf8'
)
expect(source).not.toMatch(/\bfrom\s+['"]node:/u)
expect(source).not.toMatch(/\brequire\(\s*['"]node:/u)
})
})