feat: shared supervisor timeline, story assignment and time-spiral view

Each source version is extracted once for every review scope; events carry spans and projects from their sources, and attention density is counted from messages. After publication, events are assigned to project features and sub-threads (cross-project stories only when enabled), with user rename, merge, remove, move and undo. The story graph gains a lazily loaded Three.js time-spiral view. Includes the 3D concept demo, real-data DeepSeek check scripts and docs.
This commit is contained in:
mesalogo
2026-10-02 15:21:48 +08:00
parent 3d4e9577b5
commit 5dc65457d9
53 changed files with 4051 additions and 83 deletions
+21
View File
@@ -258,6 +258,7 @@ import { HeartbeatService } from './assistant/heartbeat-service'
import { createProductionSuggestionPhraser, createProductionSupervisorService } from './assistant/supervision-production'
import { deriveSuggestions } from './assistant/supervision-suggester'
import { supervisionReviewIdSchema, supervisionBatchesRequestSchema } from '../shared/supervision-review-contracts'
import { supervisionStoryActionSchema, supervisionStoryListSchema } from '../shared/supervision-story-contracts'
import {
supervisionEntityActionSchema,
supervisionActivityRequestSchema,
@@ -7248,6 +7249,26 @@ export function registerIpcHandlers(
knowledgeService.database.updateEntity(request.entityId!, { name: request.label, type: request.type, description: request.description || null, aliases: request.aliases, locked: true })
return { operation: request.operation, status: 'committed' }
})
registerHandler(ipcChannels.supervisionStories, async (event, input: unknown) => {
assertTrustedSender(event, window)
const { scope } = supervisionStoryListSchema.parse(input)
if ((await applicationSettingsStore?.get())?.heartbeatEnabled !== true) return { stories: [], unassigned: 0, canUndo: false }
const stories = assistantDatabase.supervisionStories()
return { stories: stories.list(scope), unassigned: stories.unassignedCount(scope), canUndo: stories.canUndo() }
})
registerHandler(ipcChannels.supervisionStoryAction, async (event, input: unknown) => {
assertTrustedSender(event, window)
const action = supervisionStoryActionSchema.parse(input)
if ((await applicationSettingsStore?.get())?.heartbeatEnabled !== true) throw new Error('Supervisor is disabled')
assistantDatabase.supervisionStories().act(action)
})
registerHandler(ipcChannels.supervisionRetryStories, async (event, input: unknown) => {
assertTrustedSender(event, window)
const { runId } = supervisionReviewIdSchema.parse(input)
if ((await applicationSettingsStore?.get())?.heartbeatEnabled !== true) throw new Error('Supervisor is disabled')
if (executionPaused || shuttingDown) throw new Error('本地数据维护期间暂不接受新任务')
return trackExecution(supervisorService.organizeStoriesFor(runId))
})
registerHandler(ipcChannels.supervisionEntityAction, (event, input: unknown) => {
assertTrustedSender(event, window)
assistantDatabase.applySupervisionEntityAction(supervisionEntityActionSchema.parse(input))