/** * PublishDeployToolsNudge — mid-run reminder when the user asked to publish * a package or deploy to production, but no matching publish/deploy exec ran. * * Soft: max 1 fire. Pairs with evaluateInventedPublishDeployCompletionGate. */ export const PUBLISH_DEPLOY_TOOLS_NUDGE_MAX_ATTEMPTS = 1; const PUBLISH_DEPLOY_USER_RE = /(?:\bnpm publish\b|\bpublish (?:the )?(?:package|release)\b|\bdeploy (?:to )?(?:prod|production|staging)\b|\bship (?:to )?(?:prod|production)\b|发布到 npm|发布包|部署到|上线)/iu; const EXEC_TOOLS = new Set(['exec', 'exec_background']); export interface PublishDeployToolsNudgeRequest { userText: string; toolCallsByName: Record; messages?: Array<{ role?: string; content?: unknown }>; totalToolCalls: number; attempts: number; } export type PublishDeployToolsNudgeResult = | { fire: false } | { fire: true; correction: string }; function collectExecCommands( messages: Array<{ role?: string; content?: unknown }> | undefined, ): string[] { if (!messages?.length) return []; const out: string[] = []; for (const m of messages) { if (!m || m.role !== 'assistant' || !Array.isArray(m.content)) continue; for (const block of m.content) { const b = block as { type?: string; name?: string; input?: unknown }; if (b?.type !== 'tool_use' || !b.name || !EXEC_TOOLS.has(b.name)) continue; const input = b.input; if (!input || typeof input !== 'object') continue; const o = input as Record; for (const key of ['command', 'cmd', 'input'] as const) { if (typeof o[key] === 'string' && String(o[key]).trim()) { out.push(String(o[key])); break; } } } } return out; } function sawPublishOrDeployExec( messages: Array<{ role?: string; content?: unknown }> | undefined, ): boolean { for (const cmd of collectExecCommands(messages)) { if ( /\b(?:npm|pnpm|yarn|bun)\s+publish\b/i.test(cmd) || /\bcargo\s+publish\b/i.test(cmd) || /\btwine\s+upload\b/i.test(cmd) || /\b(?:kubectl|helm)\s+(?:apply|upgrade|install|rollout)\b/i.test(cmd) || /\b(?:vercel|netlify|flyctl|gcloud|aws|terraform|pulumi)\b/i.test(cmd) || /\b(?:deploy|gh\s+workflow\s+run)\b/i.test(cmd) ) { return true; } } return false; } export function evaluatePublishDeployToolsNudge( request: PublishDeployToolsNudgeRequest, ): PublishDeployToolsNudgeResult { if (request.attempts >= PUBLISH_DEPLOY_TOOLS_NUDGE_MAX_ATTEMPTS) return { fire: false }; if (request.totalToolCalls < 1) return { fire: false }; if (sawPublishOrDeployExec(request.messages)) return { fire: false }; const user = (request.userText || '').trim(); if (!user || !PUBLISH_DEPLOY_USER_RE.test(user)) return { fire: false }; // Conceptual "how do I publish?" without asking to do it now. if ( /(?:how (?:do|to)|what is|文档|原理|介绍)/iu.test(user) && !/(?:please|now|go ahead|现在|请|帮我)/iu.test(user) ) { return { fire: false }; } return { fire: true, correction: '[System] The user asked to publish a package or deploy, and tools have already run without a matching ' + 'publish/deploy command (`npm publish`, `cargo publish`, `kubectl apply`, `vercel`, etc.). ' + 'Run the real command via `exec` and report its output, or clearly wait for approval / say it was not published. ' + 'Do not invent release or deployment outcomes.', }; }