From c644ec8d01539cd38b5b2df042455426d0084bab Mon Sep 17 00:00:00 2001 From: -Puter <22245429+puterhimself@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:36:56 +0530 Subject: [PATCH] feat(git): thin project onboarding, provider integration, and AgentOS repository access Effect primitives: - git-provider: GitProvider, connection states, normalized errors, URL normalization, host compatibility, credential freshness window - git-provisioning: validated Puter commands, migration states, idempotency keys, safe replacement rules, owner-safe guards - git-webhook: supported events, signature verification, delivery states - host-repository: provider-neutral credential-safe clone via GIT_ASKPASS Normalized Convex schema: - gitProviderAccounts, refined gitConnections, gitProviderOrganizations, gitRepositories, gitMigrations, gitWebhookDeliveries - projects: gitRepositoryId + instructions fields - Schema fields optional for backward compatibility, with backfill cron Backend: - Connection health: verify action, hourly reconciliation (covers stale active + reauth-required + undefined-state legacy connections) - Puter provisioning: createPuterUser/Organization/Repository with owner binding, startGithubMigration (durable via scheduler), getMigration - Org ownership: explicit member add with admin role + verification - Webhook HTTP actions: HMAC verification, delivery persistence with idempotency, repository resolution, byte-length payload limit - Automatic Puter webhook creation after repo creation/migration with fail-loud state tracking - Repository sync after connection (Gitea + GitHub) - AgentOS execution resolves gitRepositoryId for real clone URL - Credential gating: state + freshness checks before execution and project creation - listForOrganization for cross-project Work filtering Frontend: - /projects onboarding page with GitHub OAuth (linkSocial) and Puter PAT - Zero-project redirect, repository selection, context editor - Provider-aware settings panel (no serverUrl for Puter) - Project selection via ?project= query param - GitHub scopes: repo + read:org Agent runtime: - Clones user repository with GIT_ASKPASS credential helper (no token in URL, args, or git config), provider-aware username - Removed fixed Zopu source path and .env copy --- .../src/components/projects/projects-page.tsx | 465 +++++ .../workspace/project-settings-panel.tsx | 10 +- .../hooks/workspace/use-project-workspace.ts | 24 +- apps/web/src/lib/workspace/types.ts | 1 - apps/web/src/routes.ts | 5 +- apps/web/src/routes/app/layout.tsx | 25 +- apps/web/src/routes/app/projects/page.tsx | 5 + docs/git-provider-setup.md | 128 ++ package.json | 52 + packages/agents/src/runtime/agent-os.ts | 17 +- .../agents/src/runtime/host-repository.ts | 88 +- packages/backend/convex/auth.ts | 1 + packages/backend/convex/authz.ts | 21 + packages/backend/convex/convex.config.ts | 3 + packages/backend/convex/crons.ts | 21 + packages/backend/convex/gitConnectionData.ts | 143 +- .../backend/convex/gitConnectionHealth.ts | 243 +++ packages/backend/convex/gitConnections.ts | 197 ++- packages/backend/convex/gitProvisioning.ts | 1538 +++++++++++++++++ packages/backend/convex/gitWebhooks.ts | 254 +++ packages/backend/convex/http.ts | 13 + packages/backend/convex/projects.ts | 36 +- packages/backend/convex/schema.ts | 156 +- packages/backend/convex/workExecutionAgent.ts | 26 +- .../convex/workExecutionWorkflow.test.ts | 27 +- .../backend/convex/workExecutionWorkflow.ts | 28 +- packages/backend/convex/workPlanning.ts | 31 +- packages/env/src/convex.ts | 3 + packages/primitives/package.json | 8 +- packages/primitives/src/execution-runtime.ts | 7 +- packages/primitives/src/git-provider.test.ts | 229 +++ packages/primitives/src/git-provider.ts | 393 +++++ packages/primitives/src/git-provisioning.ts | 366 ++++ packages/primitives/src/git-webhook.ts | 184 ++ packages/primitives/src/host-repository.ts | 188 ++ packages/primitives/src/index.ts | 4 + 36 files changed, 4821 insertions(+), 119 deletions(-) create mode 100644 apps/web/src/components/projects/projects-page.tsx create mode 100644 apps/web/src/routes/app/projects/page.tsx create mode 100644 docs/git-provider-setup.md create mode 100644 packages/backend/convex/gitConnectionHealth.ts create mode 100644 packages/backend/convex/gitProvisioning.ts create mode 100644 packages/backend/convex/gitWebhooks.ts create mode 100644 packages/primitives/src/git-provider.test.ts create mode 100644 packages/primitives/src/git-provider.ts create mode 100644 packages/primitives/src/git-provisioning.ts create mode 100644 packages/primitives/src/git-webhook.ts create mode 100644 packages/primitives/src/host-repository.ts diff --git a/apps/web/src/components/projects/projects-page.tsx b/apps/web/src/components/projects/projects-page.tsx new file mode 100644 index 0000000..56accd7 --- /dev/null +++ b/apps/web/src/components/projects/projects-page.tsx @@ -0,0 +1,465 @@ +import { authClient } from "@code/auth/web"; +import { api } from "@code/backend/convex/_generated/api"; +import type { Id } from "@code/backend/convex/_generated/dataModel"; +import { Button } from "@code/ui/components/button"; +import { useAction, useMutation, useQuery } from "convex/react"; +import { makeFunctionReference } from "convex/server"; +import { + FileText, + FolderGit2, + GitBranch, + LoaderCircle, + Server, +} from "lucide-react"; +import { useEffect, useState } from "react"; +import { Link, useNavigate, useSearchParams } from "react-router"; + +const connectGithubRef = makeFunctionReference< + "action", + Record, + { connectionId: Id<"gitConnections"> } +>("gitConnections:connectGithub"); +const connectGiteaRef = makeFunctionReference< + "action", + { token: string; username?: string }, + { connectionId: Id<"gitConnections"> } +>("gitConnections:connectGitea"); +const createProjectFromRepositoryRef = makeFunctionReference< + "mutation", + { gitRepositoryId: Id<"gitRepositories">; name: string }, + { projectId: Id<"projects"> } +>("gitProvisioning:createProjectFromRepository"); + +const LoadingState = () => ( +
+ +
+); + +const EmptyProjects = () => ( +
+ + + +

+ Connect your first project +

+

+ Choose a Git provider below to get started. +

+
+); + +const ProjectCard = ({ + instructions, + name, + projectId, + sourceUrl, +}: { + instructions: string; + name: string; + projectId: string; + sourceUrl: string; +}) => ( + +
+ +

{name}

+
+

{sourceUrl}

+ {instructions ? ( +
+ + Context configured +
+ ) : null} + +); + +const ContextEditor = ({ + projectId, +}: { + readonly projectId: Id<"projects">; +}) => { + const instructions = useQuery(api.projects.getInstructions, { + projectId, + }); + const updateInstructions = useMutation(api.projects.updateInstructions); + const [draft, setDraft] = useState(); + const [saving, setSaving] = useState(false); + + const currentDraft = draft ?? instructions ?? ""; + const dirty = draft !== undefined && draft !== (instructions ?? ""); + + const save = async () => { + if (!dirty || saving) { + return; + } + setSaving(true); + try { + await updateInstructions({ + instructions: draft ?? "", + projectId, + }); + setDraft(undefined); + } finally { + setSaving(false); + } + }; + + return ( +
+ +