@vruum/skills 0.6.47 → 0.6.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.47",
3
+ "version": "0.6.49",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.47",
3
+ "version": "0.6.49",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Skills for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/skills",
3
- "version": "0.6.47",
3
+ "version": "0.6.49",
4
4
  "description": "Vruum AI skills for Claude Code, Claude Desktop, Codex CLI, and any AI assistant with a skill directory. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis. Pairs with the Vruum MCP server at https://api.vruum.ai/mcp.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -41,5 +41,5 @@
41
41
  "outreach",
42
42
  "gtm"
43
43
  ],
44
- "contentHash": "2bf8dd9bb8668bbb6d381225dfba3131bb7352b6ea03a55ab8172ae20c81e56e"
44
+ "contentHash": "08900b7700c6e749f7d02081bf8a319ccac72e36b01c794594b4f07d44b2db84"
45
45
  }
@@ -52,6 +52,15 @@ Two paths — ask which:
52
52
  - **Reuse messaging that works** (default when they name an existing campaign): `manage_campaign` action=clone id=<existing campaign uuid> payload={name: "<new name>"}. Cloning carries the messaging structure, tone, and CTA configuration. Find the source campaign with `search` type="campaigns" if you only have its name.
53
53
  - **Fresh**: `manage_campaign` action=create payload={name, ...} — then offer to set tone/cadence via action=update once created.
54
54
 
55
+ ### Exact wording: touch templates (optional)
56
+
57
+ If the seller wants the SAME proven email/message every time (instead of per-person AI drafting), set a **template** on the touch: `manage_campaign` action=update id=<campaign uuid> with the full `touch_sequence` where that step gains `{"template": {"subject": "...", "body": "..."}}`. Rules:
58
+
59
+ - Variables: `{{first_name}}`, `{{last_name}}`, `{{company}}`, `{{title}}` — nothing else. Substitution is deterministic; no AI touches the wording. The signature is plain text inside the body.
60
+ - Templates only render for content channels (email, linkedin_message, linkedin_inmail). Email and InMail templates REQUIRE a subject. Don't put templates on connection-request or phone steps.
61
+ - Templated touches mint as ready `draft` rows (generated_by_ai=false) straight into review; a person whose variables can't resolve stays `needs_draft` with `template_fallback_reason` — authored normally at triage.
62
+ - Adding a template never rewrites already-queued touches. To render it over EXISTING unauthored rows, call `manage_campaign` action=apply_template id=<campaign uuid> payload={step: N} and report rendered/skipped counts honestly.
63
+
55
64
  ## Step 4: Assign the cohort
56
65
 
57
66
  Collect the person ids from the Step 2 preview (including IDs returned by the named-account handoff; re-run the same `search` with a higher `limit` to get the full cohort if needed — paginate with `offset` for big cohorts) and call `manage_campaign` action=members id=<campaign uuid> payload={action: "assign", person_ids: [...]}.
@@ -86,6 +86,8 @@ Categorize into three processing groups:
86
86
  2. **Follow-ups** (first_content_touch=false; fallback when null: sequence_number >= 2) — need research and quality check
87
87
  3. **Openers** (first_content_touch=true; fallback when null: sequence_number = 1) — first real message regardless of T-number; full opener research standards, never "follow-up" framing
88
88
 
89
+ **Template-authored drafts (VRU-813).** A draft with `ai_decision_context.authored_by = "template"` is the OPERATOR'S OWN template rendered per person (deterministic `{{variable}}` substitution, `generated_by_ai=false`) — it arrives in the review lane already written. Review it for FIT (right person, correct variable values, thread coherence), not style: do NOT rewrite the operator's chosen wording per-row. If the prose itself is weak, that's a template-level problem — flag it once to the user so they fix the template in the campaign workbench (or via `manage_campaign` action=update), rather than editing N copies divergently. Conversely, a `needs_draft` row carrying `ai_decision_context.template_fallback_reason` is a person the template could NOT render for (missing variable value, or the channel fell back outside the template's step) — author it normally, and if many rows share one reason, surface the pattern (e.g. "31 people have no title on file") instead of silently hand-writing around a fixable data gap.
90
+
89
91
  Present the queue composition before dispatching. For the counts in one call, use `search` with type=messages and `view=breakdown` — it returns grouped counts (by status, category, sequence/touch number, channel, and campaign) over the whole queue plus a compact items page, so you don't have to tally the pages yourself:
