Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 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
3afccc8
feat(lark): enable native private feedback through shared Inbox lifec…
huangruiteng Oct 4, 2026
7efb7bb
docs(lark): disclose default reactions and intermediate delivery sema…
huangruiteng Oct 4, 2026
f578192
test(lark): qualify feedback queue, replay and cleanup recovery
huangruiteng Oct 4, 2026
a626e41
Preserve native project defaults and render general private replies
huangruiteng Oct 4, 2026
670db0f
Document private presentation and exact project resume boundaries
huangruiteng Oct 4, 2026
0949845
Cover project default adoption and native rich reply recovery
huangruiteng Oct 4, 2026
cadecda
Avoid duplicate admission messages when native feedback succeeds
huangruiteng Oct 4, 2026
02a1dcc
Explain concise queue and reaction fallback presentation
huangruiteng Oct 4, 2026
d042e2a
Qualify queue notices and text fallback without duplicate receipts
huangruiteng Oct 4, 2026
4640e99
Prevent slow private reply readbacks from holding control feedback
huangruiteng Oct 4, 2026
cbd7c00
Reuse operation-local private inbox preparation without weakening grants
huangruiteng Oct 4, 2026
d1015be
Separate private listener discovery from message authorization
loopx-agent 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
a8d79bc
chore(lark): integrate recovery and workspace scope into private feed…
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
30873c5
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