diff --git a/apps/presentation/dashboard/smoke/workspace-scope-browser-smoke.mjs b/apps/presentation/dashboard/smoke/workspace-scope-browser-smoke.mjs index 34a295e7fc..909bfc2df3 100644 --- a/apps/presentation/dashboard/smoke/workspace-scope-browser-smoke.mjs +++ b/apps/presentation/dashboard/smoke/workspace-scope-browser-smoke.mjs @@ -54,7 +54,7 @@ try { await page.getByRole("navigation", { name: "管家视图" }).getByRole("button", { name: "对话" }).click(); await scope.click(); await page.getByRole("option", { name: "notes" }).click(); - await page.getByText("工作区对话 · 只读").waitFor(); + await page.getByText("工作区对话 · 读写").waitFor(); const workspaceUrl = page.url(); assert.match(workspaceUrl, /[?&]workspace=[0-9a-f]{24}(?:&|$)/u); const question = "整理一下最近的素材"; diff --git a/apps/presentation/dashboard/src/data/chat-model.ts b/apps/presentation/dashboard/src/data/chat-model.ts index e39d350406..66bb18b38e 100644 --- a/apps/presentation/dashboard/src/data/chat-model.ts +++ b/apps/presentation/dashboard/src/data/chat-model.ts @@ -1,7 +1,7 @@ import type { GoalDraft } from "../../../../../loopx/control_plane/collaboration/goal_draft.js"; export type LoopXModeSettings = { agent_id: string; token_budget: number }; /** A host-granted workspace an ordinary conversation may be scoped to. */ -export type ChatProject = { project_ref: string; title: string; grant: "workspace_read" }; +export type ChatProject = { project_ref: string; title: string; grant: "workspace_read" | "workspace_write" }; export type ChatTodo = { todo_id: string | null; diff --git a/apps/presentation/dashboard/src/data/chat.ts b/apps/presentation/dashboard/src/data/chat.ts index cc4a4a62c6..ceb46c31dd 100644 --- a/apps/presentation/dashboard/src/data/chat.ts +++ b/apps/presentation/dashboard/src/data/chat.ts @@ -2377,3 +2377,37 @@ export async function updateGoalOwnership(body: { goal_id: string; mode: Executi throw error; } } + + +const privateAgentSessionSchema = z.object({session_id: z.string(), goal_id: z.string(), agent_id: z.string(), executor_endpoint_id: z.string()}); +const privateAgentTargetSchema = privateAgentSessionSchema.extend({target_ref: z.string(), host_ref: z.string(), goal_instance_id: z.string().nullable()}); +const privateConversationSchema = z.object({ + binding_id: z.string(), app_ref: z.string(), context_kind: z.enum(["project", "steward"]), + project_ref: z.string(), project_title: z.string(), context_available: z.boolean(), executor_endpoint_id: z.string(), + grant: z.enum(["workspace_read", "workspace_write", "portfolio_read"]), goal_count: z.number().int().default(0), listener_status: z.string(), + pending_count: z.number().int(), recovery_count: z.number().int(), + agent_candidates: z.array(privateAgentSessionSchema).default([]), agent_targets: z.array(privateAgentTargetSchema).default([]), +}); +const privateConversationsSchema = z.object({ok: z.literal(true), revision: z.number().int(), + connections: z.array(privateConversationSchema)}); +export type PrivateConversation = z.infer; +export async function fetchPrivateConversations() { + return privateConversationsSchema.parse(await requestJson("/api/chat/lark/private-conversations")); +} +export async function connectPrivateConversation(appRef: string, projectRef: string, executor: string, contextKind: "project" | "steward" = "project", projectGrant: "workspace_read" | "workspace_write" = "workspace_write") { + return privateConversationsSchema.parse(await requestJson("/api/chat/lark/private-conversations", { + method: "POST", headers: {"Content-Type": "application/json"}, + body: JSON.stringify({app_ref: appRef, project_ref: projectRef, executor_endpoint_id: executor, context_kind: contextKind, project_grant: projectGrant}), + })); +} +export async function disconnectPrivateConversation(bindingId: string, revision: number) { + return privateConversationsSchema.parse(await requestJson("/api/chat/lark/private-conversations", { + method: "DELETE", headers: {"Content-Type": "application/json"}, body: JSON.stringify({binding_id: bindingId, revision}), + })); +} + +export async function changePrivateAgentTarget(bindingId: string, revision: number, target: {session_id: string} | {target_ref: string}) { + return privateConversationsSchema.parse(await requestJson("/api/chat/lark/private-conversations/agent-targets", { + method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({binding_id: bindingId, revision, ...target}), + })); +} diff --git a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx index 3f7eaec907..07b8a9c84a 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/i18n.tsx @@ -1154,6 +1154,7 @@ const en = { "header.scopeWorkspacesUnavailable": "Workspaces could not be read", "workspace.conversation": "Workspace conversation · {grant}", "workspace.grantRead": "read-only", + "workspace.grantWrite": "read and write", "workspace.grantRevoked": "no longer authorized", "workspace.unknownTitle": "Unavailable workspace", "workspace.unavailable": "This host no longer authorizes this workspace. The history is kept; new messages will be rejected until it is authorized again.", @@ -2443,6 +2444,7 @@ const zhCN: Record = { "header.scopeWorkspacesUnavailable": "暂时无法读取工作区", "workspace.conversation": "工作区对话 · {grant}", "workspace.grantRead": "只读", + "workspace.grantWrite": "读写", "workspace.grantRevoked": "已不再授权", "workspace.unknownTitle": "不可用的工作区", "workspace.unavailable": "此宿主已不再授权这个工作区。历史记录会保留;重新授权前,新消息会被拒绝。", diff --git a/apps/presentation/dashboard/src/features/personal-workspace/lark-settings-page.tsx b/apps/presentation/dashboard/src/features/personal-workspace/lark-settings-page.tsx index 07ef1c6b8a..cade2c603e 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/lark-settings-page.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/lark-settings-page.tsx @@ -1,3 +1,4 @@ +import {PrivateConversationPanel} from "./private-conversation-panel"; import { useEffect, useMemo, useRef, useState } from "react"; import { Bot, @@ -539,6 +540,7 @@ export function LarkSettingsPage({ )} +