fix(ship): return commitSha + coolifyDeployUrl, prevent verification chain
After "ship" succeeded the AI was burning 7+ follow-up tool calls (gitea_repos_list, gitea_credentials, shell.exec×4, apps_list) trying to verify what actually got pushed and where it deployed. That ate through MAX_TOOL_ROUNDS and the user got tool-icon spam with no narrative summary. Three fixes: 1. ship now returns commitSha (parsed from `git rev-parse HEAD`), giteaCommitUrl, giteaBranchUrl, coolifyDeployUrl, coolifyAppUuid, and a summaryHint string telling the AI exactly what to say next. 2. ship's tool description now explicitly tells Gemini "do NOT call gitea_*, shell_exec, or apps_* afterwards to verify — the result is authoritative." 3. MAX_TOOL_ROUNDS 12 → 18 as a safety net for genuinely long chains. Net effect: ship goes from ~12 tool calls to verify a deploy down to 1 (just ship itself), and the next text turn has the SHA + URL inline. Made-with: Cursor
This commit is contained in:
@@ -24,7 +24,7 @@ import type { ChatMessage, ToolCall } from '@/lib/ai/gemini-chat';
|
||||
// tool calls in one user turn. When the cap IS hit, we still emit a
|
||||
// narrative summary instead of leaving the user staring at a tool tray
|
||||
// (see the no-tools follow-up call below).
|
||||
const MAX_TOOL_ROUNDS = 12;
|
||||
const MAX_TOOL_ROUNDS = 18;
|
||||
|
||||
let chatTablesReady = false;
|
||||
async function ensureChatTables() {
|
||||
|
||||
@@ -3458,6 +3458,8 @@ async function toolShip(principal: Principal, params: Record<string, any>) {
|
||||
const apiHost = new URL(GITEA_API_URL).host;
|
||||
const remote = `https://${creds.username}:${creds.token}@${apiHost}/${creds.org}/${repo}.git`;
|
||||
|
||||
// Capture the resulting HEAD SHA on stdout in a parseable form so we
|
||||
// can return it to the caller without a second exec round-trip.
|
||||
const cmd = `set -e
|
||||
cd /workspace
|
||||
if [ ! -d .git ]; then
|
||||
@@ -3474,16 +3476,21 @@ if git diff --cached --quiet HEAD 2>/dev/null; then
|
||||
else
|
||||
git commit -q -m ${shq(message)}
|
||||
fi
|
||||
git push -u origin HEAD:${shq(branch)} 2>&1 | tail -5`;
|
||||
git push -u origin HEAD:${shq(branch)} 2>&1 | tail -5
|
||||
echo "VIBN_SHIP_SHA=$(git rev-parse HEAD)"`;
|
||||
|
||||
let pushOutput = '';
|
||||
let commitSha: string | null = null;
|
||||
try {
|
||||
const r = await execInDevContainer({
|
||||
projectId: project.id,
|
||||
command: cmd,
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
pushOutput = (r.stdout + r.stderr).trim();
|
||||
const combined = (r.stdout + r.stderr).trim();
|
||||
const shaMatch = combined.match(/VIBN_SHIP_SHA=([0-9a-f]{7,40})/);
|
||||
commitSha = shaMatch ? shaMatch[1] : null;
|
||||
pushOutput = combined.replace(/VIBN_SHIP_SHA=[0-9a-f]+\s*$/, '').trim();
|
||||
if (r.code !== 0) {
|
||||
return NextResponse.json(
|
||||
{ error: `git push failed: ${pushOutput}` },
|
||||
@@ -3497,23 +3504,45 @@ git push -u origin HEAD:${shq(branch)} 2>&1 | tail -5`;
|
||||
);
|
||||
}
|
||||
|
||||
// Build verification URLs the AI can hand to the user without
|
||||
// additional tool calls (the previous behaviour cost ~7 follow-up
|
||||
// calls per ship: gitea.repos.list, gitea.credentials, multiple
|
||||
// shell.exec verifications, apps_list).
|
||||
const giteaWebHost = new URL(GITEA_API_URL).host.replace(/^api\./, '');
|
||||
const giteaCommitUrl = commitSha
|
||||
? `https://${giteaWebHost}/${creds.org}/${repo}/commit/${commitSha}`
|
||||
: null;
|
||||
const giteaCompareUrl = `https://${giteaWebHost}/${creds.org}/${repo}/commits/branch/${branch}`;
|
||||
|
||||
// Trigger Coolify deploy if the project is linked to one.
|
||||
let deploymentUuid: string | null = null;
|
||||
let coolifyDeployUrl: string | null = null;
|
||||
const linkedAppUuid =
|
||||
typeof project.data?.coolifyAppUuid === 'string' && project.data.coolifyAppUuid.trim()
|
||||
? project.data.coolifyAppUuid.trim()
|
||||
: null;
|
||||
const coolifyHost = process.env.COOLIFY_BASE_URL
|
||||
? new URL(process.env.COOLIFY_BASE_URL).host
|
||||
: null;
|
||||
if (linkedAppUuid && Boolean(params.deploy ?? true)) {
|
||||
try {
|
||||
const ownedUuids = await getOwnedCoolifyProjectUuids(principal.workspace);
|
||||
await getApplicationInWorkspace(linkedAppUuid, ownedUuids);
|
||||
const dep = await deployApplication(linkedAppUuid, { force: false });
|
||||
deploymentUuid = dep.deployment_uuid;
|
||||
if (coolifyHost && deploymentUuid) {
|
||||
coolifyDeployUrl = `https://${coolifyHost}/project/${linkedAppUuid}/deployments/${deploymentUuid}`;
|
||||
}
|
||||
} catch (err) {
|
||||
return NextResponse.json({
|
||||
result: {
|
||||
repo,
|
||||
branch,
|
||||
message,
|
||||
pushed: true,
|
||||
commitSha,
|
||||
pushOutput,
|
||||
giteaCommitUrl,
|
||||
deploymentTriggered: false,
|
||||
deployError: err instanceof Error ? err.message : String(err),
|
||||
},
|
||||
@@ -3527,14 +3556,21 @@ git push -u origin HEAD:${shq(branch)} 2>&1 | tail -5`;
|
||||
branch,
|
||||
message,
|
||||
pushed: true,
|
||||
commitSha,
|
||||
pushOutput,
|
||||
giteaCommitUrl,
|
||||
giteaBranchUrl: giteaCompareUrl,
|
||||
deploymentTriggered: Boolean(deploymentUuid),
|
||||
deploymentUuid,
|
||||
hint: deploymentUuid
|
||||
? 'Deploy in progress; poll apps_deployments to track.'
|
||||
coolifyDeployUrl,
|
||||
coolifyAppUuid: linkedAppUuid,
|
||||
// Tell the AI exactly what to say in the next text turn so it
|
||||
// doesn't waste tool rounds verifying.
|
||||
summaryHint: deploymentUuid
|
||||
? `Pushed commit ${commitSha?.slice(0, 7) ?? '?'} to ${repo}@${branch} and triggered Coolify deployment ${deploymentUuid}. Show the user commitSha (full or short) and coolifyDeployUrl. Do NOT call additional tools to verify.`
|
||||
: linkedAppUuid
|
||||
? 'Deploy was skipped (deploy=false).'
|
||||
: 'No Coolify app linked to this project yet — call apps_create to wire one up before the next ship.',
|
||||
? `Pushed commit ${commitSha?.slice(0, 7) ?? '?'}, deploy was skipped per deploy=false. Show commitSha and giteaCommitUrl.`
|
||||
: `Pushed commit ${commitSha?.slice(0, 7) ?? '?'}. No Coolify app linked yet — tell the user to call apps_create once before the next ship. Show commitSha and giteaCommitUrl.`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -811,7 +811,10 @@ Auto-domain {name}.{workspace}.vibnai.com is assigned automatically.`,
|
||||
description:
|
||||
'Graduate the project from dev container to production. Commits everything in /workspace, pushes to the project Gitea repo, ' +
|
||||
'and triggers a Coolify production deploy if the project is linked to one. Use when the user says "ship it", "deploy this", ' +
|
||||
'or after a stable working state has been verified via dev_server_*. Pass `commitMsg` for a meaningful commit; otherwise an ISO-timestamp message is used.',
|
||||
'or after a stable working state has been verified via dev_server_*. Pass `commitMsg` for a meaningful commit; otherwise an ISO-timestamp message is used. ' +
|
||||
'Returns { commitSha, giteaCommitUrl, deploymentUuid, coolifyDeployUrl, summaryHint }. ' +
|
||||
'IMPORTANT: do NOT call gitea_*, shell_exec, or apps_* afterwards to verify — the result is authoritative. ' +
|
||||
'Just report commitSha + coolifyDeployUrl to the user.',
|
||||
parameters: {
|
||||
type: 'OBJECT',
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user