From f107ecba31afa034fd61365fb4f7e8283dde48e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Thu, 21 May 2026 09:04:36 -0400 Subject: [PATCH] Harden PRD page coverage and migration audit --- .../src/main/services/migration-service.ts | 38 +++++++++++- .../src/main/services/snapshot-service.ts | 2 + .../src/renderer/src/pages/ChatPage.tsx | 59 ++++++++++++++++--- .../src/renderer/src/pages/FilesPage.tsx | 2 +- .../src/renderer/src/pages/HomePage.tsx | 3 +- .../src/renderer/src/pages/SettingsPage.tsx | 36 ++++++++--- .../src/renderer/src/pages/SocialPage.tsx | 19 +++++- .../src/renderer/src/shared/Shell.tsx | 15 +++-- AgentCompany-New/src/renderer/src/styles.css | 14 +++++ AgentCompany-New/src/shared/domain.ts | 22 +++++++ AgentCompany-New/tests/e2e/desktop.spec.ts | 40 ++++++++++++- .../tests/unit/domain-services.test.ts | 10 ++++ 12 files changed, 235 insertions(+), 25 deletions(-) diff --git a/AgentCompany-New/src/main/services/migration-service.ts b/AgentCompany-New/src/main/services/migration-service.ts index c906be80..32a0c350 100644 --- a/AgentCompany-New/src/main/services/migration-service.ts +++ b/AgentCompany-New/src/main/services/migration-service.ts @@ -1,4 +1,5 @@ import { existsSync } from "node:fs"; +import { createHash } from "node:crypto"; import { DatabaseSync, type StatementSync } from "node:sqlite"; import type { CreateMigrationReportInput } from "../../shared/commands"; import { createId } from "../../shared/ids"; @@ -37,13 +38,35 @@ export class MigrationService { .filter((table) => availableTables.has(table)) .map((table) => [table, countRows(source.prepare(`select count(*) as count from ${table}`))]), ); + const tableChecksums = Object.fromEntries( + LEGACY_TABLES + .filter((table) => availableTables.has(table)) + .map((table) => [table, tableChecksum(source, table)]), + ); + const idMaps = Object.fromEntries( + LEGACY_TABLES + .filter((table) => availableTables.has(table)) + .map((table) => [table, tableIds(source, table)]), + ); const missingTables = LEGACY_TABLES.filter((table) => !availableTables.has(table)); + const failures = missingTables.map((table) => ({ table, reason: "missing_table" })); const reportId = createId("mig"); const now = new Date().toISOString(); this.db.prepare(` insert into migration_reports(id, org_id, source_ref, report_json, created_at) values (?, ?, ?, ?, ?) - `).run(reportId, input.orgId, input.sourceDatabasePath, JSON.stringify({ tableCounts, missingTables }), now); + `).run(reportId, input.orgId, input.sourceDatabasePath, JSON.stringify({ + tableCounts, + tableChecksums, + idMaps, + missingTables, + failures, + rollbackPack: { + sourceDatabasePath: input.sourceDatabasePath, + capturedAt: now, + restoreStrategy: "legacy_snapshot_restore", + }, + }), now); this.audit.record({ orgId: input.orgId, entityType: "migration_report", @@ -63,3 +86,16 @@ export class MigrationService { function countRows(statement: StatementSync): number { return (statement.get() as { count: number }).count; } + +function tableChecksum(source: DatabaseSync, table: string): string { + const hash = createHash("sha256"); + const rows = source.prepare(`select * from ${table} order by rowid`).all(); + for (const row of rows) hash.update(JSON.stringify(row)); + return hash.digest("hex"); +} + +function tableIds(source: DatabaseSync, table: string): string[] { + const columns = source.prepare(`pragma table_info(${table})`).all() as Array<{ name: string }>; + if (!columns.some((column) => column.name === "id")) return []; + return source.prepare(`select id from ${table} order by id`).all().map((row) => String((row as { id: unknown }).id)); +} diff --git a/AgentCompany-New/src/main/services/snapshot-service.ts b/AgentCompany-New/src/main/services/snapshot-service.ts index 1d892b6f..39567a6e 100644 --- a/AgentCompany-New/src/main/services/snapshot-service.ts +++ b/AgentCompany-New/src/main/services/snapshot-service.ts @@ -71,6 +71,8 @@ export class SnapshotService { skillBindings, skillRuntimeSnapshots: list(this.db, "skill_runtime_snapshots", "created_at desc"), socialAccounts: list(this.db, "social_accounts", "created_at asc"), + socialAccountGrants: list(this.db, "social_account_grants", "created_at asc"), + browserActions: list(this.db, "browser_actions", "created_at desc"), policyRules: list(this.db, "policy_rules", "created_at asc"), migrationReports: list(this.db, "migration_reports", "created_at desc"), auditEvents: list(this.db, "audit_events", "created_at desc").slice(0, 200), diff --git a/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx b/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx index b86ac467..17edd790 100644 --- a/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/ChatPage.tsx @@ -1,7 +1,7 @@ import { useState, type FormEvent, type ReactNode } from "react"; import { Avatar, Button, Field, Icon, IconButton } from "../shared/ui"; import { Dialog } from "../shared/Dialog"; -import type { ChatSpaceRecord } from "../../../shared/domain"; +import type { ChatAttachmentRecord, ChatSpaceRecord, FileObjectRecord } from "../../../shared/domain"; import type { PageProps } from "./types"; import { activeOrgId } from "./types"; @@ -18,10 +18,12 @@ export function ChatPage({ snapshot, actions }: PageProps) { const spaces = snapshot.chatSpaces.filter((item) => item.orgId === orgId); const employees = snapshot.employees.filter((item) => item.orgId === orgId); const agents = snapshot.agents.filter((item) => item.orgId === orgId); + const files = snapshot.files.filter((item) => item.orgId === orgId); const [spaceId, setSpaceId] = useState(spaces[0]?.id ?? ""); const [spaceOpen, setSpaceOpen] = useState(false); const activeSpace = spaces.find((space) => space.id === spaceId) ?? spaces[0]; - const messages = snapshot.chatMessages.filter((item) => item.orgId === orgId && item.spaceId === activeSpace?.id); + if (!activeSpace) throw new Error("At least one chat space is required."); + const messages = snapshot.chatMessages.filter((item) => item.orgId === orgId && item.spaceId === activeSpace.id); const sender = employees[0]; const submit = (event: FormEvent) => { @@ -31,6 +33,7 @@ export function ChatPage({ snapshot, actions }: PageProps) { const bodyMd = messageBody(form); if (!bodyMd) return; event.currentTarget.reset(); + const attachmentFileId = value(form, "attachmentFileId"); void actions.run(() => window.agentCompany.sendMessage({ orgId, spaceId: activeSpace.id, @@ -38,6 +41,7 @@ export function ChatPage({ snapshot, actions }: PageProps) { senderType: "employee", senderId: sender.id, bodyMd, + attachmentFileIds: attachmentFileId ? [attachmentFileId] : [], })); }; @@ -82,10 +86,10 @@ export function ChatPage({ snapshot, actions }: PageProps) {
- -
{activeSpace?.name ?? "Messages"}
+ +
{activeSpace.name}
- {activeSpace?.topic ?? ""} + {activeSpace.topic}
@@ -99,6 +103,7 @@ export function ChatPage({ snapshot, actions }: PageProps) { time={new Date(message.createdAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} name={displayName(message.senderType, message.senderId, employees, agents)} bot={message.senderType === "agent"} + files={messageFiles(message.id, snapshot.chatAttachments, files)} /> ))} @@ -107,7 +112,10 @@ export function ChatPage({ snapshot, actions }: PageProps) {