chore(telemetry): flatten the project slug layer and remove cd path instructions from system prompt
This commit is contained in:
@@ -34,21 +34,21 @@
|
||||
* widen it.
|
||||
*/
|
||||
|
||||
import { execInDevContainer } from '@/lib/dev-container';
|
||||
import { execInDevContainer } from "@/lib/dev-container";
|
||||
|
||||
const GITEA_API_URL = process.env.GITEA_API_URL ?? '';
|
||||
const GITEA_API_URL = process.env.GITEA_API_URL ?? "";
|
||||
// Falls back to GITEA_ADMIN_USER because production historically only set the
|
||||
// admin var; missing GITEA_USERNAME used to silently disable auto-clone.
|
||||
const GITEA_USERNAME =
|
||||
process.env.GITEA_USERNAME || process.env.GITEA_ADMIN_USER || '';
|
||||
const GITEA_API_TOKEN = process.env.GITEA_API_TOKEN ?? '';
|
||||
process.env.GITEA_USERNAME || process.env.GITEA_ADMIN_USER || "";
|
||||
const GITEA_API_TOKEN = process.env.GITEA_API_TOKEN ?? "";
|
||||
|
||||
const AI_GIT_AUTHOR_NAME = 'Vibn AI';
|
||||
const AI_GIT_AUTHOR_EMAIL = 'ai@vibnai.com';
|
||||
const AI_GIT_AUTHOR_NAME = "Vibn AI";
|
||||
const AI_GIT_AUTHOR_EMAIL = "ai@vibnai.com";
|
||||
|
||||
/** Where each project's repo lives inside the dev container. */
|
||||
export function projectRepoPath(projectSlug: string): string {
|
||||
return `/workspace/${projectSlug}`;
|
||||
return `/workspace`;
|
||||
}
|
||||
|
||||
function isGiteaConfigured(): boolean {
|
||||
@@ -100,10 +100,14 @@ export async function ensureProjectRepoCloned(
|
||||
opts: EnsureRepoClonedOpts,
|
||||
): Promise<EnsureRepoClonedResult> {
|
||||
if (!isGiteaConfigured()) {
|
||||
return { cloned: false, alreadyPresent: false, reason: 'gitea_not_configured' };
|
||||
return {
|
||||
cloned: false,
|
||||
alreadyPresent: false,
|
||||
reason: "gitea_not_configured",
|
||||
};
|
||||
}
|
||||
if (!opts.giteaCloneUrl) {
|
||||
return { cloned: false, alreadyPresent: false, reason: 'no_clone_url' };
|
||||
return { cloned: false, alreadyPresent: false, reason: "no_clone_url" };
|
||||
}
|
||||
|
||||
const repoDir = projectRepoPath(opts.projectSlug);
|
||||
@@ -121,18 +125,18 @@ export async function ensureProjectRepoCloned(
|
||||
const probe = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
command:
|
||||
`if [ -d ${shellQ(repoDir + '/.git')} ]; then echo git; ` +
|
||||
`if [ -d ${shellQ(repoDir + "/.git")} ]; then echo git; ` +
|
||||
`elif [ -d ${shellQ(repoDir)} ]; then echo dir; ` +
|
||||
`else echo absent; fi`,
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
const probeState = probe.stdout.trim();
|
||||
|
||||
if (probeState === 'git') {
|
||||
if (probeState === "git") {
|
||||
return { cloned: false, alreadyPresent: true };
|
||||
}
|
||||
|
||||
if (probeState === 'dir') {
|
||||
if (probeState === "dir") {
|
||||
// Init in place, hook up the remote, fetch, set tracking. We
|
||||
// don't try to merge — the directory's contents become whatever
|
||||
// the next auto-commit-and-push picks up. If the remote has
|
||||
@@ -147,7 +151,7 @@ export async function ensureProjectRepoCloned(
|
||||
// Best-effort fetch; if remote is empty this errors out and
|
||||
// we proceed anyway. The `|| true` keeps the chain going.
|
||||
`(git fetch origin main 2>/dev/null && git reset --soft origin/main 2>/dev/null) || true`,
|
||||
].join(' && ');
|
||||
].join(" && ");
|
||||
|
||||
const result = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
@@ -182,7 +186,7 @@ export async function ensureProjectRepoCloned(
|
||||
`git config user.email ${shellQ(AI_GIT_AUTHOR_EMAIL)}`,
|
||||
`git remote set-url origin ${shellQ(authed)}`,
|
||||
`cd /workspace && mv ${shellQ(tmpDir)} ${shellQ(repoDir)}`,
|
||||
].join(' && ');
|
||||
].join(" && ");
|
||||
|
||||
const result = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
@@ -201,7 +205,11 @@ export async function ensureProjectRepoCloned(
|
||||
// (warning: "remote HEAD refers to nonexistent ref"), clone
|
||||
// didn't actually fail — just there's no history to check
|
||||
// out. Make an empty dir + init so subsequent commits land.
|
||||
if (/empty|warning: You appear to have cloned an empty/i.test(result.stderr + result.stdout)) {
|
||||
if (
|
||||
/empty|warning: You appear to have cloned an empty/i.test(
|
||||
result.stderr + result.stdout,
|
||||
)
|
||||
) {
|
||||
const initEmpty = [
|
||||
`mkdir -p ${shellQ(repoDir)}`,
|
||||
`cd ${shellQ(repoDir)}`,
|
||||
@@ -209,7 +217,7 @@ export async function ensureProjectRepoCloned(
|
||||
`git config user.name ${shellQ(AI_GIT_AUTHOR_NAME)}`,
|
||||
`git config user.email ${shellQ(AI_GIT_AUTHOR_EMAIL)}`,
|
||||
`git remote add origin ${shellQ(authed)}`,
|
||||
].join(' && ');
|
||||
].join(" && ");
|
||||
const initR = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
command: initEmpty,
|
||||
@@ -260,21 +268,21 @@ export async function commitAndPushIfDirty(
|
||||
opts: CommitAndPushOpts,
|
||||
): Promise<CommitAndPushResult> {
|
||||
const repoDir = projectRepoPath(opts.projectSlug);
|
||||
const message = (opts.message ?? '').trim() || 'AI checkpoint';
|
||||
const message = (opts.message ?? "").trim() || "AI checkpoint";
|
||||
// Sanitize: keep messages single-line and bounded so we can't be
|
||||
// tricked into shell-escape or commit-message injection.
|
||||
const safeMessage = message.replace(/[\r\n]+/g, ' ').slice(0, 200);
|
||||
const safeMessage = message.replace(/[\r\n]+/g, " ").slice(0, 200);
|
||||
|
||||
// Bail fast if there's no repo to commit against. Don't treat as
|
||||
// an error — projects without a clone (e.g. cloning failed earlier)
|
||||
// should still be able to chat.
|
||||
const probe = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
command: `test -d ${shellQ(repoDir + '/.git')} && echo present || echo absent`,
|
||||
command: `test -d ${shellQ(repoDir + "/.git")} && echo present || echo absent`,
|
||||
timeoutMs: 5_000,
|
||||
}).catch(() => null);
|
||||
if (!probe || probe.stdout.trim() !== 'present') {
|
||||
return { committed: false, pushed: false, reason: 'no_repo' };
|
||||
if (!probe || probe.stdout.trim() !== "present") {
|
||||
return { committed: false, pushed: false, reason: "no_repo" };
|
||||
}
|
||||
|
||||
const cmd = [
|
||||
@@ -287,21 +295,24 @@ export async function commitAndPushIfDirty(
|
||||
`SHA=$(git rev-parse --short HEAD)`,
|
||||
`git push -u origin HEAD 2>&1 | tail -n 5`,
|
||||
`echo COMMITTED $SHA`,
|
||||
].join(' && ');
|
||||
].join(" && ");
|
||||
|
||||
const result = await execInDevContainer({
|
||||
projectId: opts.projectId,
|
||||
command: cmd,
|
||||
timeoutMs: 30_000,
|
||||
}).catch((err: unknown) => ({
|
||||
stdout: '',
|
||||
stderr: err instanceof Error ? err.message : String(err),
|
||||
exitCode: -1,
|
||||
} satisfies { stdout: string; stderr: string; exitCode: number }));
|
||||
}).catch(
|
||||
(err: unknown) =>
|
||||
({
|
||||
stdout: "",
|
||||
stderr: err instanceof Error ? err.message : String(err),
|
||||
exitCode: -1,
|
||||
}) satisfies { stdout: string; stderr: string; exitCode: number },
|
||||
);
|
||||
|
||||
const out = result.stdout || '';
|
||||
if (out.includes('CLEAN')) {
|
||||
return { committed: false, pushed: false, reason: 'clean' };
|
||||
const out = result.stdout || "";
|
||||
if (out.includes("CLEAN")) {
|
||||
return { committed: false, pushed: false, reason: "clean" };
|
||||
}
|
||||
const m = /COMMITTED\s+([0-9a-f]+)/.exec(out);
|
||||
if (m) {
|
||||
|
||||
Reference in New Issue
Block a user