Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,25 @@ export type ManagerRuntimeSessionReadback = {
tool_classes: string[];
};

export type CollaborationReadback = {
schema_version: "collaboration_request_readback_v0";
request_id: string;
agent_id: string;
brief: {
purpose: string;
context: string;
constraints: string[];
inputs: { ref: string; description: string; sha256?: string }[];
acceptance: string[];
return_requirement: string;
};
read_status: string;
decision: string;
returns: { phase: string; status: string }[];
};

export type ChatVisibleMessage = {
collaboration?: CollaborationReadback;
origin?: string;
attachments?: ChatImageAttachment[];
message_id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CollaborationCard } from "./collaboration-card";
import { Bot, Sparkles } from "lucide-react";

import { AttentionRow } from "./cards/attention-row";
Expand Down Expand Up @@ -77,7 +78,8 @@ export function ChannelTimeline({
{item.message.attachments?.length ? <div className="personal-message-images">{item.message.attachments.map((attachment) => <img alt={attachment.name} key={attachment.id} src={attachment.dataUrl} />)}</div> : null}
{item.message.role === "user" ? <p>{item.message.text}</p> : <MarkdownText text={item.message.text} />}
{item.message.pending ? <span className="personal-message-pending">{t("timeline.pending")}</span> : null}
<ReturnDeliveryStatus delivery={item.message.returnDelivery} />
<CollaborationCard request={item.message.collaboration} />
<ReturnDeliveryStatus delivery={item.message.returnDelivery} />
</div>
</article>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.personal-collaboration { margin-top: 12px; border: 1px solid var(--color-border, #ebebeb); border-radius: 6px; padding: 12px 16px; overflow-wrap: anywhere; }
.personal-collaboration header { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; }
.personal-collaboration header span, .personal-collaboration-status { font-size: 12px; opacity: .75; }
.personal-collaboration-status { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.personal-collaboration summary { cursor: pointer; min-height: 44px; display: list-item; align-content: center; }
.personal-collaboration h4 { margin: 12px 0 4px; font-size: 13px; }
.personal-collaboration p { white-space: pre-wrap; }
.personal-collaboration ul { padding-left: 20px; }
.personal-collaboration small { display: block; font-family: var(--font-mono); font-size: 11px; }
.personal-collaboration summary:focus-visible { outline: 2px solid var(--color-link, #0070f3); outline-offset: 2px; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { CollaborationReadback } from "../../data/chat";
import { useWorkspaceI18n } from "./i18n";
import "./collaboration-card.css";

const copy = {
"zh-CN": {
title: "交办说明", context: "背景与补充", constraints: "约束", inputs: "输入材料",
acceptance: "验收要求", return: "需要回传", supplied: "已提供给接收方", pending: "等待接收方读取",
decision: "接收方判断", unknown: "尚未记录", unavailable: "暂时无法读取",
adopt: "已采纳", defer: "已暂缓", reject: "未采纳", no_change: "无需调整",
result: "结论已保存", delivered: "结论已回传", details: "查看交办内容",
},
en: {
title: "Delegation brief", context: "Context & corrections", constraints: "Constraints", inputs: "Inputs",
acceptance: "Acceptance", return: "Expected return", supplied: "Supplied to receiver", pending: "Awaiting receiver read",
decision: "Receiver decision", unknown: "Not recorded", unavailable: "Readback unavailable",
adopt: "Adopted", defer: "Deferred", reject: "Rejected", no_change: "No change needed",
result: "Conclusion saved", delivered: "Conclusion returned", details: "View delegation details",
},
};

export function CollaborationCard({ request }: { request?: CollaborationReadback }) {
const { locale } = useWorkspaceI18n();
if (!request) return null;
const c = copy[locale];
const brief = request.brief;
const decision = request.decision as "adopt" | "defer" | "reject" | "no_change";
const conclusion = request.returns.find((reply) => reply.phase === "conclusion");
return <section className="personal-collaboration" aria-label={c.title}>
<header><strong>{brief.purpose}</strong><span>{request.agent_id}</span></header>
<p className="personal-collaboration-status">
<span>{request.read_status === "supplied" ? c.supplied : request.read_status === "unavailable" ? c.unavailable : c.pending}</span>
<span>{c.decision}: {c[decision] ?? (request.decision === "unavailable" ? c.unavailable : c.unknown)}</span>
{conclusion ? <span>{conclusion.status === "delivered" ? c.delivered : c.result}</span> : null}
</p>
<details>
<summary>{c.details}</summary>
<h4>{c.context}</h4><p>{brief.context}</p>
{brief.constraints.length ? <><h4>{c.constraints}</h4><ul>{brief.constraints.map((item, i) => <li key={i}>{item}</li>)}</ul></> : null}
{brief.inputs.length ? <><h4>{c.inputs}</h4><ul>{brief.inputs.map((input, i) => <li key={i}><code>{input.ref}</code> — {input.description}{input.sha256 ? <small>sha256:{input.sha256}</small> : null}</li>)}</ul></> : null}
<h4>{c.acceptance}</h4><ul>{brief.acceptance.map((item, i) => <li key={i}>{item}</li>)}</ul>
<h4>{c.return}</h4><p>{brief.return_requirement}</p>
</details>
</section>;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { CollaborationReadback } from "../../data/chat";
import type { TeamPlanAppliedOutcome } from "./team-plan-preview";
import type { ActionReviewPlan } from "../../../../../../loopx/control_plane/presentation/action_review_plan.js";
import type { GoalAcceptanceObservation } from "../../data/goal-acceptance-observation";
Expand Down Expand Up @@ -245,6 +246,7 @@ export type WorkspaceActionPreview = {
};

export type WorkspaceMessage = {
collaboration?: CollaborationReadback;
agentLabel?: string;
attachments?: WorkspaceImageAttachment[];
id: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CollaborationCard } from "./collaboration-card";
import {
compileActionReviewPlan,
isStaleActionFailure,
Expand Down Expand Up @@ -295,6 +296,7 @@ function ManagerConversationTray({
<div className="personal-manager-conversation-bubble">
{message.role === "user" ? <p>{message.text}</p> : <MarkdownText text={message.text} />}
{message.pending ? <small>{t("conversation.agentPending")}</small> : null}
<CollaborationCard request={message.collaboration} />
<ReturnDeliveryStatus delivery={message.returnDelivery} />
</div>
</article>
Expand Down
13 changes: 11 additions & 2 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -503,13 +503,15 @@ type PersonalHomeModel = {
};
type PersonalManagerMessage = {
sourceMessageId?: string;
sourceTurnId?: string;
activity?: string[];
agentLabel?: string;
attachments?: WorkspaceImageAttachment[];
id: number;
lines: string[];
pending?: boolean;
returnDelivery?: ChatVisibleMessage["return_delivery"];
collaboration?: ChatVisibleMessage["collaboration"];
reconnect?: boolean;
role: "assistant" | "user";
sourceLabel?: string;
Expand Down Expand Up @@ -1616,14 +1618,18 @@ function PersonalGoalHome({
const deliveryByMessage = new Map(
replies.map((row) => [row.message_id, row.return_delivery]),
);
const byTurn = new Map(snapshot.messages.filter((row) => row.role !== "user" && row.origin !== "manager_followup").map((row) => [row.turn_id, row]));
const collaborationByMessage = new Map(snapshot.messages.map((row) => [row.message_id, row.collaboration]));
let deliveryChanged = false;
const updated = previous.map((row) => {
const delivery = row.sourceMessageId
? deliveryByMessage.get(row.sourceMessageId)
: undefined;
if (JSON.stringify(delivery) === JSON.stringify(row.returnDelivery)) return row;
const source = row.sourceTurnId ? byTurn.get(row.sourceTurnId) : undefined;
const collaboration = row.sourceMessageId ? collaborationByMessage.get(row.sourceMessageId) : source?.collaboration;
if (JSON.stringify(delivery) === JSON.stringify(row.returnDelivery) && JSON.stringify(collaboration) === JSON.stringify(row.collaboration)) return row;
deliveryChanged = true;
return { ...row, returnDelivery: delivery };
return { ...row, sourceMessageId: row.sourceMessageId ?? source?.message_id, returnDelivery: delivery, collaboration };
});
if (!fresh.length && !deliveryChanged) return current;
return { ...current, [contextId]: [...updated, ...fresh.map((row) => ({
Expand Down Expand Up @@ -1733,6 +1739,7 @@ function PersonalGoalHome({
lines: [],
role: message.role === "user" ? "user" : "assistant",
returnDelivery: message.return_delivery,
collaboration: message.collaboration,
sourceLabel: message.role === "user"
? undefined
: message.role === "error"
Expand Down Expand Up @@ -2211,6 +2218,7 @@ function PersonalGoalHome({
}));
},
onPhase: (_phase, turnId) => {
if (streamingMessageId !== null) updateManagerAssistantMessage(targetContextId, streamingMessageId, { sourceTurnId: turnId });
activeTurnIds.current.set(targetContextId, turnId);
recordRuntimeBinding(targetContextId, {
agentId: selectedRoute.agentId,
Expand Down Expand Up @@ -2626,6 +2634,7 @@ function PersonalGoalHome({
id: String(message.id),
pending: message.pending,
returnDelivery: message.returnDelivery,
collaboration: message.collaboration,
role: message.role,
text: message.text || (message.pending ? "Agent 正在处理…" : message.lines.join("\n")),
},
Expand Down
14 changes: 14 additions & 0 deletions docs/architecture/rfcs/capable-manager-semantic-handoff-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,20 @@ At `43d362532`, the private `manager_runtime` profile, steward executor configur

The [overall roadmap](loopx-overall-roadmap-v0.md) records verified F1–F7 and R1–R7. Repair R1 commitment preservation, stale basis and recovery first, then qualify R2 small teams; M2/M3 converge through R3, and M4 requires real user journeys. Section 4 retains its older baseline as migration input, not an override of this checkpoint. Partial merges do not complete M1–M4. Parallel joins, pipeline dependencies, peer help/review, execution responsibility continuation and cross-host collaboration between long-running LoopX Agents follow the roadmap Section 5 matrix. R2 requires real inter-Agent handoff; M2/M3 cannot reduce to steward broadcasts or one-turn forwarding.

The local semantic exchange now extends the existing `manager-context` owner:
`collaboration_brief_v0` preserves corrections, constraints, relative artifact
versions, acceptance and return requirements alongside the unchanged owner
message. Same-Goal peers independently assess requests and return conclusions;
the requester receives results again until explicit consumption. Owner-local Chat
shows the brief and live read/decision/return facts in the original conversation.
The optional identity-scoped stdio tools support sandboxed managed dsh workers.
See the [three-Agent allocation demo](../../../examples/collaboration-delivery/README.md)
for dependent artifacts, two review rounds and owner correction. The controller
selects phases and transfers files; request transport adds no scheduling, lease
transfer or shared-intent mutation. This is a bounded R3 slice, not M2–M4/G1
completion; external-audience peer forwarding and cross-host continuation remain
with their existing roadmap owners.

## 4. Current-system contract: audited facts

The baseline already has substantial reusable machinery:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@

[整体路线总纲](loopx-overall-roadmap-v0.zh-CN.md) 记录已复核 F1–F7 与 R1–R7 执行卡。优先修 R1 承诺保留、stale basis 和恢复,再验 R2 小团队;M2/M3 按 R3 收敛,M4 需真实用户旅程。第 4 节的旧基线保留为迁移输入,不能覆盖本检查点;不因局部切片合并将 M1–M4 标为完成。 多个长程 LoopX Agent 的并行汇合、流水线依赖、peer 求助/复核、执行责任接续和跨 host 协作,统一按路线第 5 节协作矩阵验收;R2 必须包含真实 Agent 间 handoff,M2/M3 不能退化为管家广播或单轮转发。

本地语义协作沿用 `manager-context` owner:`collaboration_brief_v0` 在原始用户
消息旁保留修订、约束、相对产物版本、验收与回传要求。同 Goal 同伴独立判断请求并
回传结论,请求者在明确消费前会持续收到结果。管家原对话展示交办内容和实时读取、
判断、回传事实。显式配置的身份限定 stdio 工具支持保留沙箱的 managed dsh。
[三 Agent 分配器 demo](../../../examples/collaboration-delivery/README.md#中文操作说明)
覆盖依赖产物、两轮复核与用户修订;控制器选择阶段并交接文件。请求传输不授予调度、
lease 转交或 shared-intent 修改权限。这是 R3 的有界切片,不代表 M2–M4/G1 完成;
外部受众同伴转发与跨主机续接继续归现有路线 owner。

## 4. 当前系统:已核对的基线事实

已有大量基础应该复用:
Expand Down
1 change: 1 addition & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ Progress means another independently reproducible user journey, not more fields
| Shared authority | `AuthorityStore`, File/SQLite candidates, PostgreSQL store/service admission seam, recovery and conformance foundations | A deployed authenticated cross-host service, a promoted default provider or distributed quota |
| Alignment | Stage 1/2 source-basis reader and amendment admission/retention | A full Goal-intent revision in `source_basis_digest`, or Stage 3 automatic commit |
| Managed execution | `turn run-once`, managed step, attached broker and executor fences | Unattended long-horizon supervision from one bounded segment, or a healthy executor for every registered Agent |
| Semantic peer delivery | Existing Inbox/reply owners now carry immutable briefs, same-Goal peer requests, explicit result consumption and original-conversation readback; [real managed demo](../../../examples/collaboration-delivery/README.md) | Controller-driven phases do not qualify autonomous scheduling, cross-host/Lark peers or full G1/M2–M4 |

### Verified Findings

Expand Down
1 change: 1 addition & 0 deletions docs/architecture/rfcs/loopx-overall-roadmap-v0.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ R2 的一条依赖必须通过真实 LoopX Agent 间的请求/产物交接完成
| shared authority | `AuthorityStore`、File/SQLite 候选与 PostgreSQL store/service admission 接缝、恢复及 conformance 基础 | 已部署 authenticated 跨主机服务;已晋升任一默认 provider;分布式 quota 已成立 |
| alignment | Stage 1/2 source-basis reader 与 amendment admission/retention | `source_basis_digest` 是完整 Goal intent revision;Stage 3 自动 commit 已成立 |
| managed | `turn run-once`、managed step、attached broker 及单执行器围栏已有实现 | 有界片段等于无人值守长程监督;每个注册 Agent 都有健康执行器 |
| 语义同伴协作 | 既有 Inbox/reply owner 承载不可变交办、同 Goal 同伴请求、结果消费及原对话读回;[真实 managed demo](../../../examples/collaboration-delivery/README.md#中文操作说明) | 控制器驱动阶段不证明自主调度、跨主机/Lark 同伴或完整 G1/M2–M4 |

### 已复核的问题

Expand Down
Loading
Loading