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
17 changes: 17 additions & 0 deletions apps/presentation/dashboard/src/data/chat-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@ export type ChatCapabilities = {
approval_policy: string;
todo_write: string;
goal_id: string | null;
manager?: {
scope: "owner_global";
model: string;
reasoning_effort: string;
runtime: {
schema_version: "manager_runtime_effective_profile_v0";
runtime_profile: "restricted" | "trusted_owner";
source: string;
configuration_revision: string;
standing_grant: string;
sandbox: string;
approval_policy: string;
tool_classes: string[];
status: string;
repair?: string;
};
};
streaming?: boolean;
resume?: boolean;
interrupt?: boolean;
Expand Down
32 changes: 31 additions & 1 deletion apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,23 @@ export const chatCapabilitiesSchema = z.object({
todo_write: z.string(),
goal_subagent_configuration: z.string().optional(),
goal_id: z.string().nullable(),
manager: z.object({
scope: z.literal("owner_global"),
model: z.string(),
reasoning_effort: z.string(),
runtime: z.object({
schema_version: z.literal("manager_runtime_effective_profile_v0"),
runtime_profile: z.enum(["restricted", "trusted_owner"]),
source: z.string(),
configuration_revision: z.string(),
standing_grant: z.string(),
sandbox: z.string(),
approval_policy: z.string(),
tool_classes: z.array(z.string()),
status: z.string(),
repair: z.string().optional(),
}),
}).optional(),
streaming: z.boolean().optional(),
resume: z.boolean().optional(),
interrupt: z.boolean().optional(),
Expand Down Expand Up @@ -518,6 +535,7 @@ export async function createChatSession(
ok: true;
resumed: boolean;
session_id: string;
session: ChatSessionSummary;
}>("/api/chat/sessions", {
method: "POST",
body: JSON.stringify({ goal_id: goalId, agent_id: agentId, mode, context_kind: contextKind }),
Expand Down Expand Up @@ -545,6 +563,17 @@ export type ChatSessionSummary = {
updated_at: string;
last_activity_at: string;
resumable: boolean;
manager_runtime?: ManagerRuntimeSessionReadback | null;
};

export type ManagerRuntimeSessionReadback = {
schema_version: "manager_runtime_session_readback_v0";
runtime_profile: "restricted" | "trusted_owner";
configuration_revision: string;
status: string;
sandbox: string;
standing_grant: string;
tool_classes: string[];
};

export type ChatVisibleMessage = {
Expand Down Expand Up @@ -1241,12 +1270,13 @@ const machineConfigurationBaseSchema = z.object({
}),
capability_catalog: capabilityConfigurationCatalogSchema,
changed_namespaces: z.array(z.string()).optional().default([]),
invalid_namespaces: z.array(z.string()).optional().default([]),
machine_configuration: machineConfigurationSchema.nullable().optional(),
});

export const machineConfigurationInspectionSchema = machineConfigurationBaseSchema.extend({
schema_version: z.literal("machine_configuration_inspection_v0"),
status: z.enum(["configured", "absent"]),
status: z.enum(["configured", "absent", "invalid"]),
revision: z.string(),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ type FieldCopy = Record<string, Readonly<{ description?: string; label: string }

const capabilityCopy: Record<WorkspaceLocale, Record<string, LocalizedCopy>> = {
en: {
manager_runtime: {
displayName: "Manager runtime",
description: "Selects the persistent host-tool profile used by owner manager conversations.",
},
todo_replan_cadence: { displayName: "Goal review cadence", description: "Configures the Goal review cadence." },
change_quality_qualification: {
displayName: "Change quality qualification",
Expand Down Expand Up @@ -60,6 +64,10 @@ const capabilityCopy: Record<WorkspaceLocale, Record<string, LocalizedCopy>> = {
},
},
"zh-CN": {
manager_runtime: {
displayName: "管家 Runtime",
description: "选择管家会话持续生效的宿主工具模式。",
},
todo_replan_cadence: { displayName: "Goal 复核周期", description: "配置 Goal 的复核周期。" },
change_quality_qualification: {
displayName: "变更质量验证",
Expand Down Expand Up @@ -111,6 +119,7 @@ const capabilityCopy: Record<WorkspaceLocale, Record<string, LocalizedCopy>> = {

const fieldCopy: Record<WorkspaceLocale, FieldCopy> = {
en: {
runtime_profile: { label: "Runtime profile", description: "Restricted keeps scoped LoopX reads only. Trusted owner enables normal host tools while protected operations retain separate checks." },
completed_todos: { label: "Completed Todos between Goal reviews", description: "Machine default or explicit Goal override, from 1 to 5." },
allowed_domains: { label: "Allowed responsibility domains", description: "Enter one bounded, public-safe domain per line." },
coordinator_agent_id: { label: "Coordinator Agent", description: "Use an already registered Agent id; leave blank to disable coordination." },
Expand All @@ -130,6 +139,7 @@ const fieldCopy: Record<WorkspaceLocale, FieldCopy> = {
enabled_agents: { label: "Enabled Goal Agents", description: "Enter one registered Goal-local Agent id per line. A private binding currently accepts exactly one Agent." },
},
"zh-CN": {
runtime_profile: { label: "运行模式", description: "restricted 仅使用受限 LoopX 读取;trusted_owner 开放常规宿主工具,但受保护操作仍单独校验。" },
completed_todos: { label: "两次 Goal 复核间的已完成 Todo 数", description: "可设置 1–5;机器默认值可被 Goal 显式覆盖。" },
allowed_domains: { label: "允许的职责域", description: "每行填写一个有边界、可公开的职责域。" },
coordinator_agent_id: { label: "协调 Agent", description: "填写一个已经注册的 Agent ID;留空表示关闭协调。" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import { useEffect, useRef, useState } from "react";
import { Bot, ChevronDown, Eye, Info, Menu, RefreshCw, SlidersHorizontal } from "lucide-react";

import { localizedGoalState, useWorkspaceI18n } from "./i18n";
import type { ManagerRuntimeSessionReadback } from "../../data/chat";
import type { WorkspaceAgentOption, WorkspaceGoal, WorkspaceGoalTab } from "./personal-workspace-model";
import { goalUsageLabel } from "./personal-workspace-model";
import { WorkspaceSelect } from "./workspace-select";

export function ChannelHeader({
agents,
managerChatOpen,
managerRuntime,
mobileNavigationOpen,
onOpenGoalCapabilities,
onOpenGoalDetail,
Expand All @@ -26,6 +28,7 @@ export function ChannelHeader({
}: {
agents: WorkspaceAgentOption[];
managerChatOpen?: boolean;
managerRuntime?: ManagerRuntimeSessionReadback | null;
mobileNavigationOpen?: boolean;
onOpenGoalCapabilities?: () => void;
onOpenGoalDetail?: () => void;
Expand Down Expand Up @@ -87,6 +90,17 @@ export function ChannelHeader({
<button aria-expanded={mobileNavigationOpen ?? false} aria-label={t("header.openGoalNavigation")} className="personal-icon-button personal-mobile-menu" onClick={onOpenNavigation} type="button"><Menu size={18} /></button>
<div className="personal-channel-title">
<h1>{selectedGoal?.title ?? t("header.manager")}</h1>
{!selectedGoal && managerRuntime ? (
<p>{managerRuntime.status === "ready"
? t("header.managerRuntime", {
profile: managerRuntime.runtime_profile,
sandbox: managerRuntime.sandbox,
})
: t("header.managerRuntimeFallback", {
profile: managerRuntime.runtime_profile,
sandbox: managerRuntime.sandbox,
})}</p>
) : null}
{selectedGoal ? <p>{selectedGoal.loadState ? t(selectedGoal.loadState === "error" ? "startup.goalError" : "startup.goalLoading") : `${selectedGoal.agentLaneCount && selectedGoal.agentLaneCount > 1
? t("header.workAgentCount", { count: selectedGoal.agentLaneCount })
: selectedGoal.agentLabel ?? selectedGoal.agentId} · ${(selectedGoal.loadState ? t(selectedGoal.loadState === "error" ? "startup.goalError" : "startup.goalLoading") : localizedGoalState(selectedGoal.state, locale))}${selectedGoalUsageLabel ? ` · ${selectedGoalUsageLabel}` : ""} · ${selectedGoal.nextSentence}`}</p> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ const en = {
"header.live": "Live",
"header.manager": "LoopX Manager",
"header.managerDescription": "Your personal workspace across Goals",
"header.managerRuntime": "{profile} · {sandbox}",
"header.managerRuntimeFallback": "Configuration invalid; fell back to {profile} · {sandbox}. Repair it in Machine capabilities.",
"header.managerOverview": "Overview",
"header.managerView": "Manager view",
"header.openGoalNavigation": "Open Goal navigation",
Expand Down Expand Up @@ -800,6 +802,8 @@ const en = {
"machine.backToForm": "Back to form",
"machine.jsonInvalid": "Enter one valid JSON object before previewing changes.",
"machine.loadError": "Machine configuration could not be loaded.",
"machine.invalidStoredConfiguration": "Stored machine configuration needs repair",
"machine.invalidStoredConfigurationDescription": "Stored values are hidden because they no longer match the installed contract. Review the affected capability, then Preview and Apply its replacement; unrelated namespaces remain unchanged.",
"machine.machinePolicy": "Machine policy",
"machine.namespaceCount": "Registered namespaces",
"machine.namespaces": "Configuration namespaces",
Expand Down Expand Up @@ -1390,6 +1394,8 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"header.live": "实时",
"header.manager": "LoopX 管家",
"header.managerDescription": "跨 Goal 的个人工作入口",
"header.managerRuntime": "{profile} · {sandbox}",
"header.managerRuntimeFallback": "配置无效,已回退到 {profile} · {sandbox};请在机器能力设置中修复。",
"header.managerOverview": "总览",
"header.managerView": "管家视图",
"header.openGoalNavigation": "打开 Goal 导航",
Expand Down Expand Up @@ -1787,6 +1793,8 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"machine.backToForm": "返回表单",
"machine.jsonInvalid": "请先填写一个合法的 JSON object,再预览变更。",
"machine.loadError": "无法读取机器配置。",
"machine.invalidStoredConfiguration": "已保存的机器配置需要修复",
"machine.invalidStoredConfigurationDescription": "已保存值不再符合当前契约,因此不会在这里显示。请检查已定位的能力,并通过“预览”和“应用”替换它;无关 namespace 保持不变。",
"machine.machinePolicy": "机器策略",
"machine.namespaceCount": "已注册 Namespace",
"machine.namespaces": "配置 Namespace",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,12 @@ export function MachineConfigurationSettings() {
const capabilities = useMemo(() => orderCapabilitiesForPresentation(
inspection?.capability_catalog.capabilities ?? [], locale,
), [inspection, locale]);
const invalidNamespace = inspection?.invalid_namespaces[0];
const selectedRaw = capabilities.find(
(capability) => capability.capability_id === selectedCapabilityId,
) ?? capabilities.find((capability) => canEditCapability(capability, "machine")) ?? capabilities[0];
) ?? (invalidNamespace ? capabilities.find(
(capability) => capability.machine_namespace === invalidNamespace,
) : undefined) ?? capabilities.find((capability) => canEditCapability(capability, "machine")) ?? capabilities[0];
const selected = selectedRaw ? localizeCapability(selectedRaw, locale) : undefined;
const selectedCurrent = currentConfiguration(inspection, selected);
const configured = Boolean(selected?.machine_namespace && selectedCurrent);
Expand Down Expand Up @@ -290,6 +293,13 @@ export function MachineConfigurationSettings() {
<p>{t("machine.liveDefaultDescription")}</p>
</details>

{inspection?.status === "invalid" ? (
<section className="personal-machine-error" data-testid="machine-invalid-repair" role="alert">
<strong>{t("machine.invalidStoredConfiguration")}</strong>
<p>{t("machine.invalidStoredConfigurationDescription")}</p>
</section>
) : null}

<div className="personal-capability-layout">
<CapabilityCatalogNavigation capabilities={capabilities} locale={locale} onSelect={setSelectedCapabilityId} scope="machine" selectedCapabilityId={selected.capability_id} t={t} />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,11 @@ assert.match(machineSettings, /applyMachineConfiguration\([\s\S]*preview\.plan_r
assert.match(machineSettings, /previewMachineConfigurationRollback\(/, "Machine settings preview rollback before execution");
assert.match(machineSettings, /liveDefaultDescription/, "Live defaults and Goal overrides are explained together");
assert.match(machineSettings, /inspection\?\.capability_catalog\.capabilities/, "Machine settings discover capabilities from the shared registry catalog");
assert.match(machineSettings, /inspection\?\.invalid_namespaces\[0\]/, "Invalid machine state identifies the affected namespace without reading its stored values");
assert.match(machineSettings, /machine-invalid-repair[\s\S]*role="alert"/, "Invalid machine state exposes a visible guided repair path");
assert.match(machineSettings, /capability\.machine_namespace === invalidNamespace/, "Invalid machine state opens the affected capability editor first");
assert.match(chatData, /status: z\.enum\(\["configured", "absent", "invalid"\]\)/, "Machine inspection accepts the safe invalid repair projection");
assert.match(chatData, /invalid_namespaces: z\.array\(z\.string\(\)\)/, "Machine inspection parses value-free invalid namespace IDs");
assert.match(machineSettings, /personal-capability-json-editor/, "Every machine-configurable capability keeps an advanced JSON fallback");
assert.match(machineSettings, /selected\.machine_namespace, desiredConfiguration/, "Preview targets the selected capability namespace");
assert.match(machineSettings, /previewMachineConfigurationRemoval\(selected\.machine_namespace\)/, "Configured capabilities expose a typed removal preview");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
transitionTypedAction,
type GoalRepositoryContext,
type LarkGoalConnection,
type ManagerRuntimeSessionReadback,
type TypedActionProposal,
} from "../../data/chat";

Expand Down Expand Up @@ -750,6 +751,7 @@ export function PersonalWorkspacePage({
agents = [{ agentId: "codex", available: true, capability: "代码与项目执行", label: "Codex" }],
callbacks = {},
goalArchiveLoadState = { error: null, phase: "ready" },
managerRuntime,
model,
readOnly = false,
selectedAgentId: controlledAgentId,
Expand All @@ -759,6 +761,7 @@ export function PersonalWorkspacePage({
agents?: WorkspaceAgentOption[];
callbacks?: PersonalWorkspaceCallbacks;
goalArchiveLoadState?: WorkspaceGoalArchiveLoadState;
managerRuntime?: ManagerRuntimeSessionReadback | null;
model: WorkspaceModel;
ownerLabel?: string;
readOnly?: boolean;
Expand Down Expand Up @@ -1825,6 +1828,7 @@ export function PersonalWorkspacePage({
<ChannelHeader
agents={agents}
managerChatOpen={managerChatOpen}
managerRuntime={managerRuntime}
mobileNavigationOpen={mobileSidebarOpen}
onOpenGoalCapabilities={selectedGoal ? () => setSelection({ goalId: selectedGoal.goalId, kind: "settings", tab: "capabilities" }) : undefined}
onOpenGoalDetail={selectedGoal && !selectedGoal.loadState ? () => setSelection({ item: selectedGoal, kind: "goal" }) : undefined}
Expand Down
20 changes: 20 additions & 0 deletions apps/presentation/dashboard/src/views/dashboard-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
type ChatSessionSnapshot,
type ChatSessionSummary,
type ChatImageAttachment,
type ManagerRuntimeSessionReadback,
type ProtectedActionProposal,
type TodoProposal,
} from "../data/chat";
Expand Down Expand Up @@ -1395,6 +1396,7 @@ function PersonalGoalHome({
trust_scope?: string;
}>>([]);
const [goalSubagentConfigurationEnabled, setGoalSubagentConfigurationEnabled] = useState(false);
const [managerRuntime, setManagerRuntime] = useState<ManagerRuntimeSessionReadback | null>(null);
const model = useMemo(() => {
const base = buildPersonalHomeModel(payload, rows, t, goalSubagentConfigurationEnabled);
if (!progress) return base;
Expand Down Expand Up @@ -1618,13 +1620,24 @@ function PersonalGoalHome({
if (readOnly) {
setRuntimeAgents([]);
setGoalSubagentConfigurationEnabled(false);
setManagerRuntime(null);
return;
}
let cancelled = false;
void fetchChatCapabilities()
.then((capabilities) => {
if (!cancelled) {
setRuntimeAgents(capabilities.adapters ?? []);
const runtime = capabilities.manager?.runtime;
setManagerRuntime(runtime ? {
schema_version: "manager_runtime_session_readback_v0",
runtime_profile: runtime.runtime_profile,
configuration_revision: runtime.configuration_revision,
status: runtime.status,
sandbox: runtime.sandbox,
standing_grant: runtime.standing_grant,
tool_classes: runtime.tool_classes,
} : null);
setGoalSubagentConfigurationEnabled(
capabilities.goal_subagent_configuration === "preview_locked",
);
Expand Down Expand Up @@ -1707,6 +1720,9 @@ function PersonalGoalHome({
contextKind,
);
if (cancelled) return;
if (contextKind === "manager" && created.session.manager_runtime) {
setManagerRuntime(created.session.manager_runtime);
}
sessionIds.current.set(sessionKey, created.session_id);
const activeSnapshot = history.snapshots.find(
(snapshot) => snapshot.session.session_id === created.session_id,
Expand Down Expand Up @@ -2079,6 +2095,9 @@ function PersonalGoalHome({
mode,
targetContextId === "manager" ? "manager" : "goal",
);
if (targetContextId === "manager" && session.session.manager_runtime) {
setManagerRuntime(session.session.manager_runtime);
}
sessionId = session.session_id;
sessionIds.current.set(sessionKey, sessionId);
recordRuntimeBinding(targetContextId, {
Expand Down Expand Up @@ -2795,6 +2814,7 @@ function PersonalGoalHome({
onStartNewRunSession: startNewManagerSession,
}}
goalArchiveLoadState={goalArchiveLoadState}
managerRuntime={managerRuntime}
model={workspaceModel}
readOnly={readOnly}
selectedAgentId={selectedAgent.agentId}
Expand Down
Loading