feat: service route branch handling, workspace hover card redesign, and agent form state improvements

- Add service-route-branch-handler for routing requests to branch-specific services
- Add service-hostname utility for generating hostnames from branch names
- Redesign workspace hover card with CI check status display
- Update combined model selector and sidebar workspace list
- Improve agent form state and input draft hooks
- Update checkout-git with enhanced branch handling
- Add workspace git watch and bootstrap improvements
This commit is contained in:
Mohamed Boudra
2026-04-06 14:38:29 +07:00
parent 5d1b3383d7
commit 6e5cad812e
19 changed files with 734 additions and 247 deletions
@@ -56,7 +56,7 @@ describe("useAgentFormState", () => {
},
];
it("auto-selects the model's default thinking option when none is configured", () => {
it("does not auto-select a model on fresh drafts without preferences", () => {
const resolved = __private__.resolveFormState(
undefined,
{ provider: "codex" },
@@ -80,14 +80,14 @@ describe("useAgentFormState", () => {
new Set<string>(),
);
expect(resolved.model).toBe("gpt-5.3-codex");
expect(resolved.thinkingOptionId).toBe("xhigh");
expect(resolved.model).toBe("");
expect(resolved.thinkingOptionId).toBe("");
});
it("prefers provider defaults on fresh drafts", () => {
it("auto-selects the model's default thinking option when model is preferred but thinking is not", () => {
const resolved = __private__.resolveFormState(
undefined,
{ provider: "codex" },
{ provider: "codex", providerPreferences: { codex: { model: "gpt-5.3-codex" } } },
codexModels,
{
serverId: false,
@@ -115,7 +115,7 @@ describe("useAgentFormState", () => {
it("falls back to model default when saved thinking preference is invalid", () => {
const resolved = __private__.resolveFormState(
undefined,
{ provider: "codex" },
{ provider: "codex", providerPreferences: { codex: { model: "gpt-5.3-codex" } } },
codexModels,
{
serverId: false,
@@ -195,7 +195,7 @@ describe("useAgentFormState", () => {
it("keeps an explicit initial thinking option when it is valid", () => {
const resolved = __private__.resolveFormState(
{ thinkingOptionId: "low" },
{ model: "gpt-5.3-codex", thinkingOptionId: "low" },
{ provider: "codex" },
codexModels,
{
@@ -237,7 +237,7 @@ describe("useAgentFormState", () => {
const resolved = __private__.resolveFormState(
undefined,
{ provider: "claude" },
{ provider: "claude", providerPreferences: { claude: { model: "default" } } },
claudeModels,
{
serverId: false,
@@ -138,7 +138,7 @@ function resolveEffectiveModel(
}
const normalizedModelId = modelId.trim();
if (!normalizedModelId) {
return resolveDefaultModel(availableModels);
return null;
}
return (
availableModels.find((model) => model.id === normalizedModelId) ??
@@ -243,8 +243,6 @@ function resolveFormState(
} else {
result.model = defaultModelId;
}
} else if (defaultModelId) {
result.model = defaultModelId;
} else {
result.model = "";
}
@@ -112,13 +112,13 @@ describe("useAgentInputDraft", () => {
).toBe("gpt-5.4-mini");
});
it("falls back to the provider default model", () => {
it("returns empty string when no model selected", () => {
expect(
__private__.resolveEffectiveComposerModelId({
selectedModel: "",
availableModels: models,
}),
).toBe("gpt-5.4");
).toBe("");
});
});
@@ -88,12 +88,7 @@ function resolveEffectiveComposerModelId(input: {
selectedModel: string;
availableModels: AgentModelDefinition[];
}): string {
const selectedModel = input.selectedModel.trim();
if (selectedModel) {
return selectedModel;
}
return input.availableModels.find((model) => model.isDefault)?.id ?? input.availableModels[0]?.id ?? "";
return input.selectedModel.trim();
}
function resolveEffectiveComposerThinkingOptionId(input: {