Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6dce03a
feat(goal-channel): compose revision-guarded local room work
LIHUA919 Sep 28, 2026
cce2d81
test(goal-channel): qualify claim and recovery on local stores
LIHUA919 Sep 28, 2026
09fc4d7
docs(goal-channel): record room work usage and qualification boundary
LIHUA919 Sep 28, 2026
5d331c3
fix(goal-channel): refresh census and scope the work schema name
LIHUA919 Sep 28, 2026
6738119
feat(goal-channel): add scoped room claim offers and callback recovery
LIHUA919 Sep 28, 2026
b6caaae
test(goal-channel): qualify authenticated callbacks and native dispatch
LIHUA919 Sep 28, 2026
81bc6bc
docs(goal-channel): document one-operation room claim grants
LIHUA919 Sep 28, 2026
bb2ff7a
Merge latest main into room claim composition
LIHUA919 Sep 28, 2026
a7627a2
fix(goal-channel): refuse unqualified exact-instance room grants
LIHUA919 Sep 28, 2026
a986785
Merge remote-tracking branch 'origin/main' into codex/im-ov-room-claim
LIHUA919 Sep 28, 2026
0eb0310
feat(goal-channel): restore private scoped context with authority rea…
LIHUA919 Sep 28, 2026
11ca722
test(goal-channel): qualify read-only reconnect and reference boundaries
LIHUA919 Sep 28, 2026
5c3ac84
docs(goal-channel): describe private context restore and remaining qu…
LIHUA919 Sep 28, 2026
2a77521
Merge current main and upstream build qualification repair
LIHUA919 Sep 28, 2026
742672c
fix(goal-channel): omit stale observations after restore scope failure
LIHUA919 Sep 29, 2026
9036ba8
fix(coordination): reject unqualified GoalRef on legacy claim contracts
LIHUA919 Sep 29, 2026
c46eaa3
fix(goal-channel): repair offer encoding and recall validation seam
LIHUA919 Sep 29, 2026
47ec7e1
test(goal-channel): qualify independent source CLI claim processes
LIHUA919 Sep 29, 2026
87ac097
docs(goal-channel): clarify local qualification and lifetime ownership
LIHUA919 Sep 29, 2026
2100830
Merge origin/main into codex/im-ov-room-claim
LIHUA919 Sep 30, 2026
86db3d3
Merge origin/main into codex/im-ov-room-claim
LIHUA919 Oct 2, 2026
a23d368
Merge origin/main into codex/im-ov-room-claim
LIHUA919 Oct 3, 2026
61b3ed7
Merge origin/main into codex/im-ov-room-claim
LIHUA919 Oct 10, 2026
784e41b
Merge origin/main into codex/im-ov-room-claim
LIHUA919 Oct 11, 2026
b9b1eb9
Merge main into goal-channel scoped claims
LIHUA919 Oct 12, 2026
41c6005
Merge main to adopt interrupted Python CI diagnostics
LIHUA919 Oct 13, 2026
ff80d2f
Merge main and retire the obsolete registry I/O snapshot
LIHUA919 Oct 14, 2026
53ef47e
Merge latest Windows runtime retirement diagnostics
LIHUA919 Oct 14, 2026
b443d3d
test(digest): pin the direction authority consumer
songoow Oct 14, 2026
1adf3b8
Merge main and preserve room-work qualification
LIHUA919 Oct 14, 2026
a5c8a87
Merge browser fixture recovery and shared Lark transport
LIHUA919 Oct 14, 2026
1717319
test(browser): reuse scoped backend forwarding for replan cadence
LIHUA919 Oct 14, 2026
72b1988
test(browser): bind cadence companions to the selected package
LIHUA919 Oct 14, 2026
bef0df3
Merge current browser forwarding and Chat startup composition
LIHUA919 Oct 15, 2026
21ff4ce
Merge latest main into PR #6431 for current CI validation
songoow Oct 15, 2026
4b07062
feat(goal-channel): expose scoped room work in the packaged workspace
LIHUA919 Oct 15, 2026
763ea5e
test(goal-channel): qualify packaged claim recovery and scope guards
LIHUA919 Oct 15, 2026
6ea1172
docs(goal-channel): describe packaged room work and qualification limits
LIHUA919 Oct 15, 2026
e5a7361
fix(tests): keep room-work fixture imports and cleanup lint clean
LIHUA919 Oct 15, 2026
842ae12
fix(goal-channel): pin callback offer binding through claim admission
LIHUA919 Oct 15, 2026
47d2b10
Merge main for the verified legacy extension status repair
LIHUA919 Oct 15, 2026
991e9a1
Merge existing PR #6431 digest consumer repair into qualification branch
LIHUA919 Oct 15, 2026
8b6592a
Merge main with the accepted digest and extension lifecycle repairs
LIHUA919 Oct 15, 2026
081c207
Merge main for existing browser and validation prerequisite repairs
LIHUA919 Oct 15, 2026
b7b2e1c
fix(coordination): retain room source guards through canonical claim
LIHUA919 Oct 15, 2026
59cdd8b
refactor(presentation): place room work HTTP in its existing owner
LIHUA919 Oct 15, 2026
57d2139
docs(goal-channel): specify retained source scope at claim commit
LIHUA919 Oct 15, 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
31 changes: 31 additions & 0 deletions apps/presentation/dashboard/src/data/room-work.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {z} from "zod";
import {requestJson} from "./chat";

