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
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ function ConfigurationFieldControl({ copy, field, id, onChange, value, timezone
);
}
const numeric = field.input_kind === "number";
const modelSuggestions = field.key === "executor_model"
? ["gpt-6-sol", "gpt-6-luna", "gpt-6-astra"] : [];
return (
<label htmlFor={id}>
<span>{label}</span>
<input
id={id}
list={modelSuggestions.length ? `${id}-suggestions` : undefined}
max={field.maximum}
min={field.minimum}
onChange={onChange ? (event) => onChange(field.key, numeric ? Number(event.target.value) : event.target.value) : undefined}
Expand All @@ -67,6 +70,7 @@ function ConfigurationFieldControl({ copy, field, id, onChange, value, timezone
type={numeric ? "number" : "text"}
value={typeof value === "number" || typeof value === "string" ? value : ""}
/>
{modelSuggestions.length ? <datalist id={`${id}-suggestions`}>{modelSuggestions.map((model) => <option key={model} value={model} />)}</datalist> : null}
</label>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ type FieldCopy = Record<string, Readonly<{ description?: string; label: string }
const capabilityCopy: Record<WorkspaceLocale, Record<string, LocalizedCopy>> = {
en: {
manager_runtime: {
displayName: "Manager runtime",
displayName: "Runtime",
description: "Selects the persistent host-tool profile used by owner manager conversations.",
},
steward_executor: {
displayName: "Steward executor",
displayName: "Model and executor",
description: "Guides the steward executor, model, and selection boundary for this machine. A pinned route blocks substitution; a flexible pool permits only authorized fallback.",
},
todo_replan_cadence: { displayName: "Goal review cadence", description: "Configures the Goal review cadence." },
Expand Down Expand Up @@ -77,11 +77,11 @@ const capabilityCopy: Record<WorkspaceLocale, Record<string, LocalizedCopy>> = {
},
"zh-CN": {
manager_runtime: {
displayName: "管家 Runtime",
displayName: "运行环境",
description: "选择管家会话持续生效的宿主工具模式。",
},
steward_executor: {
displayName: "管家执行器",
displayName: "模型与执行器",
description: "配置本机管家的执行器、模型与选择边界;锁定路径禁止替代,灵活池只允许在已授权范围内回退。",
},
todo_replan_cadence: { displayName: "Goal 复核周期", description: "配置 Goal 的复核周期。" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ export function CapabilityCatalogNavigation({
onSelect,
scope,
selectedCapabilityId,
showScope = true,
t,
}: Readonly<{
capabilities: CapabilityDescriptor[];
locale: WorkspaceLocale;
onSelect: (capabilityId: string) => void;
scope: "goal" | "machine";
selectedCapabilityId: string;
showScope?: boolean;
t: WorkspaceTranslate;
}>) {
return (
Expand All @@ -101,9 +103,9 @@ export function CapabilityCatalogNavigation({
<span>
<strong>{capability.display_name}</strong>
</span>
<em>{t(capability.available_scopes.includes(scope)
{showScope ? <em>{t(capability.available_scopes.includes(scope)
? scope === "goal" ? "capabilities.goalScope" : "capabilities.machineScope"
: scope === "machine" ? "capabilities.goalScope" : "capabilities.machineScope")}</em>
: scope === "machine" ? "capabilities.goalScope" : "capabilities.machineScope")}</em> : null}
</button>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,9 +971,12 @@ const en = {
"settings.eyebrow": "Workspace preferences",
"settings.general": "General",
"settings.goalConnections": "Goal connections",
"settings.agentGroup": "Agents and models",
"settings.workspaceGroup": "Workspace",
"settings.steward": "Steward",
"settings.language": "Language",
"settings.modelProvider": "Model provider",
"settings.globalCapabilities": "Global capabilities",
"settings.globalCapabilities": "Capability Center",
"settings.languageDescription": "Choose the language used by the LoopX desktop workspace.",
"settings.languageEnglishDescription": "Use English for navigation, settings, and workspace controls.",
"settings.languageEnglish": "English",
Expand Down Expand Up @@ -2082,9 +2085,12 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"settings.eyebrow": "工作区偏好",
"settings.general": "通用",
"settings.goalConnections": "Goal 连接",
"settings.agentGroup": "Agent 与模型",
"settings.workspaceGroup": "工作区",
"settings.steward": "管家",
"settings.language": "语言",
"settings.modelProvider": "模型 Provider 配置",
"settings.globalCapabilities": "全局能力配置",
"settings.globalCapabilities": "能力中心",
"settings.languageDescription": "选择 LoopX Desktop 工作区使用的界面语言。",
"settings.languageEnglishDescription": "使用英文显示导航、设置和工作区控件。",
"settings.languageEnglish": "English",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function shortRevision(value: string | undefined) {
return value.replace(/^sha256:/, "").slice(0, 12);
}

export function MachineConfigurationSettings() {
export function MachineConfigurationSettings({ section }: { section: "steward" | "other" }) {
const { locale, t } = useWorkspaceI18n();
const [inspection, setInspection] = useState<MachineConfigurationInspection | null>(null);
const [selectedCapabilityId, setSelectedCapabilityId] = useState("");
Expand All @@ -117,14 +117,21 @@ export function MachineConfigurationSettings() {
const [notice, setNotice] = useState<string | null>(null);

const capabilities = useMemo(() => orderCapabilitiesForPresentation(
inspection?.capability_catalog.capabilities ?? [], locale,
), [inspection, locale]);
(inspection?.capability_catalog.capabilities ?? []).filter((capability) =>
capability.available_scopes.includes("machine")
&& (section === "steward"
? capability.capability_id === "steward_executor" || capability.capability_id === "manager_runtime"
: capability.capability_id !== "steward_executor" && capability.capability_id !== "manager_runtime")),
locale,
), [inspection, locale, section]);
const invalidNamespace = inspection?.invalid_namespaces[0];
const selectedRaw = capabilities.find(
(capability) => capability.capability_id === selectedCapabilityId,
) ?? (invalidNamespace ? capabilities.find(
(capability) => capability.machine_namespace === invalidNamespace,
) : undefined) ?? capabilities.find((capability) => canEditCapability(capability, "machine")) ?? capabilities[0];
) : undefined) ?? (section === "steward"
? capabilities.find((capability) => capability.capability_id === "steward_executor")
: 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 @@ -325,7 +332,7 @@ export function MachineConfigurationSettings() {
) : null}

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

<article aria-label={selected.display_name} className="personal-capability-detail" tabIndex={0}>
<CapabilityDetailHeader capability={selectedRaw} locale={locale}
Expand Down Expand Up @@ -360,6 +367,15 @@ export function MachineConfigurationSettings() {
</section>
) : null}

{selected.capability_id === "steward_executor" ? (
<section className="personal-capability-behavior-note">
<ShieldCheck aria-hidden size={18} />
<div><strong>{locale === "zh-CN" ? "管家模型与思考深度" : "Steward model and reasoning"}</strong><p>{locale === "zh-CN"
? "这里设置本机管家新会话的默认模型和思考深度。已有会话可能继续使用原来的分配;配置成功不代表正在运行的会话已切换。"
: "Choose the model and reasoning effort for new steward sessions on this machine. Existing sessions may retain their earlier allocation; saving a default does not switch a running session."}</p></div>
</section>
) : null}

{selected.capability_id === "pull_request_review" ? (
<section className="personal-capability-behavior-note">
<ShieldCheck aria-hidden size={18} />
Expand All @@ -377,7 +393,9 @@ export function MachineConfigurationSettings() {

{editorMode === "guided" ? (
<section className="personal-capability-field-summary">
<CapabilityConfigurationFields copy={localizedCapabilityFieldCopy(locale)} disabled={Boolean(busy)} editor={selected.configuration_editor} onChange={changeDraft} value={draft}
<CapabilityConfigurationFields copy={localizedCapabilityFieldCopy(locale)} disabled={Boolean(busy)} editor={selected.configuration_editor}
omitKeys={selected.capability_id === "steward_executor" && draft.selection_policy !== "flexible" ? ["eligible_endpoints"] : []}
onChange={changeDraft} value={draft}
enabledAction={<button className="personal-capability-edit-json" onClick={() => changeMode("json")} type="button"><Code2 aria-hidden size={14} />{t("machine.editJson")}</button>} />
{!editorValid ? <p className="personal-machine-validation" role="alert">{t("machine.requiredFields")}</p> : null}
</section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ export type WorkspaceDrawerSelection =
| { item: WorkspaceRun; kind: "run" }
| { item: WorkspaceOutput; kind: "output" }
| { item: WorkspaceActionPreview; kind: "proposal" }
| { goalId?: string; kind: "settings"; tab?: "appearance" | "capabilities" | "language" | "lark" | "machine" }
| { goalId?: string; kind: "settings"; tab?: "appearance" | "capabilities" | "language" | "lark" | "machine" | "steward" }
| {
item: WorkspaceSchedule;
kind: "schedule";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1909,7 +1909,7 @@ export function PersonalWorkspacePage({
goalNotifications={model.goalNotifications ?? []}
goals={workspaceGoals}
initialGoalId={selection?.kind === "settings" ? selection.goalId ?? selectedGoalId : selectedGoalId}
initialTab={selection?.kind === "settings" ? selection.tab ?? "lark" : "lark"}
initialTab={selection?.kind === "settings" ? selection.tab ?? (selectedGoalId ? "lark" : "steward") : "steward"}
onChanged={() => void refreshSettingsState()}
onClose={closeSettings}
onThemeChange={updateTheme}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from "react";
import { ArrowLeft, Check, Clock3, KeyRound, Languages, Palette, ServerCog, Settings2, SlidersHorizontal } from "lucide-react";
import { ArrowLeft, Bot, Check, Clock3, KeyRound, Languages, Palette, ServerCog, Settings2, SlidersHorizontal } from "lucide-react";

import type { WorkspaceLocale } from "./i18n";
import { useWorkspaceI18n } from "./i18n";
Expand All @@ -11,7 +11,7 @@ import { OperatorCredentialSettings } from "./operator-credential-settings";
import type { PersonalWorkspaceCallbacks, WorkspaceGoal, WorkspaceGoalNotification } from "./personal-workspace-model";
import type { WorkspaceTheme } from "./workspace-theme";

type WorkspaceSettingsTab = "provider" | "machine" | "capabilities" | "cadence" | "lark" | "appearance" | "language";
type WorkspaceSettingsTab = "steward" | "provider" | "machine" | "capabilities" | "cadence" | "lark" | "appearance" | "language";

const tabIcons: Record<WorkspaceSettingsTab, typeof Settings2> = {
appearance: Palette,
Expand All @@ -21,6 +21,7 @@ const tabIcons: Record<WorkspaceSettingsTab, typeof Settings2> = {
lark: Settings2,
machine: ServerCog,
provider: KeyRound,
steward: Bot,
};

export function WorkspaceSettingsPage({
Expand Down Expand Up @@ -67,18 +68,29 @@ export function WorkspaceSettingsPage({
observer.observe(navigation);
return () => observer.disconnect();
}, [tab]);
const tabs: Array<{ key: WorkspaceSettingsTab; label: string }> = [
...(initialGoalId ? [{ key: "capabilities" as const, label: t("capabilities.title") }] : []),
...(initialGoalId ? [{ key: "cadence" as const, label: t("cadence.title") }] : []),
// The model provider is one machine decision (which endpoint and key the
// operator credential holds); the capability catalog is another (which
// machine defaults every Goal inherits). They answer different questions
// and are edited on different surfaces, so they are separate categories.
{ key: "provider", label: t("settings.modelProvider") },
{ key: "machine", label: t("settings.globalCapabilities") },
{ key: "lark", label: "Lark" },
{ key: "appearance", label: t("settings.appearance") },
{ key: "language", label: t("settings.language") },
const tabGroups: Array<{ label: string; tabs: Array<{ key: WorkspaceSettingsTab; label: string }> }> = [
{
label: t("settings.agentGroup"),
tabs: [
{ key: "steward", label: t("settings.steward") },
// The model provider is one machine decision (which endpoint and key the
// operator credential holds); the capability catalog is another (which
// machine defaults every Goal inherits). They answer different questions
// and are edited on different surfaces, so they are separate categories.
{ key: "provider", label: t("settings.modelProvider") },
{ key: "machine", label: t("settings.globalCapabilities") },
...(initialGoalId ? [{ key: "capabilities" as const, label: t("capabilities.title") }] : []),
...(initialGoalId ? [{ key: "cadence" as const, label: t("cadence.title") }] : []),
],
},
{
label: t("settings.workspaceGroup"),
tabs: [
{ key: "lark", label: "Lark" },
{ key: "appearance", label: t("settings.appearance") },
{ key: "language", label: t("settings.language") },
],
},
];
const localeOptions: Array<{ label: string; value: WorkspaceLocale }> = [
{
Expand Down Expand Up @@ -112,6 +124,9 @@ export function WorkspaceSettingsPage({
provider: {
title: t("settings.modelProvider"),
},
steward: {
title: t("settings.steward"),
},
};
const heading = headings[tab];
const selectedGoal = goals.find((item) => item.goalId === initialGoalId);
Expand All @@ -130,17 +145,18 @@ export function WorkspaceSettingsPage({
<strong>{t("settings.title")}</strong>
</div>
<nav aria-label={t("settings.categories")} className="personal-settings-tabs" ref={tabsRef}>
{tabs.map((item) => {
const Icon = tabIcons[item.key];
return (
<button aria-current={tab === item.key ? "page" : undefined} key={item.key} onClick={() => setTab(item.key)} type="button">
<Icon size={17} />
<span>
<strong>{item.label}</strong>
</span>
</button>
);
})}
{tabGroups.map((group) => <div className="personal-settings-tab-group" key={group.label}>
<span className="personal-settings-tab-group-label">{group.label}</span>
{group.tabs.map((item) => {
const Icon = tabIcons[item.key];
return (
<button aria-current={tab === item.key ? "page" : undefined} key={item.key} onClick={() => setTab(item.key)} type="button">
<Icon size={17} />
<span><strong>{item.label}</strong></span>
</button>
);
})}
</div>)}
</nav>
</aside>

Expand Down Expand Up @@ -173,7 +189,8 @@ export function WorkspaceSettingsPage({
</div>
) : null}

{tab === "machine" ? <MachineConfigurationSettings /> : null}
{tab === "steward" ? <MachineConfigurationSettings section="steward" /> : null}
{tab === "machine" ? <MachineConfigurationSettings section="other" /> : null}
{tab === "capabilities" ? (
<GoalCapabilitySettings
callbacks={callbacks}
Expand Down
Loading
Loading