Files
fusion/packages/dashboard/app/components/settings/sections/WorkspaceReposCard.tsx
gsxdsm 2a31505823 FN-9163: Add repositories to existing workspace tasks
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>
2026-08-19 19:59:04 -07:00

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>;
}