const ownerSchema = z.object({todo_id: z.string(), current_owner: z.string().nullable(), current_claim_matches_actor: z.boolean()});
const roomWorkSchema = z.object({
ok: z.boolean(), status: z.string(), actor_id: z.string(), connection_id: z.string(), scope_revision: z.string(),
canonical_claim_accepted: z.boolean(), readback_verified: z.boolean(), execution_authority_granted: z.literal(false), claim_available: z.boolean(),
projection: z.object({source_revision: z.string(), generated_at: z.string(),
selected_todo: z.object({todo_id: z.string()}).nullable(),
counts: z.object({unclaimed: z.number(), user_gates: z.number()}),
inspected_todo: ownerSchema.nullable().optional(),
}).optional(),
receipt: ownerSchema.extend({receipt_id: z.string(), acceptance: z.string(),
current_owner: z.string().nullable().optional(), current_claim_matches_actor: z.boolean().optional(),
}).optional(),
});
export type RoomWork = z.infer<typeof roomWorkSchema>;
export const roomClaimIntentSchema = z.object({
goal_id: z.string(), connection_id: z.string(), scope_revision: z.string(),
todo_id: z.string(), expected_revision: z.string(), idempotency_key: z.string(),
});
export type RoomClaimIntent = z.infer<typeof roomClaimIntentSchema>;

