Enable validated workspace membership additions and make active tasks discover new repositories safely. - add idempotent direct-child Git repository validation and workspace configuration updates - expose repository discovery and addition through Settings and dashboard API routes - refresh workspace membership during acquisition while blocking additions after review or landing begins - document the workflow and cover core, dashboard, and engine behavior with focused tests Files changed: .changeset/fn-9163-workspace-add-repo.md | 7 ++ docs/dashboard-guide.md | 2 + docs/settings-reference.md | 2 +- docs/workspaces.md | 12 +- .../core/src/__tests__/workspace-add-repo.test.ts | 12 ++ packages/core/src/git/git-repository.ts | 68 ++++++++++- packages/core/src/index.gate.ts | 3 + packages/core/src/index.ts | 3 + packages/dashboard/app/api/agents/agents.ts | 11 +- .../settings/sections/GeneralSection.tsx | 3 + .../settings/sections/WorkspaceReposCard.css | 6 + .../settings/sections/WorkspaceReposCard.tsx | 46 ++++++++ .../__tests__/workspace-repos-card.test.tsx | 49 ++++++++ .../src/__tests__/workspace-add-repo-route.test.ts | 65 ++++++++++ .../dashboard/src/routes/register-git-github.ts | 35 +++++- .../__tests__/workspace-add-repo-midflight.test.ts | 131 +++++++++++++++++++++ packages/engine/src/agent-tools.ts | 27 ++++- packages/engine/src/executor/deps-bags.ts | 44 ++++++- packages/engine/src/executor/run-implementation.ts | 2 + 19 files changed, 512 insertions(+), 16 deletions(-) Fusion-Task-Id: FN-9163 Fusion-Task-Lineage: 7456f55f-b7c8-4076-994f-42994d8babb0 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
47 lines
2.4 KiB
TypeScript
47 lines
2.4 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { addWorkspaceRepo, fetchWorkspaceRepos } from "../../../api/agents/agents";
|
|
import "./WorkspaceReposCard.css";
|
|
|
|
/*
|
|
FNXC:Workspace 2026-08-20-02:03:
|
|
Workspace membership is editable after registration; the workspace mode toggle enables the mode but
|
|
never adds members. This single module-scope card avoids remounting the free-text input during refresh.
|
|
*/
|
|
export function WorkspaceReposCard({ projectId }: { projectId?: string }) {
|
|
const { t } = useTranslation("app");
|
|
const [repos, setRepos] = useState<string[] | null>(null);
|
|
const [available, setAvailable] = useState<string[]>([]);
|
|
const [value, setValue] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
const load = async () => {
|
|
try {
|
|
const result = await fetchWorkspaceRepos(projectId, { includeAvailable: true });
|
|
setRepos(result.repos);
|
|
setAvailable(result.available ?? []);
|
|
} catch { setRepos(null); }
|
|
};
|
|
useEffect(() => { void load(); }, [projectId]);
|
|
if (repos === null) return null;
|
|
const add = async () => {
|
|
if (!value.trim()) return;
|
|
setError(null);
|
|
try {
|
|
const result = await addWorkspaceRepo(value.trim(), projectId);
|
|
setRepos(result.repos);
|
|
setAvailable((items) => items.filter((item) => item !== value.trim()));
|
|
setValue("");
|
|
} catch (caught) { setError(caught instanceof Error ? caught.message : t("settings.general.workspaceReposError", "Could not add repository.")); }
|
|
};
|
|
return <section className="workspace-repos-card card" data-testid="workspace-repos-card">
|
|
<h3>{t("settings.general.workspaceRepos", "Workspace repositories")}</h3>
|
|
<ul>{repos.map((repo) => <li key={repo}>{repo}</li>)}</ul>
|
|
<div className="workspace-repos-card__controls">
|
|
<input className="input" list="workspace-repo-candidates" value={value} onChange={(event) => setValue(event.target.value)} placeholder={t("settings.general.workspaceReposPlaceholder", "Repository directory")} />
|
|
<datalist id="workspace-repo-candidates">{available.map((repo) => <option key={repo} value={repo} />)}</datalist>
|
|
<button type="button" className="btn" data-testid="workspace-repo-add-button" onClick={() => void add()}>{t("settings.general.workspaceReposAdd", "Add")}</button>
|
|
</div>
|
|
{error && <p className="workspace-repos-card__error" role="alert">{error}</p>}
|
|
</section>;
|
|
}
|