feat(leadgen): unify lead_profile + lead_sync into a single leadgen action#74
Merged
Conversation
eldonm
pushed a commit
that referenced
this pull request
Jul 7, 2026
…ck-yaml - Regenerate package-lock.json cleanly (rm + npm install) so it's fully in sync — the incremental install left it missing a transitive dep, so `npm ci` (what CI runs) failed with EUSAGE. `npm ci` now installs clean. - Exclude auto-generated pnpm-lock.yaml from check-yaml (mirrors #74): main's pnpm-lock carries a duplicate key from the #69 merge that trips the strict parser; lockfiles shouldn't be yaml-linted. jvchat uses npm ci, not pnpm. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eldonm
added a commit
that referenced
this pull request
Jul 7, 2026
* chore(jvchat): upgrade to React 19
Bump react / react-dom / @types/react / @types/react-dom to ^19 and regenerate
package-lock.json. Fixes the one React 19 breaking change in the tree: the
global `JSX` namespace was removed, so JsonViewer.tsx's `JSX.Element` return
types now import the type explicitly (`import type { JSX } from "react"`).
The @assistant-ui/*, radix-ui, @testing-library/react, lucide, etc. ecosystem
all dedupe cleanly onto react@19. jvchat build (tsc && vite build), lint
(eslint --max-warnings 0), and tests (vitest, 55 passing) are green under 19.
Supersedes dependabot #65 (partial bump — it missed @types/react-dom, causing the
install-time ERESOLVE). Closes #73.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(react-19): sync package-lock (npm ci); exclude pnpm-lock from check-yaml
- Regenerate package-lock.json cleanly (rm + npm install) so it's fully in sync
— the incremental install left it missing a transitive dep, so `npm ci` (what
CI runs) failed with EUSAGE. `npm ci` now installs clean.
- Exclude auto-generated pnpm-lock.yaml from check-yaml (mirrors #74): main's
pnpm-lock carries a duplicate key from the #69 merge that trips the strict
parser; lockfiles shouldn't be yaml-linted. jvchat uses npm ci, not pnpm.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Eldon Marks <eldon.marks@v75inc.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ction Consolidate the separate lead_profile and lead_sync_action packages (and their skills) into one cohesive jvagent/action/leadgen harness: engine, spec, store, sync, hooks, validators, contract validation, and a thin-harness pattern, with README + docs (frontmatter-schema, thin-harness, extending, troubleshooting), an example_leadgen skill template, an example leadgen_agent, and full tests. Adds `jvagent skill create-leadgen` CLI to scaffold a leadgen skill from the template. Removes the retired lead_profile / lead_sync_action packages and skills (-2586 lines). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-lock from check-yaml
- Rename tests/action/leadgen/test_thin_harness_guards.py ->
test_leadgen_thin_harness_guards.py: pytest imports test modules by basename
(no __init__), so it collided with tests/action/interview/ of the same name
and aborted full-suite collection ("import file mismatch"). Passed locally
only because the leadgen dir was run in isolation.
- black/isort the leadgen test files (missed in the first pass — only source +
cli were formatted).
- Exclude pnpm-lock.yaml from check-yaml: main's jvchat/pnpm-lock.yaml carries a
duplicate key (@codemirror/state@6.7.0) from the #69 lockfile merge that trips
the strict YAML parser; auto-generated lockfiles shouldn't be yaml-linted
(detect-secrets already excludes it). Unblocks check-yaml across all PRs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Refine leadgen sync.py and extend test_sync_templates.py; touch the example product_inquiry_leads SKILL.md. pre-commit clean; leadgen tests (17) pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… intro coexistence
Adapt the leadgen reference agent into a multi-skill storefront and harden the
leadgen action.
Agent (examples/jvagent_app/.../leadgen_agent):
- Add contrib/storefront mock action (dummy FAQ + product catalog) and the
product_faq / product_search skills, coexisting with leadgen on one
orchestrator surface (gpt-4.1).
- Add jvagent/intro_interact_action opener; configure sync on the leadgen
action in agent.yaml rather than the skill frontmatter.
Intro coexistence (jvagent/action/intro):
- Contribute the first-message greeting as a response-shaping *parameter* (HOW)
instead of a *directive* (WHAT), so ReplyAction weaves it into the same reply
as the substantive answer instead of emitting a second, disjoint paragraph.
Fixes intro + orchestrator "responding in parallel".
Leadgen action (jvagent/action/leadgen):
- Sync digest dedup fix: compute_digest hashed internal keys, so the stored
digest changed the hash and every capture re-synced (duplicate rows on
append destinations). Exclude _-prefixed keys from the digest and from
{profile_json} so internal state never re-syncs or leaks to a destination.
- Sync is destination-agnostic via the standard MCP interface; drop the
google_sheets-specific framing from example and docs.
- Move sync config onto the action (sync_mode / sync_min_fields /
sync_require_any / sync_destinations); a skill may still self-contain sync by
declaring its own destinations.
- More aggressive contact capture: STANDING GOAL in the capture/retrieve tool
descriptions plus a next_ask hint in tool results; value-tied gap-fill each
turn, easing off once required fields are captured.
Tests: sync dispatch/dedup/resync/internal-key + action-sync merge, intro
parameter contract, storefront search. pre-commit + affected slices green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…nce; drop stale docs - Rewrite jvagent/action/intro/README.md: was stale (described a directive + PersonaAction + wrong weights + Jac comparison). Now documents the parameter-based first-message intro, weight -300, new_user gating, ReplyAction egress. - Leadgen README/frontmatter-schema/troubleshooting: sync config lives on the action (agent.yaml) via sync_mode/min_fields/require_any/destinations, standard MCP interface (no Sheets-specific framing), proactive next_ask gap-fill, graceful "connector not configured" skip, digest/internal-key notes. - Add examples/.../leadgen_agent/README.md documenting the storefront demo (leadgen + FAQ + product-search + intro + sync). - actions-catalog: refresh intro (parameter/-300) and leadgen (destination-agnostic sync) entries. - CHANGELOG: Added storefront demo; Changed generic sync + action-level config + aggressive capture + intro parameter; Fixed digest re-sync + internal-key leak. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
test_healthcheck_requires_directive used asyncio.get_event_loop().run_until_complete, which raises "no current event loop" on CI's Python — passed locally by luck. Use @pytest.mark.asyncio + await like the other tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- task_store: set_plan persisted via bare conversation.save(), losing the plan (parsed Task detached from raw dicts); _persist_step re-parsed the stale raw entry, dropping StepHandle mutations. Both now write the mutated object back into conversation.tasks by id. - orchestrator: repeat guard ran AFTER dispatch, so an identical side-effecting call executed up to 4x; now short-circuits pre-dispatch (one retry allowed after tool error/timeout). - orchestrator: a locked flow that keeps raising trapped the user behind the turn-lock every turn; two consecutive errors now cancel the owning control-task (note_locked_flow_error/clear_locked_flow_error). - core: app_loader wrote App._cached_app directly, bypassing the _set_cached_app guard (core/CLAUDE.md contract #2). - core: CacheManager._lock mutated _loop_locks unguarded and never evicted closed-loop entries — unbounded growth on serverless warm starts (mirror of App._get_lock's eviction). - core: failed startup re-ran full action init on every request; now backs off 30s between attempts. - core: unset ${VAR} placeholders now WARN by default (silently-empty credentials); JVAGENT_WARN_EMPTY_PLACEHOLDERS=false opts out. Regression tests added for every fix (tests/memory, tests/core, tests/action/orchestrator). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- core/benchmark.py: zero importers - core/graph_traversal.py: exported but never called anywhere - action/lead_profile/: ghost dir (only __pycache__ left after the leadgen unification); action/__deltest: empty stray file - orchestrator._finalize_directives: superseded by _egress (ADR-0024); only its own tests called it — tests removed, latch coverage lives in test_single_egress/test_adapter_no_double_send - orchestrator._has_runnable_work: production never called it; tests now assert pick_top_runnable directly - dependency_installer: drop pass-through action_path param (never used) - intro: docstrings referenced retired InteractRouter/BridgeInteractAction max_concurrent_tools kept: documented reserved config key (ADR-0015). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Gates were configured to catch nothing: flake8 ignored the dead-code detectors (F401/F811/F841) and bare-except (E722); mypy ignore_errors blanketed core, memory, cli, and nearly every action family; the pre-commit pytest hook was manual-stage only. - .flake8: enforce E722/F401/F811/F841; __init__.py re-exports get a per-file ignore. Fallout: ~150 dead imports removed (autoflake), duplicate imports and unused locals dropped by hand. Two re-exports consumed by tests kept with explicit noqa (whatsapp TypingStateManager, pageindex _ssrf_guard_url). - mypy: enforce jvagent.core.*, jvagent.memory.*, jvagent.cli, jvagent.action framework + most families; ignore_errors now covers only six provider clusters (model/whatsapp/stt/tts/pageindex/ vectorstore). Fixes: App.now() @overload (fmt=None -> datetime), StepStatus Literal for normalize_step_status, User.memory widened to Dict[str, Any] (stores nested user_model), ConfigSchema._keys ClassVar, various annotations. - pre-commit: pytest hook now runs at pre-push (was manual-only, contradicting the documented mandatory gate); CLAUDE.md notes it. - pyproject: requires-python >=3.10 (mcp needs it; 3.8/3.9 untested), mypy python_version 3.10, unconditional mcp dependency. - sentdm_broadcast/test_cli.py renamed broadcast_tester.py — an interactive HTTP tester, not a pytest test; pytest never collected it. - tests/logging/ added (endpoint filter/time parsing, logs-DB contract, INTERACTION level registration) — subsystem previously untested. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Conversation.get_interactions: batch-load by conversation_id (one query on the conv_timestamp index, ordered in memory) instead of a node-by-node chain walk — the per-turn LLM-history path paid N sequential DB fetches, unbounded at the default interaction_limit=0. - Memory.get_user: debounce the last_seen bump (60s window) — every inbound message paid a full user.save() just to refresh a timestamp. - check_pip_dependency_installed: importlib.metadata + packaging version-spec matching — the old probe __import__ed the package (side effects, slow) and shelled out to 'pip list' per package at boot, and ignored version specifiers entirely. - repair_scratch.scratch_count: backend count() instead of loading all rows and len()ing them (fallback scan kept for backends without it). - distributed_conversation_lock (DynamoDB): one boto3 client per acquisition, not one per poll iteration plus one per release. - orchestrator._ingest_uploads: per-image interpretations (a model round-trip each) now run concurrently; artifact writes stay serial. - tool_decorator.collect_tools: per-class discovery cache — docstring/ signature introspection is static per class, and the orchestrator calls get_tools() on every enabled action each turn. Skipped deliberately: App.get() cache-hit context verification (the extra round-trip is the AUDIT-core C-1 multi-tenant safety check) and TaskStore incremental persist (invasive; step/plan writes fixed in 70161c3). New characterization tests: get_interactions order/limit/reverse, last_seen debounce, version-aware dependency probe (the probe test demonstrated the old side-effect import by printing the Zen of Python). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CLAUDE.md / core/CLAUDE.md: repoint drifted source anchors (App/Agent class lines, pruning at conversation.py:490, set_app_update_mode at app.py:596 — previous refs landed on unrelated lines). - use_case_loader: actually validate documents against use-case-v1.schema.json (the schema was loaded but never applied). - orchestrator/continuation: order concurrent active tasks by parsed datetime instead of lexicographic str(updated_at) — naive/aware or offset-bearing timestamps from external writers sorted wrong. - env.py: collapse two copy-paste dotenv readers into one helper; the swallowed exception is now logged at debug. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add skill_spec base, registry, contract modules for shared parsing - Refactor interview and leadgen specs to import shared primitives - Resolve leadgen merge conflicts blocking test collection Co-authored-by: Cursor <cursoragent@cursor.com>
…gress Consolidate shared channel/webhook/OAuth/spreadsheet helpers; split orchestrator into egress/loop/uploads modules; retire router/converse/retrieval/long_memory rails actions in favor of orchestrator skills. Fix ReplyAction.gather() and orchestrator _send_reply so intro parameters-only shaping emits on first engagement. Drop docling from default test/dev deps (keep jvagent[pageindex]). Pin jvspatial 0.0.12; update tests and docs for removed actions and new layout. Co-authored-by: Cursor <cursoragent@cursor.com>
…plit Trailing whitespace in ADRs, black/isort on orchestrator/cli/embed modules, and remove unused imports that CI flake8 caught but local hooks had missed. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Consolidates the separate lead_profile and lead_sync_action packages (and their skills) into one cohesive
jvagent/action/leadgenharness.What's in it
engine,spec,store,sync,hooks,validators, contract validation (_validate_contract), and a thin-harness pattern.README+docs/(frontmatter-schema, thin-harness, extending, troubleshooting).examples/example_leadgenskill template + anexampleleadgen_agent.jvagent skill create-leadgen <agent> <skill>scaffolds a leadgen skill from the template.lead_profile/lead_sync_actionpackages + skills (−2586 lines).Test plan
pytest tests/action/leadgen— 15 passed.pre-commit runclean (black, isort, flake8, mypy, detect-secrets).Independent of the outstanding dependency PRs (#67, #71, #72).
🤖 Generated with Claude Code