export async function fetchRoomWork(goalId: string, connectionId: string, todoId?: string) {
const query = new URLSearchParams({goal_id: goalId, connection_id: connectionId});
if (todoId) query.set("todo_id", todoId);
return roomWorkSchema.parse(await requestJson(`/api/chat/lark/room-work?${query}`));
}
export async function claimRoomWork(intent: RoomClaimIntent) {
return roomWorkSchema.parse(await requestJson("/api/chat/lark/room-work", {method: "POST", body: JSON.stringify(intent)}));
}
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,32 @@ const en = {
"lark.captureAll": "All messages in this Goal topic",
"lark.captureScope": "Capture scope",
"lark.captureScopeDescription": "Controls which Topic messages enter LoopX without expanding Agent permissions.",
"roomWork.readOnly": "Read-only: enable the existing Lark Goal Channel extension and permission before claiming.",
"roomWork.details": "Claim details",
"roomWork.receipt": "Receipt",
"roomWork.messageVerified": "Room receipt verified",
"roomWork.title": "Room work",
"roomWork.close": "Close room work",
"roomWork.unavailable": "Room work is unavailable. Check this connection and task service, then refresh.",
"roomWork.storageUnavailable": "This browser cannot preserve a recovery request. Enable session storage before claiming.",
"roomWork.scopeChanged": "This connection changed. Refresh before making a new claim.",
"roomWork.conflict": "Work changed since this view was read. Refresh before claiming again.",
"roomWork.deliveryPending": "The claim was accepted; its room message still needs recovery.",
"roomWork.unknown": "The response is uncertain. Reconcile this same request before claiming more work.",
"roomWork.boundary": "A claim records ownership only. Execution still requires current gates, quota and any lease. Private recall stays private.",
"roomWork.loading": "Reading current room work…",
"roomWork.agent": "Agent",
"roomWork.available": "Available tasks",
"roomWork.gates": "User decisions",
"roomWork.task": "Task",
"roomWork.none": "None",
"roomWork.observed": "Observed at",
"roomWork.accepted": "Historical claim accepted",
"roomWork.owner": "Current owner at last readback",
"roomWork.pending": "A request is awaiting reconciliation. Recovery reuses its original key.",
"roomWork.refresh": "Refresh current state",
"roomWork.recover": "Reconcile this claim",
"roomWork.claim": "Claim task and send receipt",
"lark.closeConnection": "Close connection dialog",
"lark.closeCreate": "Close create dialog",
"lark.closeSettings": "Close Lark settings",
Expand Down Expand Up @@ -2179,6 +2205,32 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"lark.captureAll": "接收此 Goal Topic 中的所有消息",
"lark.captureScope": "接收范围",
"lark.captureScopeDescription": "决定哪些 Topic 消息会进入 LoopX;不会扩大 Agent 权限。",
"roomWork.readOnly": "当前只读:领取前需启用已有飞书 Goal Channel 扩展及权限。",
"roomWork.details": "领取详情",
"roomWork.receipt": "凭据",
"roomWork.messageVerified": "房间凭据已确认",
"roomWork.title": "房间工作",
"roomWork.close": "关闭房间工作",
"roomWork.unavailable": "房间工作暂不可用。请检查此连接及任务服务后刷新。",
"roomWork.storageUnavailable": "浏览器无法保存恢复请求。请启用会话存储后再领取。",
"roomWork.scopeChanged": "连接已变化,请刷新后再发起新的领取。",
"roomWork.conflict": "工作状态已变化,请刷新后再领取。",
"roomWork.deliveryPending": "任务已领取,房间消息仍需恢复。",
"roomWork.unknown": "响应结果不确定,请先核对此次请求,再领取其他工作。",
"roomWork.boundary": "领取只确认归属,不会启动执行或发送私有回忆;执行仍需原有授权、配额及租约。",
"roomWork.loading": "正在读取当前房间工作…",
"roomWork.agent": "Agent",
"roomWork.available": "可领取任务",
"roomWork.gates": "用户待决事项",
"roomWork.task": "任务",
"roomWork.none": "无",
"roomWork.observed": "观察时间",
"roomWork.accepted": "历史领取已接受",
"roomWork.owner": "最近回读的当前归属",
"roomWork.pending": "有一项请求等待核对,恢复会沿用原请求键。",
"roomWork.refresh": "刷新当前状态",
"roomWork.recover": "核对此次领取",
"roomWork.claim": "领取任务并发送凭据",
"lark.closeConnection": "关闭连接弹窗",
"lark.closeCreate": "关闭创建弹窗",
"lark.closeSettings": "关闭 Lark 设置",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {PrivateConversationPanel} from "./private-conversation-panel";
import {RoomWorkPanel} from "./room-work-panel";
import { useEffect, useMemo, useRef, useState } from "react";
import {
Bot,
Expand Down Expand Up @@ -207,6 +208,8 @@ export function LarkSettingsPage({
}) {
const { t } = useWorkspaceI18n();
const [tab, setTab] = useState<Tab>("connections");
const [workConnection, setWorkConnection] = useState<LarkGoalConnection | null>(null);
const workTrigger = useRef<HTMLButtonElement | null>(null);
const [apps, setApps] = useState<LarkApp[]>([]);
const [connections, setConnections] = useState<LarkGoalConnection[]>([]);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -594,6 +597,7 @@ export function LarkSettingsPage({
<span>{connection.conversation_kind === "manager" ? t(connection.turn_trigger === "human_messages" ? "lark.triggerHuman" : "lark.triggerAddressed") : connection.capture_scope === "addressed_only" ? t("lark.mentionsOnly") : t("lark.allTopicMessages")}</span>
<span><strong>{connection.conversation_kind === "manager" ? t("lark.managerConversation") : ingressPresentation(connection.ingress_mode, t).label}</strong><small>{connection.conversation_kind === "manager" ? t("lark.managerConversationDescription") : connection.agent_id ?? ingressPresentation(connection.ingress_mode, t).detail}</small></span>
<span className="personal-lark-row-actions">
{connection.agent_id && connection.conversation_kind !== "manager" ? <button type="button" onClick={event => {workTrigger.current = event.currentTarget; setWorkConnection(connection);}}>{t("roomWork.title")}</button> : null}
<button aria-label={t("lark.settingsConfigure", { goal: connection.goal_title })} onClick={() => openConnectionEditor(connection)} type="button"><Settings2 size={15} /></button>
<button aria-label={t("lark.settingsDisconnect", { goal: connection.goal_title })} className={disconnectConnectionId === connection.connection_id ? "is-confirm" : ""} onClick={() => void disconnect(connection.goal_id, connection.connection_id)} type="button"><Unlink size={15} />{disconnectConnectionId === connection.connection_id ? t("common.confirm") : null}</button>
</span>
Expand All @@ -604,6 +608,7 @@ export function LarkSettingsPage({
</div>
) : null}

{workConnection ? <RoomWorkPanel key={workConnection.connection_id} connection={workConnection} triggerElement={workTrigger.current} onClose={() => setWorkConnection(null)} /> : null}
{modalOpen ? (
<div className="personal-lark-modal-backdrop" role="presentation">
<section aria-labelledby="connect-lark-title" aria-modal="true" className="personal-lark-modal" role="dialog">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import "./room-work.css";
import {useEffect, useRef, useState} from "react";
import {claimRoomWork, fetchRoomWork, roomClaimIntentSchema, type RoomClaimIntent, type RoomWork} from "../../data/room-work";
import {ChatApiError, type LarkGoalConnection} from "../../data/chat";
import {useWorkspaceI18n} from "./i18n";

/** Browser storage holds only an uncertain request tuple, never work authority. */
export function RoomWorkPanel({connection, onClose, triggerElement}: {connection: LarkGoalConnection; onClose: () => void; triggerElement: HTMLElement | null}) {
const {t} = useWorkspaceI18n();
const storageKey = `loopx.room-work:${connection.goal_id}:${connection.connection_id}`;
const [intent, setIntent] = useState<RoomClaimIntent | null>(() => {
try {
const saved = roomClaimIntentSchema.safeParse(JSON.parse(sessionStorage.getItem(storageKey) ?? "null"));
return saved.success && saved.data.goal_id === connection.goal_id && saved.data.connection_id === connection.connection_id ? saved.data : null;
} catch {return null;}
});
const [work, setWork] = useState<RoomWork | null>(null);
const [result, setResult] = useState<RoomWork | null>(null);
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const inFlight = useRef(false);
const alive = useRef(true);
const dialog = useRef<HTMLElement>(null);
useEffect(() => {
const prior = triggerElement ?? document.activeElement;
dialog.current?.focus();
function keydown(event: KeyboardEvent) {
if (event.key === "Escape") {event.preventDefault(); event.stopPropagation(); onClose();}
if (event.key !== "Tab") return;
const controls = Array.from(dialog.current?.querySelectorAll<HTMLElement>("button:not(:disabled), summary") ?? []);
const first = controls[0], last = controls.at(-1);
const outside = !dialog.current?.contains(document.activeElement);
if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog.current || outside)) {event.preventDefault(); last?.focus();}
else if (!event.shiftKey && (document.activeElement === last || outside)) {event.preventDefault(); first?.focus();}
}
document.addEventListener("keydown", keydown);
return () => {
document.removeEventListener("keydown", keydown);
if (prior instanceof HTMLElement && prior.isConnected) prior.focus();
};
}, []);

async function refresh() {
if (inFlight.current) return;
inFlight.current = true; setBusy(true); setError("");
try {
const next = await fetchRoomWork(connection.goal_id, connection.connection_id, intent?.todo_id ?? result?.receipt?.todo_id);
if (alive.current) {setWork(next); if (!next.ok) setError(t("roomWork.unavailable"));}
} catch {if (alive.current) {setWork(null); setError(t("roomWork.unavailable"));}}
finally {inFlight.current = false; if (alive.current) setBusy(false);}
}
useEffect(() => {alive.current = true; void refresh(); return () => {alive.current = false;};}, []);

async function claim() {
if (inFlight.current) return;
const selected = work?.projection?.selected_todo;
if (!intent && (!work?.ok || !work.claim_available || !selected)) return;
const request = intent ?? {goal_id: connection.goal_id, connection_id: connection.connection_id,
scope_revision: work!.scope_revision, todo_id: selected!.todo_id,
expected_revision: work!.projection!.source_revision, idempotency_key: crypto.randomUUID()};
// Preserve the original tuple before the effect; a lost response or reload
// can only retry this exact request, never generate another claim key.
try {sessionStorage.setItem(storageKey, JSON.stringify(request));}
catch {setError(t("roomWork.storageUnavailable")); return;}
setIntent(request); inFlight.current = true; setBusy(true); setError("");
try {
const next = await claimRoomWork(request);
if (!alive.current) return;
setResult(next); setWork(next);
if (next.ok || next.status === "conflict" || next.status === "rejected") {
sessionStorage.removeItem(storageKey); setIntent(null);
}
if (!next.ok) setError(t(next.status === "conflict" ? "roomWork.conflict" : next.canonical_claim_accepted ? "roomWork.deliveryPending" : "roomWork.unavailable"));
} catch (cause) {
if (!alive.current) return;
if (cause instanceof ChatApiError && [400, 409].includes(Number(cause.payload.http_status))) {
sessionStorage.removeItem(storageKey); setIntent(null); setWork(null);
setError(t("roomWork.scopeChanged"));
} else setError(t("roomWork.unknown"));
} finally {inFlight.current = false; if (alive.current) setBusy(false);}
}
const observed = !work?.projection ? null : Object.hasOwn(work.projection, "inspected_todo")
? work.projection.inspected_todo : result?.receipt;
return <div className="personal-lark-modal-backdrop" role="presentation">
<section ref={dialog} tabIndex={-1} aria-labelledby="room-work-title" aria-modal="true" className="personal-lark-modal personal-room-work" role="dialog">
<header><h2 id="room-work-title">{t("roomWork.title")}</h2><button type="button" onClick={onClose} aria-label={t("roomWork.close")}>×</button></header>
<p>{connection.chat_name} · {connection.goal_title}</p>
<p>{t("roomWork.agent")}: {work?.actor_id ?? connection.agent_id}</p>
<p className="personal-room-work-boundary">{t("roomWork.boundary")}</p>
{work?.ok && !work.claim_available && !result ? <p role="status">{t("roomWork.readOnly")}</p> : null}
{error ? <p role="alert">{error}</p> : null}
{busy ? <p role="status">{t("roomWork.loading")}</p> : null}
{work?.projection ? <dl className="personal-room-work-counts"><div><dt>{t("roomWork.available")}</dt><dd>{work.projection.counts.unclaimed}</dd></div>
<div><dt>{t("roomWork.gates")}</dt><dd>{work.projection.counts.user_gates}</dd></div></dl> : null}
{result?.canonical_claim_accepted && result.receipt ? <p role="status">{t("roomWork.accepted")}</p> : null}
{result?.readback_verified ? <p role="status">{t("roomWork.messageVerified")}</p> : null}
{observed ? <p>{t("roomWork.owner")}: {observed.current_owner === undefined ? t("roomWork.unavailable") : observed.current_owner ?? t("roomWork.none")}</p> : null}
{intent ? <p>{t("roomWork.pending")}</p> : null}
{work?.projection ? <details><summary>{t("roomWork.details")}</summary><dl>
<dt>{t("roomWork.task")}</dt><dd>{intent?.todo_id ?? work.projection.inspected_todo?.todo_id ?? result?.receipt?.todo_id ?? work.projection.selected_todo?.todo_id ?? t("roomWork.none")}</dd>
<dt>{t("roomWork.observed")}</dt><dd>{work.projection.generated_at}</dd>
{result?.receipt ? <><dt>{t("roomWork.receipt")}</dt><dd>{result.receipt.receipt_id}</dd></> : null}
</dl></details> : null}
<footer><button type="button" disabled={busy} onClick={() => void refresh()}>{t("roomWork.refresh")}</button>
{intent || work?.projection?.selected_todo ? <button className="personal-primary-action" type="button" disabled={busy || (!intent && (!work?.ok || !work.claim_available))} onClick={() => void claim()}>{t(intent ? "roomWork.recover" : "roomWork.claim")}</button> : null}</footer>
</section>
</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Scoped to the existing connection dialog; shared theme tokens own presentation. */
.personal-room-work p, .personal-room-work dd { overflow-wrap: anywhere; }
.personal-room-work { color: var(--pw-text); background: var(--pw-card); }
.personal-room-work dd { margin-inline-start: 0; }
.personal-room-work footer { flex-wrap: wrap; }

.personal-room-work .personal-room-work-boundary { color: var(--pw-muted); font-size: var(--text-caption); }
.personal-room-work .personal-room-work-counts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.personal-room-work-counts dt { color: var(--pw-muted); font-size: var(--text-caption); }
.personal-room-work .personal-room-work-counts dd { font-size: var(--text-heading); line-height: var(--leading-heading); font-variant-numeric: tabular-nums; }
.personal-room-work header button { min-height: 44px; min-width: 44px; }
Loading
Loading