90
92
 
91
93
  "Oaklet: 15 T1s, 5 T2s, 1 T3, 0 replies. How do you want to handle each group?"
@@ -251,7 +251,7 @@ Apply the requested mode before any persistence:
251
251
  Per surviving prospect:
252
252
 
253
253
  ### a. Resolve and save the company (once per unique current employer)
254
- In `save` and `save-and-enroll` modes, reuse a trustworthy candidate `company_id` when one is already known. Otherwise call `research(action="save_company", payload={idempotency_key: <stable run/company save key>, name: <CURRENT COMPANY>, website: <canonical website or domain>, linkedin_url: <canonical LinkedIn company URL>, person_id: <the person's Vruum UUID, when researching an EXISTING person's employer>, company_summary, company_stage, funding_data, growth_metrics, current_priorities: <newline-joined descriptions>, sources_by_field})` once per unique current employer and capture the returned `company_id`. This call is required for identity resolution even when Phase A produced no new fixed research fields; in that case omit those research fields and their evidence, but still send the strongest known company anchor. The API field is `name`, not `company_name`; it accepts `website`, not `domain`; and `current_priorities` is one string. Omit reusable fields that were not revalidated so the atomic patch preserves them. Explicit null deliberately clears a field, so do not send null merely because Phase A did not research it. `sources_by_field` keys must equal exactly the supplied non-null research fields. Preserve the identical idempotency key and payload for unknown-commit replay; every bulk item needs its own key. Reuse the returned `company_id` for every prospect at that employer.
254
+ In `save` and `save-and-enroll` modes, reuse a trustworthy candidate `company_id` when one is already known. Otherwise call `research(action="save_company", payload={idempotency_key: <stable run/company save key>, name: <CURRENT COMPANY>, industry: <required Vruum canonical industry>, website: <canonical website or domain>, linkedin_url: <canonical LinkedIn company URL>, person_id: <the person's Vruum UUID, when researching an EXISTING person's employer>, company_summary, company_stage, funding_data, growth_metrics, current_priorities: <newline-joined descriptions>, sources_by_field})` once per unique current employer and capture the returned `company_id`. This call is required for identity resolution even when Phase A produced no new fixed research fields; canonical industry is still required, while unsourced research fields and their evidence stay omitted. Select `industry` from this exact list: Agriculture; Construction; Consumer Goods; Consumer Services; Education; Energy & Utilities; Financial Services; Government; Healthcare; Hospitality & Travel; Legal Services; Manufacturing; Marketing & Advertising; Media & Entertainment; Nonprofit; Professional Services; Real Estate; Retail & E-commerce; Software & Technology; Staffing & Recruiting; Telecommunications; Transportation & Logistics; Wholesale & Distribution; Other. Never send `Unspecified` or a provider-specific label. The API field is `name`, not `company_name`; it accepts `website`, not `domain`; and `current_priorities` is one string. Omit reusable fields that were not revalidated so the atomic patch preserves them. Explicit null deliberately clears a field, so do not send null merely because Phase A did not research it. `sources_by_field` keys must equal exactly the supplied non-null research fields. Preserve the identical idempotency key and payload for unknown-commit replay; every bulk item needs its own key. Reuse the returned `company_id` for every prospect at that employer.
255
255
 
256
256
  **Person linkage check (VRU-767):** when the research is about a saved person's employer (triage/authoring-time refresh), ALWAYS pass their UUID as `payload.person_id` and read the response's `person_link`. `matched`/`linked`/`repointed` mean the person's future touches will see this research. `mismatch` means no pin was written because the evidence disagreed — the pin sits on a DIFFERENT company (anchored, or a stub whose name carries more identity than the researched one), or an unpinned person's known positions show no role at the researched company. People can hold multiple positions and researching a secondary employer never switches the primary pin. This research will NOT surface on their touches — verify which company they actually work for before authoring from it. `conflict` is a transient race (the pin changed mid-save): replay the identical payload with the same idempotency key once — `person_id` is exempt from the idempotency hash, so adding it to a replay of an earlier save is also the supported repair path. Never assume a bare `success` means the research reached the person.
257
257