Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
492 lines
13 KiB
TypeScript
492 lines
13 KiB
TypeScript
import { afterEach, describe, expect, it } from "vitest";
|
|
import { setAgentGuiI18nTestLocale } from "../../../i18n/testUtils";
|
|
import type { AgentSessionPermissionConfig } from "../../../shared/agentSessionTypes";
|
|
import type { AgentGUINodeData } from "../../../types";
|
|
import {
|
|
buildNodeDefaultComposerSettings,
|
|
slashCommandPoliciesEqual,
|
|
nodeDataFromComposerSettings,
|
|
permissionModeOptions,
|
|
providerSkillsFromComposerOptions,
|
|
readNodeDefaultDraftSettings,
|
|
reasoningSelectionFromComposerOptions
|
|
} from "./agentGuiController.composerHelpers";
|
|
import type { AgentActivityComposerOptions } from "@tutti-os/agent-activity-core";
|
|
|
|
describe("slash command policy equality", () => {
|
|
const policy = {
|
|
fallbackCommands: ["compact", "goal"],
|
|
commandEffects: [
|
|
{ command: "compact", effect: "submitImmediate" as const },
|
|
{ command: "goal", effect: "activateGoalMode" as const }
|
|
]
|
|
};
|
|
|
|
it("compares cloned policy values structurally", () => {
|
|
expect(slashCommandPoliciesEqual(policy, structuredClone(policy))).toBe(
|
|
true
|
|
);
|
|
expect(
|
|
slashCommandPoliciesEqual(policy, {
|
|
...structuredClone(policy),
|
|
commandEffects: [
|
|
{ command: "compact", effect: "submitImmediate" },
|
|
{ command: "goal", effect: "showStatus" }
|
|
]
|
|
})
|
|
).toBe(false);
|
|
expect(
|
|
slashCommandPoliciesEqual(policy, {
|
|
...structuredClone(policy),
|
|
commandCatalogAuthoritative: true
|
|
})
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("descriptor-backed skill invocation", () => {
|
|
it("does not apply invocation metadata from unavailable capabilities", () => {
|
|
const options = {
|
|
skills: [
|
|
{
|
|
name: "example",
|
|
trigger: "/example",
|
|
sourceKind: "plugin"
|
|
}
|
|
],
|
|
capabilityCatalog: [
|
|
{
|
|
name: "example",
|
|
label: "Example",
|
|
kind: "skill",
|
|
status: "unavailable",
|
|
trigger: "/example",
|
|
invocation: "promptItem"
|
|
}
|
|
]
|
|
} as unknown as AgentActivityComposerOptions;
|
|
|
|
expect(providerSkillsFromComposerOptions(options)).toEqual([
|
|
{
|
|
name: "example",
|
|
trigger: "/example",
|
|
sourceKind: "plugin"
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("projects capability-only prompt-item skills for ordinary provider catalogs", () => {
|
|
const options = {
|
|
provider: "tutti-agent",
|
|
behavior: { nativePluginCatalogAuthoritative: false },
|
|
skills: [],
|
|
capabilityCatalog: [
|
|
{
|
|
id: "skill:review",
|
|
name: "review",
|
|
label: "Review",
|
|
kind: "skill",
|
|
status: "available",
|
|
trigger: "$review",
|
|
invocation: "promptItem",
|
|
path: "/skills/review/SKILL.md",
|
|
description: "Review code"
|
|
},
|
|
{
|
|
id: "skill:disabled",
|
|
name: "disabled",
|
|
label: "Disabled",
|
|
kind: "skill",
|
|
status: "disabled",
|
|
trigger: "$disabled",
|
|
invocation: "promptItem",
|
|
path: "/skills/disabled/SKILL.md"
|
|
}
|
|
]
|
|
} as unknown as AgentActivityComposerOptions;
|
|
|
|
expect(providerSkillsFromComposerOptions(options)).toEqual([
|
|
{
|
|
name: "review",
|
|
trigger: "$review",
|
|
invocation: "promptItem",
|
|
sourceKind: "plugin",
|
|
kind: "skill",
|
|
path: "/skills/review/SKILL.md",
|
|
description: "Review code"
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("projects local connector capabilities with connection status", () => {
|
|
const options = {
|
|
provider: "tutti-agent",
|
|
behavior: {},
|
|
skills: [],
|
|
capabilityCatalog: [
|
|
{
|
|
id: "connector:github",
|
|
name: "github",
|
|
label: "GitHub",
|
|
kind: "connector",
|
|
status: "available",
|
|
trigger: "/github",
|
|
invocation: "textTrigger",
|
|
iconUrl: "data:image/png;base64,Z2l0aHVi",
|
|
description: "Repositories and pull requests",
|
|
source: "local-db"
|
|
},
|
|
{
|
|
id: "connector:notion",
|
|
name: "notion",
|
|
label: "Notion",
|
|
kind: "connector",
|
|
status: "authRequired",
|
|
trigger: "/notion",
|
|
invocation: "textTrigger",
|
|
source: "local-db"
|
|
}
|
|
]
|
|
} as unknown as AgentActivityComposerOptions;
|
|
|
|
expect(providerSkillsFromComposerOptions(options)).toEqual([
|
|
{
|
|
name: "GitHub",
|
|
connectorKey: "github",
|
|
trigger: "/github",
|
|
invocation: "textTrigger",
|
|
sourceKind: "connector",
|
|
kind: "connector",
|
|
status: "available",
|
|
iconUrl: "data:image/png;base64,Z2l0aHVi",
|
|
description: "Repositories and pull requests"
|
|
},
|
|
{
|
|
name: "Notion",
|
|
connectorKey: "notion",
|
|
trigger: "/notion",
|
|
invocation: "textTrigger",
|
|
sourceKind: "connector",
|
|
kind: "connector",
|
|
status: "authRequired"
|
|
}
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("permissionModeOptions", () => {
|
|
afterEach(() => {
|
|
setAgentGuiI18nTestLocale("en");
|
|
});
|
|
|
|
it("localizes codex auto permission through the zh-CN provider label", () => {
|
|
setAgentGuiI18nTestLocale("zh-CN");
|
|
|
|
const permissionConfig: AgentSessionPermissionConfig = {
|
|
configurable: true,
|
|
defaultValue: "auto",
|
|
modes: [
|
|
{
|
|
id: "auto",
|
|
label: "Approve for me",
|
|
description: "Ask only when risky actions are detected",
|
|
semantic: "auto"
|
|
}
|
|
]
|
|
};
|
|
|
|
expect(permissionModeOptions("codex", permissionConfig)).toEqual([
|
|
{
|
|
value: "auto",
|
|
label: "替我审批",
|
|
description: "仅对检测到的风险操作请求批准"
|
|
}
|
|
]);
|
|
});
|
|
|
|
it("presents OpenCode permissions independently from Plan mode", () => {
|
|
const permissionConfig: AgentSessionPermissionConfig = {
|
|
configurable: true,
|
|
defaultValue: "ask",
|
|
modes: [
|
|
{
|
|
id: "read-only",
|
|
label: "Read-only",
|
|
description: "Read-only",
|
|
semantic: "locked-down"
|
|
},
|
|
{
|
|
id: "ask",
|
|
label: "Ask",
|
|
description: "Ask",
|
|
semantic: "ask-before-write"
|
|
},
|
|
{
|
|
id: "full-access",
|
|
label: "Full access",
|
|
description: "Full access",
|
|
semantic: "full-access"
|
|
}
|
|
]
|
|
};
|
|
|
|
expect(permissionModeOptions("opencode", permissionConfig)).toEqual([
|
|
expect.objectContaining({ value: "read-only", label: "Read-only" }),
|
|
expect.objectContaining({ value: "ask", label: "Ask" }),
|
|
expect.objectContaining({
|
|
value: "full-access",
|
|
label: "Full access",
|
|
description: expect.stringContaining("separate Plan mode")
|
|
})
|
|
]);
|
|
});
|
|
});
|
|
|
|
describe("model reasoning options", () => {
|
|
afterEach(() => {
|
|
setAgentGuiI18nTestLocale("en");
|
|
});
|
|
|
|
const composerOptions = {
|
|
provider: "codex",
|
|
models: [
|
|
{ value: "gpt-5.6-sol", label: "GPT-5.6-Sol" },
|
|
{ value: "gpt-5.6-luna", label: "GPT-5.6-Luna" }
|
|
],
|
|
reasoningEfforts: [{ value: "high", label: "High" }],
|
|
speeds: [],
|
|
skills: [],
|
|
reasoningOptionsByModel: {
|
|
"gpt-5.6-sol": {
|
|
defaultValue: "low",
|
|
options: [
|
|
{ value: "low", label: "Low" },
|
|
{ value: "ultra", label: "ultra" }
|
|
]
|
|
},
|
|
"gpt-5.6-luna": {
|
|
defaultValue: "medium",
|
|
options: [
|
|
{ value: "low", label: "Low" },
|
|
{ value: "medium", label: "Medium" }
|
|
]
|
|
},
|
|
"no-reasoning": {
|
|
defaultValue: null,
|
|
options: []
|
|
}
|
|
},
|
|
loadedAtUnixMs: 1
|
|
} as unknown as AgentActivityComposerOptions;
|
|
|
|
it("switches the list by model without another catalog request", () => {
|
|
expect(
|
|
reasoningSelectionFromComposerOptions(
|
|
composerOptions,
|
|
"high",
|
|
"gpt-5.6-sol"
|
|
)
|
|
).toEqual({
|
|
currentValue: "low",
|
|
options: [
|
|
{ value: "low", label: "Low" },
|
|
{ value: "ultra", label: "ultra" }
|
|
]
|
|
});
|
|
expect(
|
|
reasoningSelectionFromComposerOptions(
|
|
composerOptions,
|
|
"ultra",
|
|
"gpt-5.6-luna"
|
|
)
|
|
).toEqual({
|
|
currentValue: "medium",
|
|
options: [
|
|
{ value: "low", label: "Low" },
|
|
{ value: "medium", label: "Medium" }
|
|
]
|
|
});
|
|
});
|
|
|
|
it("prefers the active ACP config options over the home catalog profile", () => {
|
|
expect(
|
|
reasoningSelectionFromComposerOptions(
|
|
composerOptions,
|
|
"xhigh",
|
|
"gpt-5.6-sol",
|
|
{
|
|
configOptions: [
|
|
{
|
|
id: "reasoning_effort",
|
|
currentValue: "xhigh",
|
|
options: [
|
|
{ value: "high", name: "High" },
|
|
{ value: "xhigh", name: "X High" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
)
|
|
).toEqual({
|
|
currentValue: "xhigh",
|
|
options: [
|
|
{ value: "high", label: "High" },
|
|
{ value: "xhigh", label: "X High" }
|
|
]
|
|
});
|
|
});
|
|
|
|
it("preserves an authoritative empty model reasoning list", () => {
|
|
expect(
|
|
reasoningSelectionFromComposerOptions(
|
|
composerOptions,
|
|
"high",
|
|
"no-reasoning"
|
|
)
|
|
).toEqual({ currentValue: null, options: [] });
|
|
});
|
|
|
|
it("rejects a stale live current value outside the advertised list", () => {
|
|
expect(
|
|
reasoningSelectionFromComposerOptions(
|
|
composerOptions,
|
|
"ultra",
|
|
"gpt-5.6-sol",
|
|
{
|
|
configOptions: [
|
|
{
|
|
id: "reasoning_effort",
|
|
currentValue: "ultra",
|
|
options: [
|
|
{ value: "high", name: "High" },
|
|
{ value: "xhigh", name: "X High" }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
)
|
|
).toEqual({
|
|
currentValue: "high",
|
|
options: [
|
|
{ value: "high", label: "High" },
|
|
{ value: "xhigh", label: "X High" }
|
|
]
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("target-keyed composer defaults", () => {
|
|
const baseNodeData: AgentGUINodeData = {
|
|
provider: "codex",
|
|
agentTargetId: "local:codex",
|
|
lastActiveAgentSessionId: null,
|
|
composerOverrides: null,
|
|
composerOverridesByAgentTargetId: {
|
|
"local:codex": { model: "target-model" }
|
|
},
|
|
composerOverridesByProvider: {
|
|
codex: { model: "provider-model" }
|
|
}
|
|
};
|
|
|
|
it("isolates target composer overrides from provider defaults", () => {
|
|
expect(buildNodeDefaultComposerSettings(baseNodeData).model).toBe(
|
|
"target-model"
|
|
);
|
|
expect(
|
|
buildNodeDefaultComposerSettings({
|
|
...baseNodeData,
|
|
agentTargetId: "other-target"
|
|
}).model
|
|
).toBeNull();
|
|
expect(
|
|
buildNodeDefaultComposerSettings({
|
|
...baseNodeData,
|
|
agentTargetId: null
|
|
}).model
|
|
).toBe("provider-model");
|
|
});
|
|
|
|
it("writes target composer overrides without mutating provider defaults", () => {
|
|
const next = nodeDataFromComposerSettings(baseNodeData, {
|
|
model: "target-new",
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
browserUse: true,
|
|
computerUse: true,
|
|
permissionModeId: null
|
|
});
|
|
|
|
expect(next.composerOverridesByAgentTargetId?.["local:codex"]?.model).toBe(
|
|
"target-new"
|
|
);
|
|
expect(next.composerOverridesByProvider?.codex?.model).toBe(
|
|
"provider-model"
|
|
);
|
|
expect(next.composerOverrides?.model ?? null).toBeNull();
|
|
});
|
|
|
|
it("reads target draft settings before provider legacy defaults", () => {
|
|
expect(
|
|
readNodeDefaultDraftSettings({
|
|
data: baseNodeData,
|
|
drafts: {
|
|
"__agent_gui_node_defaults__:codex": {
|
|
model: "provider-draft-model",
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
browserUse: true,
|
|
computerUse: true,
|
|
permissionModeId: null
|
|
},
|
|
"__agent_gui_node_defaults__:target:local:codex": {
|
|
model: "target-draft-model",
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
browserUse: true,
|
|
computerUse: true,
|
|
permissionModeId: null
|
|
}
|
|
}
|
|
}).model
|
|
).toBe("target-draft-model");
|
|
});
|
|
|
|
it("does not read provider or global draft settings for target-backed composers", () => {
|
|
expect(
|
|
readNodeDefaultDraftSettings({
|
|
data: {
|
|
...baseNodeData,
|
|
composerOverrides: {
|
|
model: "legacy-generic-model",
|
|
permissionModeId: "full-access"
|
|
}
|
|
},
|
|
drafts: {
|
|
"__agent_gui_node_defaults__:codex": {
|
|
model: "provider-draft-model",
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
browserUse: true,
|
|
computerUse: true,
|
|
permissionModeId: "auto"
|
|
},
|
|
__agent_gui_node_defaults__: {
|
|
model: "global-draft-model",
|
|
reasoningEffort: null,
|
|
speed: null,
|
|
planMode: false,
|
|
browserUse: true,
|
|
computerUse: true,
|
|
permissionModeId: "full-access"
|
|
}
|
|
}
|
|
})
|
|
).toEqual({});
|
|
});
|
|
});
|