Andy ops fixes: 10 issues (skills, Draft+Approve, briefings, timeouts, FB auto-approve, CRM)#2178
Closed
blaykeelder1-commits wants to merge 118 commits intoqwibitai:mainfrom
Closed
Andy ops fixes: 10 issues (skills, Draft+Approve, briefings, timeouts, FB auto-approve, CRM)#2178blaykeelder1-commits wants to merge 118 commits intoqwibitai:mainfrom
blaykeelder1-commits wants to merge 118 commits intoqwibitai:mainfrom
Conversation
Add Docker support to container-runner (auto-detects Linux vs macOS), CRM database tables (contacts, outreach_log, campaigns), email/social/CRM tools, agent skills for outreach workflows, and Contabo VPS deployment scripts with security hardening and systemd service. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds tools/sheets/ for reading/writing Google Sheets via service account, container/skills/vending-inventory/ for daily sales tracking automation, and container/skills/google-sheets/ for spreadsheet operations. Updates Dockerfile with googleapis dep, container-runner with Sheets env vars, and deploy script with .env placeholders. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Baileys v7 waits 20s for a history sync notification that never arrives for already-synced linked devices, leaving the event pipeline in a broken state. Setting shouldSyncHistoryMessage to always return false skips this wait entirely and goes straight to online. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match official repo exactly - no extra options. Let history sync timeout naturally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Mount security: use exact match instead of substring matching to prevent false positives (e.g. "key" blocking "monkey"), add secrets.json/token.json/.ssh-agent - Container spawn: add --cap-drop=ALL and --no-new-privileges for Docker - Secret sanitization: expand bash env stripping from 2 to 17 keys to match readSecrets() - Output markers: use per-run random nonce to prevent marker injection attacks - Dockerfile: remove curl/git after build, verify tsc output before running - .env permissions: warn if file is group/world-readable on Linux/macOS Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
google.auth.fromJSON() was not passing credentials properly, causing "unregistered callers" errors. Switch to explicit JWT constructor with options object. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New calendar CLI tool (list-events, create-event, update-event, delete-event, free-busy) - New google-calendar skill for agent containers - Add GOOGLE_CALENDAR_ID to secrets pipeline (container-runner + agent-runner) - Create snak-group and trailer-rental CLAUDE.md templates (gitignored, deployed separately) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use --security-opt=no-new-privileges instead of --no-new-privileges, which is the correct flag format for Docker 29+. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add platform detection to ensureContainerSystemRunning() for Docker on Linux - Replace stale data/registered_groups.json refs with SQLite + MCP tools in main CLAUDE.md - Add business operations (Snak Group, Trailer Rental) to main channel instructions - Fix schedule_task 'once' error message contradicting tool description (said use Z, should say don't) - Add requires_trigger param to register_group MCP tool for personal/solo chats - Rewrite vending-inventory skill with detailed platform navigation and reorder decision matrix - Add googleapis and nodemailer dependencies Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Refactor to multi-channel architecture (channels[] array, routeOutbound) - Add Quo Phone (OpenPhone) SMS channel with webhook server on port 3100 - Add Groq Whisper voice message transcription - Add WhatsApp image/document download and media passthrough - Pre-compile container agent (skip runtime tsc, saves 3-5s/invocation) - Add usage_log table for token/cost tracking - Add CRM auto-create contacts from inbound SMS - Document all new env vars (Quo, Groq, IDLE_TIMEOUT) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Quo API uses 'text' not 'body' for message content, and 'to' can be an array. Added request-level logging to debug webhook delivery issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Polls Quo conversations API every 15 seconds for new messages. Deduplicates with webhook-delivered messages via message ID set. Keeps webhook server running as primary path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After service restart, the in-memory lastSenderByJid map is empty. Now falls back to querying the last non-bot sender from the DB. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rospecting - Add deals + deal_stage_log tables with pipeline tracking (new → qualified → appointment_booked → proposal → closed_won/closed_lost) - Add channel_source column to contacts for channel-aware follow-ups - Create tools/crm/pipeline.ts CLI for deal management (create, move, list, health, get, history) - Create tools/crm/unsubscribe.ts for bounce/opt-out handling with do-not-contact tagging - Create tools/iddi/iddi.ts wrapping IDDI REST API (inventory, expiring, redistribution, shopping-list, top-products, swipe-results, analytics) - Create iddi-inventory skill with three-data-source documentation - Add IDDI secrets (IDDI_BASE_URL, IDDI_EMAIL, IDDI_PASSWORD) to container-runner readSecrets() - Add follow-up count cap (<3 touches) and do-not-contact filter to query-contacts.ts - Update crm-query and outreach-workflow skills with pipeline + unsubscribe tools - Add reputation guards and reply tracking to outreach-workflow skill - Add deploy/upgrade-agent.sh for VPS deployment with SQL migrations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… import - Google Drive tool (list/search/download files) - Google Maps Places API lead finder with CRM import - Website email/phone scraper with batch mode - Lead scoring engine (0-100) with configurable weights - Apollo importer: direct Google Sheets support + flexible column mapping - DB migrations: lead_score, website, address, city, state, google_place_id, industry - Lead finder + Google Drive skills for container agents - Updated CLAUDE.md, crm-query skill, snak-group CLAUDE.md with lead gen docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix IDLE_TIMEOUT default from 30min to 5min (was same as CONTAINER_TIMEOUT, causing containers to always be hard-killed instead of graceful shutdown) - Fix message cursor: defer lastAgentTimestamp advancement until agent succeeds, preventing permanent message loss on timeout - Add webhook rate limiting (30 req/min/IP) - Add webhook signature verification (HMAC-SHA256 via openphone-signature) - Add Zod schema validation for webhook payloads - Add 1MB body size limit on webhook requests - Add nginx reverse proxy config with TLS, rate limiting, security headers - Add fail2ban filter/jail for webhook abuse detection - Add structured audit logging for security events - Update setup-vps.sh: nginx/certbot install, close port 3100 externally - Document secret rotation procedure and webhook security in SECURITY.md - Mark bugs #2 and qwibitai#3 as FIXED in DEBUG_CHECKLIST.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Baileys default version was outdated, causing WhatsApp servers to reject connections with 405. Now fetches the latest version at startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…owser identity Baileys wiki explicitly warns against fetchLatestWaWebVersion as it causes incompatibility when protobufs are behind. Multiple GitHub issues confirm macOS browser identity causes pairing failures on Linux hosts. Use Baileys defaults for version and Linux browser tuple. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Same fix as main whatsapp.ts — Browsers.macOS causes pairing failures on Linux hosts. Use consistent NanoClaw/Chrome/22.04 browser identity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Baileys npm release (7.0.0-rc.9) has stale default WA version causing 405 errors. Install from GitHub main for updated protobufs, use fetchLatestWaWebVersion for current version, and Ubuntu browser identity which is proven to work on Linux VPS hosts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Health monitor (every 5 min): - Detects WA protocol error loops (405/515) and force-reconnects - Checks message silence during active hours, auth state - Writes health_snapshot.json for Andy's daily reports - Alerts main group if critical for 30+ min deploy/deploy.sh: safe deploy with auto-rollback on build/test failure, service verification, and backup rotation. Andy gets daily 9am health check and weekly Monday 10am dependency audit as seeded scheduled tasks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Scheduler auto-advances stale cron tasks (>10 min past due) instead of endlessly re-queueing them 2. Queue distinguishes scheduled tasks from message containers — messages always get their own container, never blocked by a scheduled task running on the same JID 3. Health monitor auto-fixes cron tasks stuck >30 min in the past Root cause: scheduled tasks from other groups (sheridan-rentals) using the main group's chat_jid blocked real messages from being processed, because the queue keyed active state by JID alone. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Route scheduled tasks to Haiku ($1/M input), interactive to Sonnet ($3/M) - Add per-query budget caps: $0.05 scheduled, $0.50 interactive - Pass model + maxBudgetUsd through ContainerInput to Agent SDK query() - Add self-improvement system: global CLAUDE.md instructions + per-group lessons.md - Lessons cover customer service, conversion, issue resolution, efficiency - Include linter/security improvements across codebase Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, Gmail API - Mount tools/ read-only for non-main groups so skills work outside main - Increase Docker memory to 1GB and pids-limit to 512 for browser automation - Add per-task model/budget_usd columns to scheduled_tasks - Task scheduler uses task-level overrides, falls back to global defaults - Add Gmail API tool with domain-wide delegation (list/search/read/send/reply) - Add actionable 401/403/404 error hints to sheets, calendar, drive tools - Add BUDGET_SCHEDULED_HEAVY config for complex scheduled tasks - Deploy script upgrades briefing/vending tasks to Sonnet + $0.50 budget Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- list command auto-excludes promotions, social, updates, forums, noreply/newsletter senders via Gmail search query - --no-filter true to bypass when owner wants everything - Skill instructions tell Andy to only read business-relevant emails and skip automated/mass-sent messages to save credits Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Prompt injection defense in groups/global/CLAUDE.md
- Email/SMS/web content treated as untrusted data
- Agent will never follow instructions found in emails
- Only sends to addresses from owner-info.md or direct chat requests
2. Daily email send rate limiting (20/day default)
- Shared counter in tools/shared/send-rate-limit.ts
- Enforced in both gmail.ts and send-email.ts
- Hard reject above limit, resets at midnight UTC
3. Per-group secret scoping
- Non-main groups only get core + google + email secrets
- Social media, IDDI, lead gen keys restricted to main
- Groups can opt-in via containerConfig.extraSecretScopes
4. Bash secret sanitization synced with readSecrets()
- Added GMAIL_USER_EMAIL, GOOGLE_MAPS_API_KEY, IDDI_* to
SECRET_ENV_VARS in agent-runner
5. Entrypoint no longer writes secrets to temp file
- stdin piped directly to node via exec
- No /tmp/input.json race window
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New web channel (src/channels/web.ts): Socket.IO + Express server for website live chat, serves static widget files, handles concurrent visitors - Square payment tool (tools/square/square.ts): Create payment links, check payment status, list payments via Square API - Chat widget (widget/): Repurposed from rv-rental-chatbot, adapted for NanoClaw Socket.IO backend with session persistence - Updated config.ts with WEB_CHANNEL_PORT and WEB_CHANNEL_ORIGINS - Registered web channel in index.ts alongside WhatsApp and Quo - Relaxed folder UNIQUE constraint in db.ts to allow multiple channels per group (web + SMS both route to sheridan-rentals) - Added Square payments skill (container/skills/square-payments/SKILL.md) - Updated sheridan-rentals CLAUDE.md with web booking workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Google Calendar freebusy API requires valid RFC3339 datetime strings. The timestamps were missing a timezone suffix (e.g., 'Z'), causing every availability check to return "Bad Request" — dates never showed as booked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rovements - Add nanoclaw-alert systemd service for failure notifications - Enhanced health checks with comprehensive system verification - Marketing task registration and stagger scripts - WhatsApp channel and CLI runner reliability improvements - Group queue and task scheduler hardening Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ut statuses
Three systemic failures fixed:
1. Availability display only checked 'confirmed' bookings but checkout blocked
'pending' + 'paid' + 'confirmed' — calendar showed dates as free that would
reject at checkout. Now both check the same statuses, with eager pending
expiry on every availability request (not just every 5 min).
2. Embedded server expired bookings to 'expired' status, but standalone server's
hasOverlappingBooking used status != 'cancelled' — meaning expired bookings
permanently blocked dates forever. Unified to 'cancelled' and changed overlap
check to explicit status IN ('pending', 'paid', 'confirmed').
3. Standalone webhook silently dropped payments that arrived after pending
booking expiry — customer paid but booking never confirmed. Now reactivates
cancelled bookings when payment is received.
Also: standalone availability endpoint now checks DB + Calendar (was calendar-only),
checkout error returns blockedDates[] for frontend auto-refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ecklist Documents the two-server architecture, status consistency rules, eager expiry pattern, webhook late-payment handling, and step-by-step debugging checklist for booking availability issues. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ner emails 1. Clear balance to $0 in DB when balance payment webhook fires — previously balance stayed at original amount even after full payment, causing stale data in Sheets dashboard and emails. 2. Include 'paid' status in getActiveBookings() — deposit-only bookings with balance owed were excluded from active booking queries. 3. Owner payment email now says "Full Payment Received" vs "Deposit Received" based on actual payment type, with correct amount and balance info. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Square sends multiple payment.updated webhooks for the same order within seconds. The balance payment handler (status=paid + balance>0) was triggering on the duplicate, incorrectly promoting a deposit-only booking to confirmed with balance zeroed. Root cause found via Christopher's booking SR-402A1DF6 (logs show two payment.updated 2 seconds apart). Fix: balance payment handler now verifies the webhook's order ID differs from the booking's original order ID before treating it as a balance payment. Duplicate webhooks for the same order are explicitly ignored. Also: clearBalance() zeros the balance column after balance payment, getActiveBookings() includes 'paid' status, and owner email correctly labels deposit vs full payment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. All owner notifications now go to info@sheridantrailerrentals.us (routed to snakgroupteam). Removed blayke.elder1@gmail.com from balance-reminder.ts and square-payments.ts. 2. Owner gets alerted on EVERY balance reminder (7-day, 2-day, day-of) with urgency-tagged subject lines [REMINDER] / [URGENT] / [TODAY], not just on day-of pickup. 3. Customer reminder emails now include "If you've already submitted your payment, please disregard" to prevent irritation from timing gaps between payment and email sends. 4. Added sheridan-sheet-sync scheduled task (every 6 hours) so the Google Sheets dashboard stays current without depending on Andy. Also fixed Austin Saari booking SR-1DE9F6FB — deposit was paid but booking was expired before webhook arrived. Reactivated to 'paid' status, calendar event created, dates now blocked. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ent failure fixes
Response-time bottlenecks: reduce poll interval (2s→500ms), spawn cooldown
(10s→3s), cache .env reads via mtime, cache mount validation stat calls,
faster IPC polling (1s→250ms). All tunable via env vars.
HTTP resilience: new resilientFetch() utility with timeouts, exponential
backoff, and circuit breaker integration. Adopted across Messenger, OpenPhone,
Square, Groq, and booking service — no more hanging requests on third-party
slowness.
Silent failure fixes: replace 5 empty .catch(()=>{}) with logger.debug,
fix Square webhook to use timing-safe HMAC comparison, externalize hardcoded
booking health URL / WA queue cap / presence interval into config.
Codified 6 code standards in CLAUDE.md to prevent recurrence.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trailer leaving the lot without delivery requires higher security deposit ($500 vs $250) since there's more risk. Determined by checking if 'delivery' is in the add-ons array. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VPS runs Europe/Berlin, causing .toISOString() to shift CDT all-day event boundaries to wrong UTC dates. April 14 event at 19:00-05:00 became April 15 in UTC, falsely blocking checkout. Customer saw dates as available (frontend correct) but got rejected at payment. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d RIVER promo Frontend form.html and inspection.html already collected these fields but the API silently discarded them — customers saw promo/holiday pricing in the UI while Square was charged the regular rate. This ships the backend so the UI data actually flows through to pricing, DB, and disk. - pricing.ts: isHoliday() (fixed + floating US holidays incl. Easter), RV_HOLIDAY_RATE $175, PROMO_CODES with RIVER (rv-only: $175 flat, $500 deposit, removes delivery). Generator $75 → $85 to match form.html advertised rate. - types.ts: CheckoutRequest gets promoCode, licenseFileId, sessionId, paymentMode; Booking gets licenseFileId. - db.ts: license_photo column migration + setLicensePhoto(). - server.ts: /api/upload (license), /api/upload-inspection + /api/inspection/:id (car-hauler only). Multipart parser with 10 MB cap, image-type whitelist (jpeg/png/webp/heic), path-traversal guard on sessionId/bookingId. Checkout reads promoCode + links license photo to booking on success. - pricing.test.ts: 14 new tests covering holiday splits, RIVER overrides, promo case-insensitivity, carhauler isolation. 3 stale tests corrected ($500 RV-pickup deposit). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RV rentals must include delivery unless a promo (RIVER) removes it. The frontend already hid the pickup option; this tightens the server so malformed requests (RV without delivery, no promo) are rejected with 400 instead of silently creating a pickup booking at the wrong deposit. - pricing.ts: drop the RV-without-delivery → $500 fallback. Equipment default ($250) now holds for all non-promo RV pricing; RIVER still overrides to $500 via promo.deposit. - server.ts: validate RV requires either delivery add-on or a promo with removeDelivery; reject missing/overlong delivery_address (max 500 chars). Pass deliveryAddress through to createBooking. - types.ts: CheckoutRequest.deliveryAddress; Booking.deliveryAddress. - db.ts: delivery_address column migration; createBooking writes it; rowToBooking surfaces it. - pricing.test.ts: 3 RV-without-delivery tests corrected to $250 (the equipment default, since server now blocks that shape from hitting Square). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route owner notifications via OWNER_EMAIL env (default sheridantrailerrentals@gmail.com) instead of hardcoded info@sheridantrailerrentals.us, which has no receiving mailbox and was bouncing every booking/balance/payment alert. Move owner notification from pre-payment checkout to the Square webhook success path so abandoned carts and bot submissions no longer generate alerts. Webhook now fires the richer sendOwnerNotification template on confirmed payment. Harden checkout: per-IP rate limit (5/min), strict email regex, and honeypot `website` field short-circuits bots silently. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(booking): deliverable owner email + spam-proof checkout
…ool (Phase 0) Andy now has a single source of truth for routine vs. escalate decisions (groups/global/authority.md) and a first-class escalate() MCP tool that routes structured WhatsApp pings to Blayke for any novel/refund/complaint/ custom-priced/pattern-breaking situation. What ships: - groups/global/authority.md - allow-list spine, every persona inherits - groups/global/authority-test-cases.md - 10 verification scenarios - container/agent-runner/src/ipc-mcp-stdio.ts - new escalate() tool (severity, summary, recommendation, options, customer_channel) - src/ipc.ts - formatEscalationMessage() helper + escalation handler routes IPC type=escalation to main JID, logs structured audit line - groups/global/CLAUDE.md - Decision Authority section + tool reference - groups/main/CLAUDE.md - Decision Authority section - .gitignore - whitelist authority.md and authority-test-cases.md Verification: walk Andy through the 10 cases in authority-test-cases.md on WhatsApp; pass criterion is 9/10 correct. Misses become first lessons.md entries and feed Phase 1 evals. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rity-escalate feat(authority): Phase 0 - Decision Authority allow-list + escalate() MCP tool
…ywright MCP Andy runs scheduled tasks in CLI mode where agent-browser is not installed. The runtime regex rewriter in src/cli-runner.ts:syncSkillsForCli mangled dense skills (vending-inventory had 32 agent-browser refs) into broken prose like \`Run \`Playwright MCP tool\` close 2>/dev/null\`. Rewrites both affected skills to use mcp__playwright__* tools natively: browser_navigate / _snapshot / _click / _type / _wait_for_text / _close. Documents Playwright's auto-waiting model and reuses single browser context across HahaVending + Vendera login (no close between platforms). Simplifies syncSkillsForCli to just translate workspace paths (/workspace/project, /workspace/group) to host equivalents — skill content is now ready-to-run on either runtime. Deletes container/skills/agent-browser/SKILL.md (no longer referenced). Unblocks snak-vending-daily which has been failing for 5+ days with "agent-browser: command not found". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the absolute email/SMS lockdown in groups/global/CLAUDE.md with a Draft+Approve carve-out: when a customer message arrives on Gmail / Quo SMS / web / messenger, Andy MUST draft the reply, call escalate() with the draft as recommendation, wait for Blayke to reply on WhatsApp with "send"/"edit:..."/"skip", and only then send via the original channel. Cold/marketing email still ONLY via Instantly (unchanged). Owner-facing briefings/digests/reports move to WhatsApp main group (no email). Replaces the agent-browser tool description with Playwright MCP in both groups/global/CLAUDE.md and groups/main/CLAUDE.md "What You Can Do". Updates groups/main/CLAUDE.md so Snak/Sheridan booking flows route the owner notification to WhatsApp (was: email snakgroupteam@snakgroup.biz), and the daily 8am digest is WhatsApp not email. Replaces "send HTML email" outreach instructions with the Instantly drip flow (Andy queues leads, Instantly handles delivery + warmup + sequences). Unblocks: daily briefings (was being skipped due to lockdown), customer emails arriving on Gmail (was being read but never replied to), the "0 emails sent" structural failure. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…30min Two reliability fixes: 1. TWITTER_BEARER_TOKEN was not in SECRETS_SOCIAL on either runner, so even when added to .env it never reached scheduled tasks. Adds it to both src/cli-runner.ts and src/container-runner.ts. Documents in deploy/.env.example as a separate var from the OAuth1 X_API_* keys (used by post-tweet) — bearer token is only for read access via trend-scraper. 2. CLI_TIMEOUT default raised from 600000ms (10min) → 1800000ms (30min). Six scheduled tasks (vending-daily, lead-scrape, marketplace-renew, sams-club-prices, sheridan-leadgen, morning-outreach) routinely time out at 10 min because they do multi-site browser automation + DB writes + reconcile. Override via CLI_TIMEOUT env var if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… one-shot Three changes to deploy/upgrade-andy.sql: 1. snak-daily-briefing and sheridan-daily-briefing now send via WhatsApp main group instead of email (email is locked down per global CLAUDE.md). Both now include an "Open Complaints (>24h)" section that queries the complaints table and flags any age_days > 7 with "🚨 STUCK — needs Blayke today" + an escalate() call per stuck complaint. Plugs the Mahindra-shaped invisibility gap where 3 complaints sat open 32+ days because the briefing prompt never queried for them. 2. New Stage 9 task complaint-nag-daily (weekdays 8am CT) is a belt-and-suspenders complement: queries complaints open >7 days and pings WhatsApp main with one structured escalate() per stuck complaint, options [call_customer, refund_partial, refund_full, close_no_action, other]. Silent if zero stuck complaints. 3. New Stage 10 one-shot mahindra-one-shot-brief: Andy reads each open Mahindra complaint, sends a separate WhatsApp brief per complaint (customer / channel / age / snippet / Andy's read / suggested options) so Blayke can decide live. Marked schedule_type=once so it fires once on the next scheduler cycle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Weekly approval flow conflicted with Moderate authority — themed posts (Mon=Fleet, Tue=Local, Wed=Use Case, Thu=Seasonal, Fri=Engagement) should auto-act when they don't introduce new positioning/promo/pricing. Without this, Andy queues posts indefinitely waiting for "post snak"/"post sheridan" approvals that never come. Adds explicit auto-approve criteria (theme verbatim, no new offer, photo from existing asset-catalog, no competitor name, no novel claim) and reserves escalation for promo/pricing posts, competitor mentions, video TikTok versions, and new claims. Adds a 12h silent-veto rule so escalated posts default-approve if Blayke doesn't actively veto — drains the queue without owner action while still preserving real veto power. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lead-score.ts gives null-industry contacts a base of ~33-48 (data quality bonuses + company keyword) instead of the 60-80 they'd score with industry properly tagged. Apollo importer was silently dropping the Industry column. Two fixes: 1. tools/crm/import-apollo.ts:normalizeRow now reads Industry / industry / Vertical / vertical columns and writes to the contacts.industry field on insert + COALESCE on conflict. Existing rows with non-null industry are preserved; null industry gets backfilled by the next re-import. 2. tools/crm/backfill-industry.ts (new) — keyword classifier on company name + address/tags. Maps to scoring-config.json verticals (healthcare, logistics, manufacturing, education, hospitality, fitness, food_service, automotive, coworking, office, retail, religious, finance, residential). Provides 'coverage' for a quick read of current coverage and 'run [--limit] [--dry-run]' for the backfill. Recommended ops: schedule backfill-industry.ts run --limit 1000 nightly until coverage stops climbing, then per-import only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 2, 2026
Collaborator
|
@blaykeelder1-commits thank you for the PR. This seems to have pulled in many unrelated changes so closing. Please make sure any contributions are from a clean branch that is cought up with main repo branch and only one fix per PR. |
This was referenced May 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the 10 operational issues Andy reported: vending agent-browser failures, Maps 403, Twitter token gap, container 600s timeouts, LinkedIn 0/day, 0 emails sent, lead score stuck at 35-40, 3 stuck Mahindra complaints, FB queue not posting, CLI auto-restart instability.
Six independent commits that you can review, accept, or revert per phase.
Commits (in order)
9388ffdfeat(skills) — Rewritesvending-inventory(32 agent-browser refs) andfb-marketplaceskills natively in Playwright MCP. Deletes the agent-browser skill. Simplifiessrc/cli-runner.ts:syncSkillsForClito just translate workspace paths (no more regex mangling). Unblocks #1 (snak-vending-daily).f9e867efeat(andy) — Customer Reply Draft+Approve workflow ingroups/global/CLAUDE.md: Andy drafts → escalates draft to WhatsApp → Blayke repliessend/edit:/skip→ Andy sends via original channel only after approval. Replaces direct email instructions ingroups/main/CLAUDE.mdwith WhatsApp + Instantly drip. Replacesagent-browsermentions in both CLAUDE.md files with Playwright MCP. Unblocks Replace IPC busy-loop polling with async fs.watch #6 + customer-facing email/SMS that the lockdown was silently killing.9ace42ffeat(config) — AddsTWITTER_BEARER_TOKENtoSECRETS_SOCIALin both runners (was missing, so even adding to.envwouldn't pipe it through). BumpsCLI_TIMEOUTdefault 600000 → 1800000ms (30 min) for the 6 heavy tasks that legitimately exceed 10 min. Documents indeploy/.env.example. Unblocks Secure IPC with per-group namespaces to prevent privilege escalation #3 + Fix task data leakage: isolate IPC directories per group #4.e292416feat(briefings) —snak-daily-briefingandsheridan-daily-briefingnow ship via WhatsApp main group (not email — locked) and include an*Open Complaints (>24h)*section that flags any complaint > 7 days old as STUCK and escalates per row. Addscomplaint-nag-daily(8am weekdays) as a belt-and-suspenders. Addsmahindra-one-shot-brieffor the 3 currently stuck Mahindra cases — Andy WhatsApps you a brief per complaint so you can decide live. Unblocks Add validation for cron/date/interval schedule values #8.8aed61efeat(fb-posts) — Per Moderate authority, themed weekly posts (Mon=Fleet, Tue=Local, etc.) auto-approve when they don't introduce new positioning/promo/pricing. Promo/pricing posts still escalate. 12-hour silent-veto rule drains the queue without owner action. Unblocks Remove message content from info-level logs #9.dbe540dfeat(crm) —tools/crm/backfill-industry.ts(new) keyword classifier maps company name → scoring vertical for null-industry contacts.tools/crm/import-apollo.tsnow reads theIndustry/VerticalCSV columns. Lifts lead scores from 35-40 toward 60-80 once run. Unblocks Fix hardcoded home directory fallback in container runner #7.What's NOT in this PR (deferred / external)
Person LinkedIn URLcolumn included. Importer already supports it (line 56). Don't pre-merge until you decide on inferred-URL backfill.TWITTER_BEARER_TOKEN) — Code is now ready; needs you to generate a bearer token at developer.twitter.com and add to VPS.env.Test plan
npm run typecheckpassesnpm testproduces no NEW failures (40 pre-existing failures on main)git pull,npm run build,sqlite3 store/messages.db < deploy/upgrade-andy.sql,sudo systemctl restart nanoclaw, then:journalctl -u nanoclaw -n 200 | grep -iE "error|timeout"should show no fresh agent-browser errorsnpx tsx tools/leads/google-maps.ts search --query "coffee shops Houston TX"returns places (not 403)TWITTER_BEARER_TOKENto.env: trend-scraper twitter scans return tweets (not "Missing TWITTER_BEARER_TOKEN")Notes for review
complaint-nag-dailyis keyed onbusinessfield; ifbusinessis empty for any complaint it won't be filtered correctly. Spot-check after first run.🤖 Generated with Claude Code