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
8 changes: 6 additions & 2 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ export const todoApplyResultSchema = z.object({

const goalSubagentOrchestrationSchema = z.object({
model_config: z.object({ model: z.string(), reasoning_effort: z.string().optional() }).optional(),
execution_config: z.string().optional(),

mode: z.string(),
spawn_allowed: z.boolean(),
Expand Down Expand Up @@ -302,6 +303,7 @@ export type GoalSubagentConfigurationResult = z.infer<typeof goalSubagentConfigu

export type GoalSubagentConfigurationRequest = {
modelConfig?: { model: string; reasoning_effort?: string } | null;
executionConfig?: string;
allowedDomains: string[];
enabled: boolean;
goalId: string;
Expand Down Expand Up @@ -848,10 +850,10 @@ export async function interruptChatTurn(sessionId: string, turnId: string) {
);
}

export type LoopXModeSettings = { agent_id: string; token_budget: number; execution_config: string };
export type LoopXModeSettings = { agent_id: string; token_budget: number };
export type LoopXModeSnapshot = {
ok: true; session_id: string; enabled: boolean; active_turn_id: string | null; conversation_busy: boolean;
settings: Partial<LoopXModeSettings>;
settings: Partial<LoopXModeSettings> & { execution_config?: string };
native: { status: string; tokenBudget?: number; tokensUsed?: number };
registered_agents: string[]; paused: boolean; recovery_required: boolean;
members: Array<{id: string; agent_id: string; todo_id: string}>;
Expand Down Expand Up @@ -1025,6 +1027,7 @@ function goalSubagentConfigurationBody(request: GoalSubagentConfigurationRequest
goal_id: request.goalId,
enabled: request.enabled,
...(request.modelConfig !== undefined ? { model_config: request.modelConfig } : {}),
...(request.executionConfig !== undefined ? { execution_config: request.executionConfig } : {}),
...(request.enabled ? {
max_children: request.maxChildren,
allowed_domains: request.allowedDomains,
Expand All @@ -1042,6 +1045,7 @@ function verifyGoalSubagentConfigurationResult(
const matchesRequest = result.goal_id === request.goalId
&& enabled === request.enabled
&& (request.modelConfig === undefined || JSON.stringify(orchestration.model_config ?? null) === JSON.stringify(request.modelConfig))
&& (request.executionConfig === undefined || (orchestration.execution_config ?? "") === request.executionConfig)
&& (request.enabled
? orchestration.max_children === request.maxChildren
&& JSON.stringify(orchestration.allowed_domains) === JSON.stringify(expectedDomains)
Expand Down
1 change: 1 addition & 0 deletions apps/presentation/dashboard/src/data/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const controlPlaneSchema = z.object({

export const orchestrationPolicySchema = z.object({
model_config: z.object({ model: z.string(), reasoning_effort: z.string().optional() }).optional(),
execution_config: z.string().optional(),

mode: z.string().optional().default("default"),
orchestration_mode: z.string().optional().nullable(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type TodoOperation = "block" | "complete" | "defer" | "successor_create";

type GoalSubagentPreview = {
modelConfig?: { model: string; reasoning_effort?: string } | null;
executionConfig?: string;
allowedDomains: string[];
changed: boolean;
enabled: boolean;
Expand Down Expand Up @@ -97,6 +98,7 @@ function subagentConfigurationsMatch(
return left.enabled === right.enabled
&& left.maxChildren === right.maxChildren
&& JSON.stringify(left.modelConfig ?? null) === JSON.stringify(right.modelConfig ?? null)
&& (left.executionConfig ?? "") === (right.executionConfig ?? "")
&& [...left.allowedDomains].sort((a, b) => a.localeCompare(b)).join("\u0000")
=== [...right.allowedDomains].sort((a, b) => a.localeCompare(b)).join("\u0000");
}
Expand Down Expand Up @@ -128,6 +130,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
const [subagentMaxChildren, setSubagentMaxChildren] = useState(2);
const [subagentModel, setSubagentModel] = useState("");
const [subagentEffort, setSubagentEffort] = useState("");
const [subagentExecutionConfig, setSubagentExecutionConfig] = useState("");
const [subagentMutationState, setSubagentMutationState] = useState<"idle" | "previewing" | "ready" | "applying" | "success" | "warning" | "error">("idle");
const [subagentPreview, setSubagentPreview] = useState<GoalSubagentPreview | null>(null);
const [verifiedSubagentConfiguration, setVerifiedSubagentConfiguration] = useState<WorkspaceGoalSubagentConfiguration | null>(null);
Expand Down Expand Up @@ -156,6 +159,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(configuration?.allowedDomains ?? []);
setSubagentModel(configuration?.modelConfig?.model ?? "");
setSubagentEffort(configuration?.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(configuration?.executionConfig ?? "");
setSubagentMaxChildren(configuration?.maxChildren ? Math.min(configuration.maxChildren, 32) : 2);
setSubagentFeedback(null);
setSubagentMutationState("idle");
Expand Down Expand Up @@ -185,6 +189,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(authoritativeSubagentConfiguration.allowedDomains);
setSubagentModel(authoritativeSubagentConfiguration.modelConfig?.model ?? "");
setSubagentEffort(authoritativeSubagentConfiguration.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(authoritativeSubagentConfiguration.executionConfig ?? "");
setSubagentMaxChildren(authoritativeSubagentConfiguration.maxChildren || 2);
setSubagentFeedback(null);
setSubagentMutationState("idle");
Expand All @@ -210,6 +215,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(authoritativeSubagentConfiguration.allowedDomains);
setSubagentModel(authoritativeSubagentConfiguration.modelConfig?.model ?? "");
setSubagentEffort(authoritativeSubagentConfiguration.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(authoritativeSubagentConfiguration.executionConfig ?? "");
setSubagentMaxChildren(
authoritativeSubagentConfiguration.maxChildren || 2,
);
Expand Down Expand Up @@ -382,6 +388,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(currentSubagentConfiguration.allowedDomains);
setSubagentModel(currentSubagentConfiguration.modelConfig?.model ?? "");
setSubagentEffort(currentSubagentConfiguration.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(currentSubagentConfiguration.executionConfig ?? "");
setSubagentMaxChildren(currentSubagentConfiguration.maxChildren || 2);
setSubagentFeedback(null);
setSubagentMutationState("idle");
Expand Down Expand Up @@ -421,6 +428,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
enabled,
goalId: selection.item.goalId,
maxChildren: enabled ? subagentMaxChildren : 0,
executionConfig: subagentExecutionConfig.trim(),
...subagentModelRequest(includeModel, subagentModel, subagentEffort),
};
setSubagentMutationState("previewing");
Expand All @@ -438,6 +446,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(preview.configuration.allowedDomains);
setSubagentModel(preview.configuration.modelConfig?.model ?? "");
setSubagentEffort(preview.configuration.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(preview.configuration.executionConfig ?? "");
setSubagentMaxChildren(preview.configuration.maxChildren || 2);
setSubagentMutationState("success");
setSubagentFeedback(t("drawer.subagentNoChange"));
Expand All @@ -463,6 +472,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
goalId: subagentPreview.goalId,
maxChildren: subagentPreview.maxChildren,
modelConfig: subagentPreview.modelConfig,
executionConfig: subagentPreview.executionConfig,
previewId: subagentPreview.previewId,
});
verifiedSubagentBaselineRef.current = authoritativeSubagentConfiguration
Expand All @@ -474,6 +484,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
setSubagentAllowedDomains(verifiedConfiguration.allowedDomains);
setSubagentModel(verifiedConfiguration.modelConfig?.model ?? "");
setSubagentEffort(verifiedConfiguration.modelConfig?.reasoning_effort ?? "");
setSubagentExecutionConfig(verifiedConfiguration.executionConfig ?? "");
setSubagentMaxChildren(verifiedConfiguration.maxChildren || 2);
setSubagentMutationState("success");
setSubagentFeedback(t("drawer.subagentApplied"));
Expand Down Expand Up @@ -730,6 +741,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
})
: t("drawer.subagentDisableSummary")}</p>
{subagentPreview.modelConfig !== undefined ? <p>{t("drawer.subagentModel")}: {subagentPreview.modelConfig?.model || t("drawer.subagentModelDefault")} · {subagentPreview.modelConfig?.reasoning_effort || t("drawer.subagentModelDefault")}</p> : null}
<p>{t("drawer.subagentExecutionConfig")}: {subagentPreview.executionConfig || t("drawer.subagentExecutionConfigNone")}</p>
<div>
<button className="personal-primary-action" onClick={() => void applyGoalSubagentConfiguration()} type="button">{t("common.confirm")}</button>
<button className="personal-secondary-action" onClick={resetSubagentDraft} type="button">{t("common.cancel")}</button>
Expand All @@ -747,6 +759,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<div><dt>{t("drawer.subagentEffort")}</dt><dd>{currentSubagentConfiguration.modelConfig?.reasoning_effort || t("drawer.subagentModelDefault")}</dd></div>
<div><dt>{t("drawer.subagentCurrentBoundary")}</dt><dd>{currentSubagentConfiguration.allowedDomains.join(" · ") || t("drawer.subagentDomainsUnrestricted")}</dd></div>
<div><dt>{t("drawer.subagentChildLimit")}</dt><dd>{currentSubagentConfiguration.maxChildren || 0}</dd></div>
<div><dt>{t("drawer.subagentExecutionConfig")}</dt><dd>{currentSubagentConfiguration.executionConfig || t("drawer.subagentExecutionConfigNone")}</dd></div>
</dl>
{readOnly ? (
<p className="personal-subagent-read-only">{t("drawer.subagentRemoteReadOnly")}</p>
Expand Down Expand Up @@ -793,6 +806,17 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<button className="personal-secondary-action" disabled={subagentBusy} type="button" onClick={() => { setSubagentModel("gpt-5.6-luna"); setSubagentEffort("max"); setSubagentPreview(null); setSubagentMutationState("idle"); setSubagentFeedback(null); }}>{t("drawer.subagentLunaPreset")}</button>
<button className="personal-secondary-action" disabled={subagentBusy} type="button" onClick={() => { setSubagentModel(""); setSubagentEffort(""); setSubagentPreview(null); setSubagentMutationState("idle"); setSubagentFeedback(null); }}>{t("drawer.subagentClearModel")}</button>
<p>{t("drawer.subagentModelHint")}</p>
<label>
<span>{t("drawer.subagentExecutionConfig")}</span>
<input
aria-label={t("drawer.subagentExecutionConfig")}
disabled={subagentBusy}
onChange={(event) => { setSubagentExecutionConfig(event.target.value); setSubagentPreview(null); setSubagentMutationState("idle"); setSubagentFeedback(null); }}
placeholder=".loopx/config/delegations.json"
value={subagentExecutionConfig}
/>
</label>
<p>{t("drawer.subagentExecutionConfigHint")}</p>
<label className="personal-subagent-limit-field">
<span>{t("drawer.subagentMaxChildren")}</span>
<select
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export function GoalLoopXMode({sessionId, onPrepare, onExecute, onChange}: {
const [editing, setEditing] = useState(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const [settings, setSettings] = useState<LoopXModeSettings>({agent_id: "", token_budget: 0, execution_config: ".loopx/config/delegations.json"});
const [settings, setSettings] = useState<LoopXModeSettings>({agent_id: "", token_budget: 0});
useEffect(() => {
let alive = true;
let refreshing = false;
Expand All @@ -38,10 +38,10 @@ export function GoalLoopXMode({sessionId, onPrepare, onExecute, onChange}: {
const active = Boolean(snapshot?.enabled && snapshot.active_turn_id);
const native = snapshot?.native.status ?? "absent";
const resume = !["absent", "complete"].includes(native);
const configured = Boolean(snapshot?.settings.agent_id && snapshot.settings.token_budget);
const configured = Boolean(snapshot?.settings.agent_id && snapshot.settings.token_budget
&& snapshot.settings.execution_config);
const editSettings = (current: LoopXModeSnapshot) => {
setSettings({agent_id: current.settings.agent_id ?? "", token_budget: current.settings.token_budget ?? 0,
execution_config: current.settings.execution_config ?? ".loopx/config/delegations.json"});
setSettings({agent_id: current.settings.agent_id ?? "", token_budget: current.settings.token_budget ?? 0});
setEditing(true);
};
const status = !snapshot?.enabled ? (zh ? "普通对话" : "Conversation")
Expand Down Expand Up @@ -92,9 +92,9 @@ export function GoalLoopXMode({sessionId, onPrepare, onExecute, onChange}: {
</div></div>
{editing ? <div className="goal-loopx-mode-settings"><label>{zh ? "已注册的协调身份" : "Registered coordinator"}<select value={settings.agent_id} onChange={event => setSettings({...settings, agent_id: event.target.value})}><option value="">{zh ? "选择已授权身份" : "Select authorized identity"}</option>{snapshot?.registered_agents.map(id => <option key={id} value={id}>{id}</option>)}</select></label>
<label>{zh ? "协调员总 token 额度" : "Coordinator total token allowance"}<input type="number" min={1} max={2147483647} value={settings.token_budget || ""} onChange={event => setSettings({...settings, token_budget: Number(event.target.value)})}/></label>
<label>{zh ? "成员执行绑定文件(项目内)" : "Member execution bindings (project relative)"}<input value={settings.execution_config} onChange={event => setSettings({...settings, execution_config: event.target.value})}/></label>
<p>{zh ? "复用现有协作执行配置,文件须位于 .loopx/config/。额度包含协调员历史用量;成员沿用各自授权,不随开启扩大。" : "Reuse an existing delegation configuration under .loopx/config/. The allowance includes coordinator history; member grants remain separate."}</p>
<button type="button" disabled={busy || !settings.agent_id || settings.token_budget < 1} onClick={() => void mutate("configure")}>{zh ? "保存设置" : "Save settings"}</button></div> : null}
<label>{zh ? "成员执行绑定文件(Goal 配置)" : "Member execution bindings (Goal configuration)"}<input readOnly value={snapshot?.settings.execution_config ?? (zh ? "未配置" : "Not configured")}/></label>
<p>{zh ? "绑定文件由 Goal 子代理设置统一管理;额度包含协调员历史用量,成员授权不会因开启模式而扩大。" : "Manage the binding file in Goal sub-agent settings. The allowance includes coordinator history; enabling this mode does not expand member grants."}</p>
<button type="button" disabled={busy || !settings.agent_id || settings.token_budget < 1 || !snapshot?.settings.execution_config} onClick={() => void mutate("configure")}>{zh ? "保存设置" : "Save settings"}</button></div> : null}
{snapshot?.enabled && snapshot.native.tokensUsed !== undefined ? <p className="goal-loopx-mode-usage">{zh ? "协调员累计用量" : "Coordinator usage"} {snapshot.native.tokensUsed.toLocaleString()} / {snapshot.native.tokenBudget?.toLocaleString() ?? "—"} tokens</p> : null}
{snapshot?.enabled && snapshot.ingress.some(row => row.status !== "delivered") ? <p role="status">{zh ? "待处理消息:" : "Pending messages: "}{snapshot.ingress.filter(row => row.status !== "delivered").map(row => `${row.mode === "loopx_queue" ? "queue" : "inbox"} · ${row.status}`).join(" / ")}</p> : null}
{snapshot?.enabled && snapshot.deliveries.length ? <div className="goal-loopx-mode-members" aria-label={zh ? "最近一次成员回读" : "Last member observations"}><span>{zh ? "成员最近回读" : "Last observations"}</span>{snapshot.deliveries.map(row => <span key={row.operation_id}>{row.agent_id} · {row.status === "accepted" ? (zh ? "已通过验收" : "Accepted") : row.status === "rejected" ? (zh ? "未通过验收" : "Rejected") : row.status === "unavailable" ? (zh ? "需要重新核验" : "Recheck required") : (zh ? "执行中" : "Working")}</span>)}</div> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ const en = {
"drawer.subagentClearModel": "Clear model preference",
"drawer.subagentModelHint": "Use Preview configuration update to save this preference. It can be saved while execution is off; host support is checked at launch.",
"drawer.subagentModelRequired": "Choose a child model before setting reasoning effort.",
"drawer.subagentExecutionConfig": "Delegation bindings",
"drawer.subagentExecutionConfigHint": "Optional repo-relative ignored JSON under .loopx/config/. Planning sees only authorized route status; the file remains the execution-grant owner.",
"drawer.subagentExecutionConfigNone": "Not configured",
"drawer.subagentMaxChildren": "Maximum child agents",
"drawer.subagentNoChange": "The current Goal already matches this setting; nothing was written.",
"drawer.subagentPending": "Pending confirmation",
Expand Down Expand Up @@ -1347,6 +1350,9 @@ const zhCN: Record<WorkspaceMessageKey, string> = {
"drawer.subagentClearModel": "清除模型偏好",
"drawer.subagentModelHint": "填写后通过“预览配置调整”保存,执行关闭时也可保存偏好;启动时须核验宿主支持情况。",
"drawer.subagentModelRequired": "设置推理档位前请先指定子 Agent 模型。",
"drawer.subagentExecutionConfig": "委托执行绑定",
"drawer.subagentExecutionConfigHint": "可选;填写 .loopx/config/ 下仓库相对、已忽略的 JSON。规划只读取已授权路由状态,执行授权仍由该文件持有。",
"drawer.subagentExecutionConfigNone": "未配置",
"drawer.subagentMaxChildren": "最多子代理数",
"drawer.subagentNoChange": "当前 Goal 已是这个设置,没有写入任何内容。",
"drawer.subagentPending": "待确认",
Expand Down
Loading
Loading