9e8079de69
Bundles a string of multi-repo fixes and refactors that came up while testing the feature end-to-end: - workspace_path stored on multi-repo TaskEnvironment was one level too high, so agentctl booted with the wrong WorkDir on cold start; fixed in resolveTaskEnvWorkspacePath + GetWorkspaceInfoForSession. - Stage/Unstage/Discard/Commit/Revert/Reset/Amend now thread `repo` through agentctl client → WS handler → frontend hook → SessionGit fan-out, so the right git repo is targeted (was running at the task root, which isn't a git repo for multi-repo tasks). - agentctl GitLog is multi-repo aware: when no `repo` is specified and per-repo trackers exist, fans out one log per repo and merges sorted newest-first with RepositoryName stamped on each commit. - Per-repo grouping for files (Changes panel) and commits, with collapsible group headers; commits section defaults to collapsed. Algorithm extracted into one generic groupByRepositoryName helper (was copy-pasted three times). - Task creation dialog rewritten around a unified `fs.repositories[]` list — no more "primary vs extras" split. Chip-style row with one pill per repo + branch, "+" to add, x to remove, GitHub URL toggle inline. Discovered (on-machine) repos appear in any chip's dropdown. Removes ~6 fields and 3 helpers from the dialog state. - Branch listing endpoints collapsed to one: GET /workspaces/:id/branches?repository_id=X | path=Y Frontend collapses to a single useBranches hook with a tagged-union source. Drops two HTTP routes, two service methods, two API client funcs, and one whole hook file. Tests added across the chain: workspace_path resolver, sort-commits- by-date, manager poll-mode propagation, RepoSubpaths, chip grouping + collapse + per-repo isLatest, group-by-repo helper, dialog payload builder + chip wiring + discovered-repo flow.
169 lines
4.8 KiB
TypeScript
169 lines
4.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import {
|
|
computeDisabledReason,
|
|
REASON_TITLE,
|
|
REASON_REPO,
|
|
REASON_BRANCH,
|
|
REASON_WORKSPACE,
|
|
REASON_WORKFLOW,
|
|
REASON_AGENT,
|
|
REASON_DESCRIPTION,
|
|
} from "./task-create-dialog-footer";
|
|
import type { ButtonKind, TaskCreateDialogFooterProps } from "./task-create-dialog-footer";
|
|
|
|
const KIND_START: ButtonKind = "start-task";
|
|
const KIND_UPDATE: ButtonKind = "update";
|
|
const KIND_DEFAULT: ButtonKind = "default";
|
|
|
|
function makeProps(
|
|
overrides: Partial<TaskCreateDialogFooterProps> = {},
|
|
): TaskCreateDialogFooterProps {
|
|
return {
|
|
isSessionMode: false,
|
|
isCreateMode: true,
|
|
isEditMode: false,
|
|
isTaskStarted: false,
|
|
isPassthroughProfile: false,
|
|
isCreatingSession: false,
|
|
isCreatingTask: false,
|
|
hasTitle: true,
|
|
hasDescription: true,
|
|
hasRepositorySelection: true,
|
|
hasAllBranches: true,
|
|
agentProfileId: "agent-1",
|
|
workspaceId: "ws-1",
|
|
effectiveWorkflowId: "wf-1",
|
|
executorHint: null,
|
|
onCancel: () => {},
|
|
onUpdateWithoutAgent: () => {},
|
|
onCreateWithoutAgent: () => {},
|
|
onCreateWithPlanMode: () => {},
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("computeDisabledReason (start-task)", () => {
|
|
it("returns null when nothing is missing", () => {
|
|
expect(computeDisabledReason(makeProps(), KIND_START)).toBeNull();
|
|
});
|
|
|
|
it("returns null while a submission is in flight", () => {
|
|
expect(
|
|
computeDisabledReason(makeProps({ isCreatingTask: true, hasTitle: false }), KIND_START),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("flags missing title first", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ hasTitle: false, hasRepositorySelection: false }),
|
|
KIND_START,
|
|
),
|
|
).toBe(REASON_TITLE);
|
|
});
|
|
|
|
it("flags missing repository selection", () => {
|
|
expect(computeDisabledReason(makeProps({ hasRepositorySelection: false }), KIND_START)).toBe(
|
|
REASON_REPO,
|
|
);
|
|
});
|
|
|
|
it("flags missing branch", () => {
|
|
expect(computeDisabledReason(makeProps({ hasAllBranches: false }), KIND_START)).toBe(
|
|
REASON_BRANCH,
|
|
);
|
|
});
|
|
|
|
it("flags missing workspace in create mode", () => {
|
|
expect(computeDisabledReason(makeProps({ workspaceId: null }), KIND_START)).toBe(
|
|
REASON_WORKSPACE,
|
|
);
|
|
});
|
|
|
|
it("flags missing workflow in create mode", () => {
|
|
expect(computeDisabledReason(makeProps({ effectiveWorkflowId: null }), KIND_START)).toBe(
|
|
REASON_WORKFLOW,
|
|
);
|
|
});
|
|
|
|
it("ignores missing workspace/workflow outside create mode", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ isCreateMode: false, isEditMode: true, workspaceId: null }),
|
|
KIND_START,
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("flags missing agent profile for start-task button", () => {
|
|
expect(computeDisabledReason(makeProps({ agentProfileId: "" }), KIND_START)).toBe(REASON_AGENT);
|
|
});
|
|
});
|
|
|
|
describe("computeDisabledReason (update)", () => {
|
|
it("only flags missing title for the update button", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ hasTitle: false, hasRepositorySelection: false, agentProfileId: "" }),
|
|
KIND_UPDATE,
|
|
),
|
|
).toBe(REASON_TITLE);
|
|
});
|
|
|
|
it("returns null for update when title is present, even with other gaps", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ hasRepositorySelection: false, agentProfileId: "" }),
|
|
KIND_UPDATE,
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("computeDisabledReason (default)", () => {
|
|
it("does not require agent outside session mode", () => {
|
|
expect(computeDisabledReason(makeProps({ agentProfileId: "" }), KIND_DEFAULT)).toBeNull();
|
|
});
|
|
|
|
it("requires agent in session mode", () => {
|
|
expect(
|
|
computeDisabledReason(makeProps({ isSessionMode: true, agentProfileId: "" }), KIND_DEFAULT),
|
|
).toBe(REASON_AGENT);
|
|
});
|
|
|
|
it("flags missing session description in session mode", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ isSessionMode: true, hasDescription: false }),
|
|
KIND_DEFAULT,
|
|
),
|
|
).toBe(REASON_DESCRIPTION);
|
|
});
|
|
|
|
it("does not flag missing description for passthrough profiles", () => {
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({ isSessionMode: true, hasDescription: false, isPassthroughProfile: true }),
|
|
KIND_DEFAULT,
|
|
),
|
|
).toBeNull();
|
|
});
|
|
|
|
it("ignores base reasons in session mode to match DefaultSubmitButton disabled logic", () => {
|
|
// In session mode the default button is only disabled by !agentProfileId or
|
|
// missing description — NOT by missing title/repo/branch/workspace/workflow.
|
|
// The tooltip must not contradict that state.
|
|
expect(
|
|
computeDisabledReason(
|
|
makeProps({
|
|
isSessionMode: true,
|
|
hasTitle: false,
|
|
hasRepositorySelection: false,
|
|
hasAllBranches: false,
|
|
}),
|
|
KIND_DEFAULT,
|
|
),
|
|
).toBeNull();
|
|
});
|
|
});
|