From 6e611c5de03b1af2d535714a86cc16a92bfd78c2 Mon Sep 17 00:00:00 2001 From: weichen Date: Thu, 22 Oct 2026 06:06:01 +0000 Subject: [PATCH] Keep the scratch flag out of the Core-generated contracts Why: src/contracts.ts is regenerated from Agent-System-Core by the Dev composite; the scratch field added there is dropped on regeneration and tsc then fails in make build. Evidence: contracts.ts equals the Core-generated output; npx tsc -b --noEmit, npx vitest run (685 tests) and check-knip pass locally (SELF). Impact: ConversationView (Conversation & { scratch?: boolean }) in api.ts carries the server-local flag, mirroring the Backend conversationView; no behavior change. Refs: Agent-System-Frontend#123, Agent-System-Dev#691; agent-exec receipt u3f-run (cursor, success) Co-authored-by: Cursor --- src/api.ts | 10 ++++++---- src/components/TaskSidebar.tsx | 7 +++---- src/contracts.ts | 2 -- src/store.test.ts | 7 ++++--- src/store.ts | 9 ++++----- test/scratch-session.test.ts | 9 +++++---- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/api.ts b/src/api.ts index 4aea859..29ea657 100644 --- a/src/api.ts +++ b/src/api.ts @@ -83,6 +83,8 @@ async function json(res: Response): Promise { return res.json() as Promise } +export type ConversationView = Conversation & { /** 无项目对话:服务端内部项目只承载这一条会话,不出现在项目列表里。 */ scratch?: boolean } + export const api = { permissionCatalog: () => auth.fetch(`${BASE}/api/admin/permissions/catalog`).then(r => json<{ permissions: PermissionDescriptor[] }>(r)).then(d => d.permissions ?? []), listRoles: (request: { query?: string; mode?: 'exact' | 'fuzzy'; status?: 'all' | 'enabled' | 'disabled'; action?: string; cursor?: string; limit?: number } = {}) => { @@ -117,13 +119,13 @@ export const api = { explainSystemAction: (action: string) => auth.fetch(`${BASE}/api/authz/explain?action=${encodeURIComponent(action)}`).then(r => json<{ allowed: boolean; actionId: string; source?: string; reason?: string; authorityEpoch: number }>(r)), listProjects: (signal?: AbortSignal) => auth.fetch(`${BASE}/api/projects`, signal ? { signal } : undefined).then(r => json(r)), createProject: (name: string) => auth.fetch(`${BASE}/api/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }).then(r => json(r)), - createScratchSession: (title?: string) => auth.fetch(`${BASE}/api/scratch-sessions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(title ? { title } : {}) }).then(r => json<{ project: Project; conversation: Conversation }>(r)), + createScratchSession: (title?: string) => auth.fetch(`${BASE}/api/scratch-sessions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(title ? { title } : {}) }).then(r => json<{ project: Project; conversation: ConversationView }>(r)), renameProject: (id: string, name: string) => auth.fetch(`${BASE}/api/projects/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name }) }).then(r => json(r)), deleteProject: (id: string) => auth.fetch(`${BASE}/api/projects/${id}`, { method: 'DELETE' }).then(checked), - listConversations: (projectId: string) => auth.fetch(`${BASE}/api/projects/${projectId}/conversations`).then(r => json(r)), + listConversations: (projectId: string) => auth.fetch(`${BASE}/api/projects/${projectId}/conversations`).then(r => json(r)), // 跨项目任务列表(方案 A 侧栏):默认仅未归档;includeArchived 含归档。 - listTasks: (includeArchived?: boolean) => auth.fetch(`${BASE}/api/conversations${includeArchived ? '?archived=1' : ''}`).then(r => json(r)), + listTasks: (includeArchived?: boolean) => auth.fetch(`${BASE}/api/conversations${includeArchived ? '?archived=1' : ''}`).then(r => json(r)), setConversationFlags: (id: string, flags: { pinned?: boolean; archived?: boolean; allowCrossRead?: boolean; crossPrivate?: boolean }) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(flags) }).then(r => { if (!r.ok) throw new Error(`${r.status}: ${r.statusText}`) }), setPermissionMode: (id: string, permissionMode: string) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ permissionMode }) }).then(r => { if (!r.ok) throw new Error(`${r.status}: ${r.statusText}`) }), checkpoints: (id: string, messageId: string) => auth.fetch(`${BASE}/api/conversations/${id}/checkpoints?messageId=${encodeURIComponent(messageId)}`).then(r => json<{ restored?: string[]; skipped?: string[] }>(r)), @@ -189,7 +191,7 @@ export const api = { setConversationProfile: (id: string, profileId: string) => auth.fetch(`${BASE}/api/conversations/${id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ profileId }) }).then(r => json(r)), conversationDetail: (id: string) => auth.fetch(`${BASE}/api/conversations/${id}`).then(r => - json<{ conversation: Conversation; messages: Message[]; runs: Run[]; activeHitl: HITLCard[]; lastSeq: number; resumeSeq?: number }>(r)), + json<{ conversation: ConversationView; messages: Message[]; runs: Run[]; activeHitl: HITLCard[]; lastSeq: number; resumeSeq?: number }>(r)), runEventsPage: (convId: string, runId: string, after: number, signal?: AbortSignal) => auth.fetch(`${BASE}/api/conversations/${encodeURIComponent(convId)}/runs/${encodeURIComponent(runId)}/events?after=${after}&limit=500`, { signal }) diff --git a/src/components/TaskSidebar.tsx b/src/components/TaskSidebar.tsx index 9421142..33eac53 100644 --- a/src/components/TaskSidebar.tsx +++ b/src/components/TaskSidebar.tsx @@ -3,9 +3,8 @@ import { useEffect, useRef, useState } from 'react' import { store, useStore } from '../store' import { auth } from '../auth' -import { api } from '../api' +import { api, type ConversationView } from '../api' import { Icon } from '@ui' -import type { Conversation } from '../contracts' import { relativeTime, filterTasks } from '../tasklist' import { canCreateProject, emptyProjectsNotice, projectCapabilitySummary, projectStage, projectStateLabel, projectWorkError, @@ -96,7 +95,7 @@ export function TaskSidebar({ onCollapse = store.toggleLeft, onConversationOpen, const syncNotice = syncStatus === 'degraded' ? '项目列表同步异常,正在自动重试' : syncStatus === 'offline' ? '网络已断开,恢复后自动同步项目列表' : null - const rowActions = (t: Conversation) => ( + const rowActions = (t: ConversationView) => (