Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
851aae7
feat(chat): add authorized ordinary workspace conversations
huangruiteng Oct 3, 2026
f428385
docs(chat): qualify the local workspace conversation journey
huangruiteng Oct 3, 2026
1e0bedc
refactor(chat): keep project context in native Chat capability package
huangruiteng Oct 3, 2026
7684631
feat(chat): bind owner private messages to native project sessions
huangruiteng Oct 3, 2026
abfd418
docs(chat): record native private admission checkpoint and limits
huangruiteng Oct 3, 2026
49562a4
fix(lark): isolate expired private App identities
huangruiteng Oct 3, 2026
c49bace
feat(chat): add bounded native steward commissions to private convers…
huangruiteng Oct 3, 2026
a0c3d68
docs(chat): record bounded steward acceptance and remaining gates
huangruiteng Oct 3, 2026
9b58bf2
feat(chat): report bound private queue and context status
huangruiteng Oct 4, 2026
2b83fa8
docs(chat): record scoped private status acceptance
huangruiteng Oct 4, 2026
0e19f89
feat(chat): select exact authorized attached Agent targets
huangruiteng Oct 4, 2026
e053823
docs(chat): record attached Agent selection checkpoint
huangruiteng Oct 4, 2026
657567b
feat(chat): default ordinary projects to workspace write grants
huangruiteng Oct 4, 2026
a76bf06
docs(chat): record workspace write defaults and qualification boundaries
huangruiteng Oct 4, 2026
20f803b
test(chat): qualify scoped writes, read-only hosts and grant revocation
huangruiteng Oct 4, 2026
6e7f41b
Merge main and repair private-chat compatibility
loopx-agent Oct 4, 2026
c1e22b1
fix(chat): recover committed steward commissions and sync workspace UI
loopx-agent Oct 4, 2026
fca7ee3
chore(chat): integrate steward recovery into private status
loopx-agent Oct 4, 2026
f655ea9
chore(chat): integrate recovery and workspace scope into Agent selection
loopx-agent Oct 4, 2026
ce75950
fix(chat): preserve workspace writes in the unified conversation scope
loopx-agent Oct 4, 2026
a8fba93
chore(chat): reconcile registry IO metadata across the private Bot stack
loopx-agent Oct 4, 2026
a1984be
chore(chat): reconcile registry IO metadata across the private Bot stack
loopx-agent Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "整理一下最近的素材";
Expand Down
2 changes: 1 addition & 1 deletion apps/presentation/dashboard/src/data/chat-model.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
34 changes: 34 additions & 0 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof privateConversationSchema>;
export async function fetchPrivateConversations() {
return privateConversationsSchema.parse(await requestJson<unknown>("/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<unknown>("/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<unknown>("/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<unknown>("/api/chat/lark/private-conversations/agent-targets", {
method: "POST", headers: {"Content-Type": "application/json"}, body: JSON.stringify({binding_id: bindingId, revision, ...target}),
}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down Expand Up @@ -2443,6 +2444,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"header.scopeWorkspacesUnavailable": "暂时无法读取工作区",
"workspace.conversation": "工作区对话 · {grant}",
"workspace.grantRead": "只读",
"workspace.grantWrite": "读写",
"workspace.grantRevoked": "已不再授权",
"workspace.unknownTitle": "不可用的工作区",
"workspace.unavailable": "此宿主已不再授权这个工作区。历史记录会保留;重新授权前,新消息会被拒绝。",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {PrivateConversationPanel} from "./private-conversation-panel";
import { useEffect, useMemo, useRef, useState } from "react";
import {
Bot,
Expand Down Expand Up @@ -539,6 +540,7 @@ export function LarkSettingsPage({
</header>
)}

<PrivateConversationPanel />
<nav className="personal-lark-tabs" aria-label={t("lark.management")}>
<button aria-current={tab === "apps" ? "page" : undefined} onClick={() => setTab("apps")} type="button">{t("lark.apps")} <span>{loading ? "…" : apps.length}</span></button>
<button aria-current={tab === "connections" ? "page" : undefined} onClick={() => setTab("connections")} type="button">{t("lark.connections")} <span>{loading ? "…" : connections.length}</span></button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1877,7 +1877,7 @@ export function PersonalWorkspacePage({
],
value: selectedWorkspaceRef ?? stewardScopeValue,
} : null}
workspaceGrantLabel={selectedWorkspaceRef ? t(workspaceUnavailable ? "workspace.grantRevoked" : "workspace.grantRead") : null}
workspaceGrantLabel={selectedWorkspaceRef ? t(workspaceUnavailable ? "workspace.grantRevoked" : selectedWorkspaceProject?.grant === "workspace_write" ? "workspace.grantWrite" : "workspace.grantRead") : null}
managerChatOpen={managerChatOpen}
managerChannelBinding={managerChannelBinding}
managerRuntime={managerRuntime}
Expand Down
Loading
Loading