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
4 changes: 4 additions & 0 deletions apps/presentation/dashboard/src/data/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,10 @@ export const goalChannelNotificationRowSchema = z.object({
enabled: z.boolean().optional().default(false),
human_gate_auto_notify_enabled: z.boolean().optional().default(false),
blocked_notice_auto_notify_enabled: z.boolean().optional().default(false),
steward_notice_delivery: z.object({
pending_count: z.number(),
failed_count: z.number(),
}).optional(),
blocked_notice_delivery: z.object({
delivered_count: z.number(),
unverified_count: z.number(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const en = {
"drawer.applying": "Applying…",
"drawer.attentionBlocking": "Blocking an Agent",
"drawer.attentionWaiting": "Waiting for your decision",
"drawer.autoNotify": "Human-gate auto notifications",
"drawer.autoNotify": "Steward decision messages",
"drawer.branch": "Branch",
"drawer.closeDetail": "Close details and return to {context}",
"drawer.connected": "Connected",
Expand Down Expand Up @@ -673,8 +673,10 @@ const en = {
"lark.error.cliMissing": "lark-cli was not found. Install lark-cli and restart LoopX.",
"lark.error.cliStart": "lark-cli failed to start. Check the installation and restart LoopX.",
"lark.error.disconnect": "Disconnect failed",
"notifications.autoNotify": "Send automatically when your confirmation is required",
"notifications.blockedAutoNotify": "Send blocked task notices to this Goal Channel",
"notifications.autoNotify": "Send steward messages when your decision is needed",
"notifications.stewardHint": "Uses your configured steward model to explain the decision and impact. Local steward context is available without a channel.",
"notifications.stewardPending": "{count} messages have no verified delivery receipt. Check steward availability and channel delivery, then retry the refresh.",
"notifications.blockedAutoNotify": "Let the steward explain blocked work in this channel",
"notifications.blockedDelivery": "Blocked notices: {delivered} verified, {unverified} unverified, {resolved} resolved",
"notifications.bind": "Bind notification group",
"notifications.bindConfirm": "Bind “{goal}” to “{target}”. LoopX will verify that the bot is in the group and send a confirmation message.",
Expand All @@ -688,7 +690,7 @@ const en = {
"notifications.noTargets": "No notification groups are available. Group delivery uses a private bot identity; configure one from the terminal first:",
"notifications.notBound": "Not bound",
"notifications.recent": "Latest {time}",
"notifications.sentCount": "{count} sent",
"notifications.sentCount": "{count} delivery records",
"notifications.settings": "Goal notification bindings",
"notifications.setupFailed": "Could not update settings",
"notifications.title": "Feishu group notifications",
Expand Down Expand Up @@ -1391,7 +1393,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"drawer.applying": "正在应用…",
"drawer.attentionBlocking": "正在阻塞 Agent",
"drawer.attentionWaiting": "等待你的决定",
"drawer.autoNotify": "Human-gate 自动通知",
"drawer.autoNotify": "管家决策消息",
"drawer.branch": "分支",
"drawer.closeDetail": "关闭详情:返回{context}",
"drawer.connected": "已连接",
Expand Down Expand Up @@ -1923,8 +1925,10 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"lark.error.cliMissing": "未发现 lark-cli。请先安装 lark-cli,然后重新启动 LoopX。",
"lark.error.cliStart": "lark-cli 启动失败。请检查安装状态,然后重新启动 LoopX。",
"lark.error.disconnect": "解绑失败",
"notifications.autoNotify": "需要你确认时自动推送到群里",
"notifications.blockedAutoNotify": "任务受阻时推送到此目标群",
"notifications.autoNotify": "需要你决定时由管家生成消息并推送到群里",
"notifications.stewardHint": "使用已配置的管家模型说明决策与影响。没有 Channel 时,本地管家仍可读取这些事实。",
"notifications.stewardPending": "{count} 条消息尚未取得投递确认。请检查管家可用性与 Channel 投递状态,再重试刷新。",
"notifications.blockedAutoNotify": "任务受阻时由管家解释并推送到此目标群",
"notifications.blockedDelivery": "受阻通知:已核验 {delivered} 条,未核验 {unverified} 条,已解除 {resolved} 条",
"notifications.bind": "绑定到通知群",
"notifications.bindConfirm": "将把「{goal}」绑定到通知群「{target}」,绑定时会验证机器人在群内并发送一条确认消息。",
Expand All @@ -1938,7 +1942,7 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"notifications.noTargets": "还没有可用的通知群。通知群涉及机器人私有身份,请先在终端配置一次:",
"notifications.notBound": "未绑定",
"notifications.recent": "最近 {time}",
"notifications.sentCount": "已发送 {count} 条",
"notifications.sentCount": "投递记录 {count} 条",
"notifications.settings": "Goal 通知绑定",
"notifications.setupFailed": "设置失败",
"notifications.title": "飞书群通知",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export function GoalAutoNotifyToggle({
<span>{t(kind === "blocked_notice" ? "notifications.blockedAutoNotify" : "notifications.autoNotify")}</span>
{busy ? <Loader2 aria-hidden className="is-spinning" size={14} /> : null}
</label>
{kind === "human_gate" ? <p className="personal-notification-hint">{t("notifications.stewardHint")}</p> : null}
{kind === "human_gate" && (notification?.stewardNoticeDelivery?.pending_count ?? 0) > 0 ? (
<p className="personal-notification-error" role="status">{t("notifications.stewardPending", { count: notification!.stewardNoticeDelivery!.pending_count })}</p>
) : null}
{error ? <p className="personal-notification-error" role="alert">{error}</p> : null}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ export type WorkspaceGoalNotification = {
configured: boolean;
enabled: boolean;
humanGateAutoNotifyEnabled: boolean;
stewardNoticeDelivery?: { pending_count: number; failed_count: number };
blockedNoticeAutoNotifyEnabled?: boolean;
blockedNoticeDelivery?: { deliveredCount: number; unverifiedCount: number; resolvedCount: number };
lastNotifiedAt?: string | null;
Expand Down
1 change: 1 addition & 0 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,7 @@ function buildPersonalHomeModel(
configured: row.configured,
enabled: row.enabled,
humanGateAutoNotifyEnabled: row.human_gate_auto_notify_enabled,
stewardNoticeDelivery: row.steward_notice_delivery,
blockedNoticeAutoNotifyEnabled: row.blocked_notice_auto_notify_enabled,
blockedNoticeDelivery: row.blocked_notice_delivery ? {
deliveredCount: row.blocked_notice_delivery.delivered_count,
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 @@ -573,6 +573,20 @@ Qualify worker→worker through this adapter as the concrete second M2 consumer

### 5.14 Steward adoption of the reusable conversation work surface

The steward's local attention intake is independent of external channel setup.
It consumes the same canonical blocker/decision facts as the optional Goal
Channel, then synthesizes their effect on objectives, prior decisions and safe
continuation. One Todo's blocker and request form one subject, not two mechanical
alerts. Semantic grouping preserves distinct request identities and decision
terms; model prose cannot change authority or certify delivery. See the
[Goal Channel intake checkpoint](goal-channel-collaboration-v0.md#local-steward-intake-and-optional-channel-delivery).
The bounded implementation supplies facts to existing Turns and replaces channel
templates with configured, restricted steward synthesis. Verified gate messages
cover the matching blocker revision; generation failures remain pending. Local
autonomous wake, change/read/recovery receipts and sustained model quality remain
Stage 2/R3 work. The external synthesis transcript is isolated from live owner Turns.


The shared [conversation work surface](intelligent-review-presentation-surfaces-v0.md#88-reusable-conversation-work-surface) owns adaptive reports, truthful event presentation, Turn-scoped stop/steer, reconnect and cross-channel density for all LoopX conversations. This RFC applies those same rules to the steward's owner conversation; it owns recipient selection, receiver assessment and the original-route return. A manager-specific answer format or transport must not become a second presentation authority.

Routing uses §5.5 rather than a static Agent name list. For a product-design request addressed to the steward, first inspect authorized current Goal, registration, claimed work and fresh session reachability; then rank eligible receivers by responsibility and context, with model/profile fit and actual capacity as separate constraints. Explain the selected recipient or the exact gap. The receiver must acknowledge and assess the full corrected intent, then either work or defer with an owner and condition. The original conversation receives the assessment and final evidenced result through §5.6; the catalog, a stored inbox request and a spinner are three distinct incomplete states. This must pass with a real active worker plus stopped, registered-only, stale and model-mismatched decoys before advertising automatic delegation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,16 @@ Stage A 替换当前 Todo note,不提供不可变历史版本或私有 memory

### 5.14 管家接入可复用的对话工作界面

管家的本地注意力接收独立于外部 Channel 配置,与可选 Goal Channel 消费相同的
canonical 阻塞/决策事实,再结合目标、已有决定和安全续接汇总。同一 Todo 的
阻塞与请求形成一个对象,避免机械拆成两条;语义合并须保留独立请求身份与条款。
模型文字不改变授权、不证明送达。见 [Goal Channel 接收 checkpoint](goal-channel-collaboration-v0.zh-CN.md#本地管家接收与可选-channel-投递)。
有界实现向现有 Turn 提供事实,并将 Channel 模板替换为已配置、restricted 管家生成。
已核验 gate 消息覆盖对应 blocker revision,生成失败保持待处理;外部生成 transcript
独立于 live owner Turn。本地自动唤醒、变化 / 已读 / 恢复回执及持续模型质量仍归
Stage 2 / R3。


共用的[对话工作界面](intelligent-review-presentation-surfaces-v0.zh-CN.md#88-可复用的对话工作界面)拥有适应问题的报告、真实事件展示、Turn 级停止/纠偏、重连与跨渠道展示密度,适用于所有 LoopX 对话。本文将同一规则接入管家与主人的对话,负责接收者选择、评估及原路回传。管家专属的回答模板或传输方式不应成为第二套展示权威。

路由沿用 §5.5,而非固定 Agent 名单。管家收到产品设计请求时,先查看获授权的当前 Goal、注册、已认领工作及新鲜的 session 可达性;再按职责与上下文选择合格接收者,单独约束模型/profile 适配和实际容量。说明选择理由或真实缺口。接收者要确认、评估包含历史修正的完整意图,随后执行或明确延期条件和负责人。通过 §5.6 将评估与有证据的最终结果送回原对话;候选列表、已存入收件箱、正在处理是三种不同的未完成状态。对真实活跃 worker 与已停止、仅注册、过期、模型不适配的干扰候选都验收后,才能宣传自动委派。
Expand Down
82 changes: 72 additions & 10 deletions docs/architecture/rfcs/goal-channel-collaboration-v0.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,19 +264,18 @@ and interaction-contract surface:
- `user_todo_summary`;
- `user_gate_notification_cooldown`.

The message includes:

- goal label and short objective;
- concrete gate question;
- up to three user-gate or user-action todos;
- expected reply format;
- Kanban link or channel control link;
- next safe action while waiting, if any.
The steward explains the selected decision, its effect on the Goal and a useful
next step. Up to three complete selected requests retain their exact references,
scope and evidence; incomplete requests remain explicit gaps. It may combine a
related blocker with the decision, but does not broaden approval. Kanban links
remain available when useful. This replaces the fixed `Action required / Decision
requests` template; selection, cooldown and provider permissions retain their owners.

It excludes local paths, raw active state, private logs, credentials, message
ids, and raw provider payloads.

Automatic delivery is disabled by default. After Goal Channel setup, preview
New Goal Channel setup enables human-gate delivery by default; existing bindings
keep their recorded setting. Blocked-notice delivery stays default-off. Preview
and then enable it explicitly:

```bash
Expand Down Expand Up @@ -352,6 +351,68 @@ extension became unavailable. The latter fails closed with a retryable
`extension_unavailable` postcondition; the marker contains no provider ids,
credentials, channel metadata, or raw payloads.

### Local steward intake and optional channel delivery

The private local steward is the default attention consumer for its permitted
Goals. An absent Lark connection, disabled external delivery or failed transport
cannot hide a canonical blocker or owner request. Goal Chat uses the same facts within
its selected Goal; external audiences retain their exact grants. Intake grants
neither publication nor execution authority.

Compose one subject from a Todo's blocker and owner request, retaining concrete
decision terms. “The owner should know” and “the owner must act” remain separate.
Reuse the blocked-transition builder and shared TS decision/attention projection;
keep provider addressing, send/readback and private receipts in Lark. A channel
switch changes transport receipts, not source visibility. Model rewording is not
a source revision; source inspection is not proof the owner was notified.

The steward relates a material change to the objective, prior decisions and
independent work, groups related causes and recommends a useful next step.
Agent-owned recovery stays background work. Owner decisions retain their object,
terms, evidence and inaction consequence; unknown or redacted facts remain gaps.
Do not replay each transition mechanically or turn a blocker into a new approval.
Use the configured conversation runtime for synthesis; the model does not own
eligibility, authorization or receipt transitions.

**Implementation checkpoint.** Existing steward/Goal Turns read current canonical
attention without a Channel or run history. The shared TS owner coalesces a Todo's
blocker and decision, prioritizes owner action and discloses omitted subjects.
The whole Turn includes at most twelve subjects; per-Goal coverage retains the
omitted count, and the existing scoped Todo read supplies complete remaining facts.
Python supplies canonical I/O and public-safe fields, with no second selection rule.

The existing human-gate and blocked-notice senders now use one steward synthesis
adapter instead of separate message templates. An admitted external notification
uses the configured steward executor/model in an isolated, restricted Chat Turn
(startup up to 30 seconds, reasoning up to 90 seconds). Read authority is limited
to this Goal and audience; no host, delegation or publication grant is added.
Canonical request content, lifecycle, blocker revisions and referenced continuation
facts are checked before and after synthesis. Full canonical reads remain required;
unrelated Todo updates or creation do not invalidate the selected notice. Exact
request references protect reply routing: fresh and cached bodies
must contain each complete identifier as a whole token. Punctuation and Markdown
may surround it; a prefixed or suffixed identifier does not satisfy this delivery
obligation. Unknown execution or fallback remains unknown; advice is not proof
that a worker is running.

The generated body is saved in the existing private effect receipt before send;
retries reuse the exact text and provider key. A verified gate message records the
blocker revisions it covered so the separate blocker adapter does not send the
same fact again. Failed generation remains pending and never falls back to a
mechanical template; the existing frontend settings describe model usage and show
unverified delivery. Legacy verified receipts stay quiet. Legacy ambiguous blocker
receipts without a saved body require reconciliation rather than generating new
text under an already attempted provider key.

No new inbox, scheduler or notification store is introduced. Default local intake
means evidence in the next existing Turn, not autonomous presentation or a read
receipt. Local material-delta wake, budgeted proactive synthesis, read/recovery
acknowledgments, cross-Goal semantic batching and sustained quality remain open
under presentation Stage 2/R3. One live synthetic Codex synthesis qualifies the
model path; it does not establish long-running notification quality or a live
Lark deployment. The isolated external transcript does not share a live owner
session or claim full conversation continuity.

## Command Contract

Each effectful command returns a compact packet:
Expand Down Expand Up @@ -447,7 +508,8 @@ The first slice must prove:
- a Goal Control message is sent, pinned, and readback verified;
- human-gate notification respects cooldown and idempotency;
- repeated notification retries do not duplicate visible messages;
- automatic delivery is disabled by default and can be suppressed per refresh;
- new-channel human-gate delivery defaults on; blocked notices default off; both
retain explicit disable and per-refresh external-sink suppression;
- automatic delivery reads canonical quota and does not send for non-gate state;
- doctor reports missing bot auth, missing channel, missing Kanban, or stale
extension activation with typed blockers;
Expand Down
Loading
Loading