From cc7c475b0121d560e19ab1add66483e100417ac6 Mon Sep 17 00:00:00 2001 From: Nitasha Walia Date: Wed, 20 May 2026 09:53:01 -0700 Subject: [PATCH] feat: add Pre-Work Brief skills (base + customization) Adds two complementary skills for setting up and customizing the Field Service Mobile Pre-Work Brief feature. skills/setting-up-pre-work-brief/ End-to-end setup of Pre-Work Brief on a Salesforce org with the Einstein for Field Service add-on. Detects provisioning state, enables Lightning Data Service, deploys the prompt template, assigns permission sets, adds the PreWorkBriefPromptTemplate field to the Work Order layout, and creates a fresh test Work Order + Service Appointment so the admin can validate on-device immediately. Verified end-to-end against an Einstein for Field Service trial org, May 2026. skills/customizing-pre-work-brief/ Generator-pattern skill for vertical-specific Pre-Work Briefs deployable as code. The admin provides industry, website, and a sentence about what their technicians do; the skill produces custom objects, an autolaunched PromptFlow, a sibling prompt template, a permission set, and a test Work Order. Loads vertical-specific seed templates from templates/.md. Five seeds ship: - hvac.md (verified end-to-end on a trial org, May 2026) - banking.md, telecom.md, healthcare.md, retail-merchandising.md (scaffolds; ready for the next admin in those verticals to verify) Each seed contributes recommended custom objects, a section structure for the prompt template, vertical-specific rules, and a cadence example. For verticals not in the library, --industry other synthesizes a draft seed at runtime and writes it back to templates/ for next time. The two skills compose: base setup gets PWB working at all on the managed flow; customizing builds a parallel customer-specific bundle. --- skills/customizing-pre-work-brief/SKILL.md | 539 +++++++++ .../templates/banking.md | 103 ++ .../templates/healthcare.md | 108 ++ .../templates/hvac.md | 107 ++ .../templates/retail-merchandising.md | 98 ++ .../templates/telecom.md | 100 ++ skills/setting-up-pre-work-brief/SKILL.md | 1033 +++++++++++++++++ 7 files changed, 2088 insertions(+) create mode 100644 skills/customizing-pre-work-brief/SKILL.md create mode 100644 skills/customizing-pre-work-brief/templates/banking.md create mode 100644 skills/customizing-pre-work-brief/templates/healthcare.md create mode 100644 skills/customizing-pre-work-brief/templates/hvac.md create mode 100644 skills/customizing-pre-work-brief/templates/retail-merchandising.md create mode 100644 skills/customizing-pre-work-brief/templates/telecom.md create mode 100644 skills/setting-up-pre-work-brief/SKILL.md diff --git a/skills/customizing-pre-work-brief/SKILL.md b/skills/customizing-pre-work-brief/SKILL.md new file mode 100644 index 0000000..e4689e7 --- /dev/null +++ b/skills/customizing-pre-work-brief/SKILL.md @@ -0,0 +1,539 @@ +--- +name: customizing-pre-work-brief +description: "Generate a vertical-specific Pre-Work Brief on Field Service Mobile, deployable as code. The admin provides industry, website, and a sentence describing what their technicians do; this skill produces custom objects, an autolaunched flow, a prompt template, a permission set, and a test record. TRIGGER when: user wants Pre-Work Brief tailored to their company or industry; user wants the brief to reference custom objects or fields specific to their business; user mentions website-grounded or vertical-specific briefs; user wants a second 'customized' brief alongside the default; user asks to clone or extend the managed Field Service flow; user references a specific vertical (HVAC, banking, telecom, healthcare, retail merchandising). DO NOT TRIGGER when: org has not yet completed base setup (use setting-up-pre-work-brief first); user wants Voice to Form; user wants Post-Work Summary; user wants a brand-new prompt template type from scratch (use Prompt Builder docs)." +allowed-tools: Bash Read Write Edit Glob Grep WebFetch +license: Apache-2.0 +metadata: + version: "0.2.0" + last_updated: "2026-05-20" + argument-hint: " --company --industry --website [--description ] [--service-resource ]" + compatibility: claude-code +--- + +# Customizing Pre-Work Brief + +Generate a vertical-specific Pre-Work Brief that grounds on the customer's business and the data model their technicians actually work with. The skill produces a deployable bundle — custom objects, autolaunched flow, prompt template, permission set, plus a test Work Order — so an admin goes from "the default brief works but feels generic" to "the brief reads like someone who knows our business wrote it" in one run. + +This skill is the second half of a two-step adoption journey. Step one — `setting-up-pre-work-brief` — gets PWB working at all on the managed flow. Step two — this skill — replaces the managed flow with a customer-specific bundle deployable entirely from metadata. No Save-As click in Flow Builder is required; the only manual step is one click to activate the prompt template, consistent with the base setup pattern. + +The skill is **vertical-agnostic by design.** It ships with seed templates for common Field Service archetypes (HVAC, banking, telecom, healthcare, retail merchandising) at `templates/.md`. The seed contributes a section structure, recommended custom objects, and grounding patterns. For verticals not in the library, the skill synthesizes from the website + admin description and adds the result back into `templates/` for next time. + +--- + +## Prerequisites + +The base setup must be complete. Run `setting-up-pre-work-brief` against the same org first if any of the checks below fail: + +```bash +ORG_ALIAS="${1:-}" +[ -z "$ORG_ALIAS" ] && { echo "Usage: customizing-pre-work-brief --company --industry --website "; exit 1; } + +echo "Checking base setup is in place..." + +sf org list metadata --metadata-type GenAiPromptTemplate --target-org "$ORG_ALIAS" --json | \ + jq -r '[.result[]? | select(.fullName == "Pre_Work_Brief")] | length as $n | + if $n > 0 then "✓ Default Pre_Work_Brief template deployed" else "✗ Default template missing — run setting-up-pre-work-brief first" end' + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT DeveloperName FROM PermissionSetLicense WHERE DeveloperName = 'EinsteinFieldServicePsl' AND TotalLicenses > 0" --json | \ + jq -r '.result.totalSize as $n | if $n > 0 then "✓ Einstein for Field Service PSL present" else "✗ PSL missing — run setting-up-pre-work-brief first" end' + +sf sobject describe --sobject WorkOrder --target-org "$ORG_ALIAS" --json | \ + jq -r '[.result.fields[]? | select(.name == "PreWorkBriefPromptTemplate")] | length as $n | + if $n > 0 then "✓ PreWorkBriefPromptTemplate field present" else "✗ Field missing — run setting-up-pre-work-brief first" end' +``` + +All three must report `✓` before continuing. + +--- + +## Args + +| Arg | Required | Default | Purpose | +|---|---|---|---| +| `` | yes | — | Target org. | +| `--company ` | yes | — | Company name. Used in the prompt template label and in business context generation. | +| `--industry ` | yes | — | One of: `hvac`, `banking`, `telecom`, `healthcare`, `retail-merchandising`, or `other`. Picks the seed template at `templates/.md`. If `other`, the skill synthesizes a template at runtime. | +| `--website ` | yes | — | Public website URL. The skill fetches the homepage to derive technician context. | +| `--description ` | no | (admin prompted) | 1-2 sentence description of what the company's technicians actually do on-site. If omitted, the skill asks. | +| `--service-resource ` | no | (admin prompted) | ServiceResource Id to assign the test Service Appointment to. | +| `--customized-template-name ` | no | `PreWorkBrief__` | Developer name of the new prompt template. Increment if running multiple times for the same org. | + +Parse args: + +```bash +COMPANY="" +INDUSTRY="" +WEBSITE="" +DESCRIPTION="" +SERVICE_RESOURCE_ID="" +TEMPLATE_NAME="" + +while [ $# -gt 0 ]; do + case "$1" in + --company) COMPANY="$2"; shift 2 ;; + --industry) INDUSTRY="$2"; shift 2 ;; + --website) WEBSITE="$2"; shift 2 ;; + --description) DESCRIPTION="$2"; shift 2 ;; + --service-resource) SERVICE_RESOURCE_ID="$2"; shift 2 ;; + --customized-template-name) TEMPLATE_NAME="$2"; shift 2 ;; + *) shift ;; + esac +done + +[ -z "$COMPANY" ] && { echo "--company is required"; exit 1; } +[ -z "$INDUSTRY" ] && { echo "--industry is required (hvac|banking|telecom|healthcare|retail-merchandising|other)"; exit 1; } +[ -z "$WEBSITE" ] && { echo "--website is required"; exit 1; } + +# Default template name +if [ -z "$TEMPLATE_NAME" ]; then + CLEAN_COMPANY=$(echo "$COMPANY" | tr -cd '[:alnum:]_') + CLEAN_INDUSTRY=$(echo "$INDUSTRY" | tr '[:lower:]' '[:upper:]' | head -c 1)$(echo "$INDUSTRY" | tail -c +2) + TEMPLATE_NAME="PreWorkBrief_${CLEAN_INDUSTRY}_${CLEAN_COMPANY}" +fi +``` + +--- + +## Step 1: Build the business context + +Combine the company website with the admin's description into a short technician-facing brief. + +**1a. Fetch the website.** + +Use Claude's `WebFetch` tool against `$WEBSITE` with the prompt: + +> Read this company's homepage and any obvious sub-pages. Return a 5-bullet summary of (1) what the company does, (2) the products or services they sell, (3) who their typical customer is, (4) what kind of work a field technician would perform on-site for them, (5) any specialized equipment, certifications, or terminology a technician would need to know. Keep it factual and pulled directly from the page. Do not invent details. + +Capture the response as `$WEBSITE_SUMMARY`. + +**1b. Get the admin's description.** + +If `--description` was passed, use it directly. Otherwise ask: + +> "In 1-2 sentences, what do your technicians actually do on-site for this business?" + +Capture as `$ADMIN_DESCRIPTION`. + +**1c. Synthesize.** + +Combine `$WEBSITE_SUMMARY` and `$ADMIN_DESCRIPTION` into a single 3-5 sentence paragraph saved as `$BUSINESS_CONTEXT`. Show the result to the admin and ask for sign-off before continuing. + +--- + +## Step 2: Load the vertical seed template + +```bash +SEED_PATH="templates/${INDUSTRY}.md" + +if [ -f "$SEED_PATH" ]; then + echo "✓ Found seed template at $SEED_PATH" + SEED=$(cat "$SEED_PATH") +else + echo "No seed for industry '$INDUSTRY'. Synthesizing one from business context." + INDUSTRY="other" + SEED="" +fi +``` + +The seed contributes: + +- **Section structure** for the prompt template (e.g., HVAC = Mission and contact / Customer and SLA / Site access and certifications / Equipment and refrigerant context). +- **Recommended custom objects** with the fields each typically carries. +- **Standard objects + fields** to query in the flow. +- **Cadence example** to anchor the model's tone. + +If the seed doesn't exist, the skill writes one at the end of the run so the next admin in the same vertical gets it. + +--- + +## Step 3: Audit the org's existing schema + +Some of what the seed proposes may already exist in the org. Audit before creating duplicates. + +```bash +GROUNDING_OBJECTS="Account Asset Contact Case WorkOrder WorkOrderLineItem ServiceAppointment WorkPlan WorkStep" +EXCLUDE_NAMESPACES="FSL__,FSSK__,SDO__,FSLDemoTools__" + +for obj in $GROUNDING_OBJECTS; do + echo "" + echo "Custom fields on $obj:" + sf sobject describe --sobject "$obj" --target-org "$ORG_ALIAS" --json 2>/dev/null | \ + jq -r --arg obj "$obj" --arg exclude "$EXCLUDE_NAMESPACES" ' + ($exclude | split(",")) as $ns_list | + .result.fields[]? + | select(.name | endswith("__c")) + | . as $f + | select(any($ns_list[]; . as $ns | $f.name | startswith($ns)) | not) + | " \($obj).\(.name) | type=\(.type) | label=\(.label)" + ' +done > /tmp/pwb-existing-customs.txt + +# Check whether the seed's recommended custom objects already exist +sf sobject list --target-org "$ORG_ALIAS" --sobject-type custom --json | \ + jq -r '.result[]?' > /tmp/pwb-existing-objects.txt + +cat /tmp/pwb-existing-customs.txt +echo "" +echo "Existing custom objects: $(wc -l < /tmp/pwb-existing-objects.txt)" +``` + +Cross-reference against the seed's recommended objects. For each recommended object: + +- Already exists → skip object creation, audit its fields. +- Doesn't exist → propose to admin for creation. + +Show the diff to the admin and ask for confirmation before deploying anything. + +--- + +## Step 4: Generate and deploy custom objects + +For each approved custom object, write metadata files. The general shape (using HVAC's `Refrigerant_Log__c` as an illustration; the actual fields come from the seed): + +```xml + + + Deployed + {seed-supplied description} + true + true + true + true + + + + AutoNumber + {prefix}-{0000} + 1 + + {seed-supplied plural label} + ReadWrite + +``` + +Lookup field rules to avoid the "must specify either cascade delete or restrict delete" deploy error: + +```xml + + + Asset__c + SetNull + + Asset + Refrigerant Logs + Refrigerant_Logs + Lookup + +``` + +Note: do not set `true` on lookups — pair it with `` or the deploy fails. For the customs the brief queries, optional lookups are fine. + +If any new lookup goes onto WorkOrder (e.g., `WorkOrder.Maintenance_Contract__c`), write that field too — the flow will reference it via `$Input.WorkOrder.__c`. + +Deploy: + +```bash +mkdir -p /tmp/pwb-custom-build/force-app/main/default +cat > /tmp/pwb-custom-build/sfdx-project.json <<'EOF' +{"packageDirectories":[{"path":"force-app","default":true}],"namespace":"","sourceApiVersion":"65.0"} +EOF + +# (Write all object + field XMLs into /tmp/pwb-custom-build/force-app/main/default/objects/...) + +cd /tmp/pwb-custom-build +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 30 --api-version 65.0 +``` + +--- + +## Step 5: Generate the from-scratch flow + +Write a `PromptFlow` flow that grounds on the relevant standard objects + the new customs. The pattern (verified working against the `afvuser` trial org, 2026-05-20): + +- `PromptFlow` +- `65.0` (the managed flow's `getCustomerSignalsInsights` action requires v65+; from-scratch flows that don't use it can stay lower, but 65 is the safe default) +- `` block has `Capability` and a `` element declaring `PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief` with a `WorkOrder` SObject input +- A chain of `` elements with `getFirstRecordOnly=true` (single-record references resolve cleanly; collections require Loops which complicate the demo) +- An `` element with `AddPromptInstructions` that builds `$Output.Prompt` from grounded references + +Skeleton: + +```xml + + + 65.0 + {Industry}-specific Pre-Work Brief grounding flow for {Company}. + Pre-Work Brief {Industry} {!$Flow.CurrentDateTime} + + PromptFlow + Active + + 0 + 0 + + PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief + PromptTemplateType://einstein_gpt__fieldServicePreWorkBrief + + WorkOrder + WorkOrder + SOBJECT://WorkOrder + false + + + + {first lookup} + + Capability + + + +``` + +The seed at `templates/.md` enumerates which lookups to chain, what each queries, and how `$Output.Prompt` is structured. + +Deploy: + +```bash +cd /tmp/pwb-custom-build +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/flows --wait 30 --api-version 65.0 +``` + +Activate the flow (deploying with `Active` is necessary but not sufficient — Salesforce treats it as draft until explicitly activated): + +```bash +FD_ID=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT Id FROM FlowDefinition WHERE DeveloperName = '${TEMPLATE_NAME}_Flow'" --json | \ + jq -r '.result.records[0].Id') + +LATEST_VERSION=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT VersionNumber FROM Flow WHERE Definition.DeveloperName = '${TEMPLATE_NAME}_Flow' ORDER BY VersionNumber DESC LIMIT 1" --json | \ + jq -r '.result.records[0].VersionNumber') + +cat > /tmp/activate-flow.json <.Prompt} +``` + +Skeleton: + +```xml + + + {TEMPLATE_NAME} + Pre-Work Brief — {Company} ({Industry}) + + You are briefing a {Industry} field technician on the job they are about to perform on-site. Address the technician directly, in the second person. + +Business context (do not contradict): +{BUSINESS_CONTEXT} + +Specific job grounding: +{!$Flow:{flow-developer-name}.Prompt} + +{INSTRUCTION_BLOCK from seed — sections + rules + cadence example} + + + WorkOrder + SOBJECT://WorkOrder + Input:WorkOrder + true + + sfdc_ai__DefaultOpenAIGPT4OmniMini + Published + + flow://{flow-developer-name} + + SOBJECT://WorkOrder + true + WorkOrder + {!$Input:WorkOrder} + + Flow:{flow-developer-name} + + + einstein_gpt__fieldServicePreWorkBrief + Global + +``` + +Deploy and surface the activation deeplink: + +```bash +cd /tmp/pwb-custom-build +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/genAiPromptTemplates --wait 30 --api-version 65.0 + +# Resolve the template Id +TEMPLATE_ID=$(sf api request rest "/services/data/v62.0/einstein/prompt-templates?pageSize=200" --target-org "$ORG_ALIAS" 2>/dev/null | \ + python3 -c " +import sys, json, os +d = json.loads(sys.stdin.read()) +name = os.environ.get('TEMPLATE_NAME') +for t in d.get('promptRecords', []): + if t.get('fields', {}).get('DeveloperName', {}).get('value') == name: + print(t.get('fields', {}).get('Id', {}).get('value')) + break") + +# Fall back to deploy report if runtime catalog hasn't picked it up yet +if [ -z "$TEMPLATE_ID" ]; then + for did in $(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT Id FROM DeployRequest WHERE NumberComponentsDeployed > 0 ORDER BY CompletedDate DESC LIMIT 10" --json | \ + jq -r '.result.records[]?.Id'); do + cand=$(sf project deploy report --target-org "$ORG_ALIAS" --job-id "$did" --json 2>/dev/null | \ + jq -r ".result.details.componentSuccesses[]? | select(.componentType == \"GenAiPromptTemplate\" and .fullName == \"$TEMPLATE_NAME\") | .id" | head -1) + [ -n "$cand" ] && TEMPLATE_ID="$cand" && break + done +fi + +echo "" +echo " ⚠ Action required: click Activate in Prompt Builder." +echo " The template is deployed and Published, but until activated, it" +echo " won't appear in the runtime catalog. The mobile app will fail with" +echo " 'We hit a snag' until activation is complete." +echo "" +echo " Generating sign-in link to the template (valid ~15 minutes):" +sf org open --target-org "$ORG_ALIAS" \ + --path "/lightning/setup/EinsteinPromptStudio/$TEMPLATE_ID/edit" \ + --url-only 2>&1 | grep -oE 'https://[^[:cntrl:][:space:]]+frontdoor[^[:cntrl:][:space:]]+' | head -1 | sed 's/\x1b\[[0-9;]*m//g' +``` + +Pause until the admin confirms activation. Verify: + +```bash +sf api request rest "/services/data/v62.0/einstein/prompt-templates?pageSize=200" --target-org "$ORG_ALIAS" 2>/dev/null | \ + python3 -c " +import sys, json, os +d = json.loads(sys.stdin.read()) +name = os.environ.get('TEMPLATE_NAME') +hit = [t for t in d.get('promptRecords', []) if t.get('fields', {}).get('DeveloperName', {}).get('value') == name] +print('✓ Active in runtime catalog' if hit else '✗ Not yet active — click Activate in Prompt Builder')" +``` + +> **Why a click is still required.** Salesforce does not currently expose a programmatic activation path for `GenAiPromptTemplate`. We tried Tooling REST PATCH on `IsActive`, Connect API `/activate` endpoints across v62-v66, Apex `ConnectApi.EinsteinLLM` methods, and metadata `activeVersionNumber` — none work for `GenAiPromptTemplate` (the `activeVersionNumber` PATCH does work for Flow, which is why Step 5 can activate the flow programmatically). The skill follows the same one-click activation pattern the base `setting-up-pre-work-brief` skill uses for consistency. + +--- + +## Step 7: Generate the permission set + +Bundle CRUD on the new objects, FLS on the new fields (including any new WorkOrder lookups), and assign to admin + technician: + +```xml + + + Read/Edit on {Industry} custom objects + new fields used by the {Industry} Pre-Work Brief flow for {Company}. + false + + + true + true + true + true + false + {Custom_Object__c} + true + + + true{Object}.{Field__c}true + + +``` + +Deploy + assign: + +```bash +cd /tmp/pwb-custom-build +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app/main/default/permissionsets --wait 30 --api-version 65.0 + +PERMSET_NAME="PreWorkBrief_${INDUSTRY}_Access" +sf org assign permset --name "$PERMSET_NAME" --target-org "$ORG_ALIAS" +sf org assign permset --name "$PERMSET_NAME" --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS" +``` + +> **Asset.ProductDescription gotcha.** Verified during the HVAC dry-run on `afvuser` (2026-05-19): even with full Asset CRUD granted, `ProductDescription` is read-only for non-admin profiles in some org configurations. If the seed proposes populating it, fall back to writing the same content into `Asset.Description` instead. + +--- + +## Step 8: Create a fresh test Work Order with seed data + +Mirrors the `setting-up-pre-work-brief` Step 8-fresh pattern. Resolve Service Resource + Service Territory dynamically; create Account → Asset → custom-object records → Contact → WO → SA → AssignedResource as a chain so the brief has real data to ground on. + +The seed contributes example values for the test record (e.g., HVAC seed populates a 10-ton rooftop unit, Platinum SLA contract, R-410A refrigerant log at 14.2 lbs). + +After creation, hand off to the admin: + +> The {Industry} Pre-Work Brief is wired up. Open the Field Service mobile app, sign in as **`$TECH_USERNAME`**, navigate to today's schedule. The new Work Order **`$WO_NUMBER`** should appear. Open it; the Pre-Work Brief should render in the Overview tab and reference {Company}'s business context plus the custom-object data this skill just created. + +--- + +## Adding a new vertical seed + +If you ran with `--industry other` and want to contribute the synthesized template back, the skill writes a draft to `templates/.md` at the end of the run. Review the draft, edit as needed, and commit. The next admin in your industry skips the synthesis step. + +A seed should contain: + +| Section | What it specifies | +|---|---| +| **Business archetype** | One-sentence description of the vertical's typical customer + technician work | +| **Recommended custom objects** | 2-4 objects with field lists. Include why each matters (compliance, SLA, asset history). | +| **Standard objects + fields to query** | Which standard fields the flow's Get-record elements should pull (e.g., `ServiceAppointment.ArrivalWindowStartTime`, `Contact.Email`) | +| **Prompt template section structure** | The 3-5 sections the brief should contain (e.g., HVAC = Mission and contact / Customer and SLA / Site access and certifications / Equipment and refrigerant context) | +| **Rules** | Vertical-specific rules (e.g., HVAC = "if last refrigerant service was >12 months ago, call it out") | +| **Cadence example** | Tone-anchoring example paragraph using the section structure | +| **Test data sample** | Realistic values for the test WO + seed records the skill creates in Step 8 | + +See `templates/hvac.md` as the canonical example. + +--- + +## Idempotency + +Re-running this skill against the same org with the same `--customized-template-name`: + +- Re-fetches the website and re-prompts for description (cheap; admins re-confirm). +- Re-audits org schema; new customs may have appeared since last run. +- Skips object creation if Step 4 finds the customs already exist. +- Re-deploys the flow as a new version (Salesforce treats identical metadata as no-op). +- Re-deploys the prompt template (same; identical metadata is a no-op). +- Creates a **new** test WO + SA every run. + +For a second vertical in the same org, pass a different `--industry` and `--customized-template-name`. The skill produces a parallel pipeline (objects + flow + permset + template + test WO) without touching the first. + +--- + +## Related afv-library Skills + +- `setting-up-pre-work-brief` — required prerequisite. Runs the base setup that this skill builds on. +- `generating-flow` — useful when the from-scratch flow needs deeper edits than this skill performs (e.g., complex Loops over collections, Decision elements). Open the deployed flow in Flow Builder for visual review. +- `generating-permission-set` — for orgs that already have a permission set pattern to extend rather than create anew. + +--- + +## References + +- Pre-Work Brief setup: `https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief.htm` +- Pre-Work Brief data model: `https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief_data.htm` +- Prompt Builder: `https://help.salesforce.com/s/articleView?id=ai.prompt_builder_about.htm` +- Flow Reference (PromptFlow processType): `https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_visual_workflow.htm` + +--- + +## Known Limitations + +- Programmatic activation of `GenAiPromptTemplate` is not exposed in the API today. Step 6's deeplink is the supported path until Salesforce ships an Activate REST endpoint. The skill follows the same one-click pattern as `setting-up-pre-work-brief` for consistency. +- Flows with multi-record record lookups (`getFirstRecordOnly=false`) require a Loop element to reference individual records in the Output.Prompt assignment; the from-scratch pattern in Step 5 uses `getFirstRecordOnly=true` to keep the demo simple. For verticals where a Work Order has multiple Refrigerant Logs / Compliance Checks / etc., the seed should specify a Loop pattern. +- `WebFetch` results depend on the company's homepage being public and not paywalled or JS-only. Pages rendered entirely by client-side JS may return little usable content; in that case fall back to admin description only. +- `AssignedResource` create may fail in orgs that require a Service Territory on the Service Appointment. Step 8 resolves Service Territory from the chosen Service Resource's `ServiceTerritoryMember` and includes it on create. If the resource has no territory membership, ask the admin to pick a different resource or assign a territory in Setup. +- `Asset.ProductDescription` is read-only for non-admin profiles in some org configurations (verified on `afvuser` 2026-05-19). Seeds should write descriptive text to `Asset.Description` instead. diff --git a/skills/customizing-pre-work-brief/templates/banking.md b/skills/customizing-pre-work-brief/templates/banking.md new file mode 100644 index 0000000..41b500a --- /dev/null +++ b/skills/customizing-pre-work-brief/templates/banking.md @@ -0,0 +1,103 @@ +# Banking / ATM Service Seed Template + +Scaffold. Not yet verified end-to-end against a live org. Adapted from the Unisys customization work (2026-05-19) and standard banking field service patterns. + +## Business archetype + +This customer services banking and financial-services hardware — ATMs, bank branch teller stations, drive-thru tubes, vault systems — at retail bank locations and remote ATM sites. Technicians perform cash replenishment, hardware repair, software updates, and compliance audits. Cassette levels and last-audit timestamps are critical to every visit. Many sites have escort requirements, time-of-day windows, and dual-control protocols (two technicians required for vault access). + +## Recommended custom objects + +### `ATM_Cassette__c` + +Why: Cash levels and denomination mix drive whether the visit is a routine replenishment vs. emergency. + +| Field | Type | Notes | +|---|---|---| +| `Asset__c` | Lookup → Asset | `SetNull` | +| `Cassette_Position__c` | Picklist (restricted) | Top, Middle Upper, Middle Lower, Bottom | +| `Denomination__c` | Picklist (restricted) | $1, $5, $10, $20, $50, $100 | +| `Current_Notes_Count__c` | Number(6,0) | Notes remaining at last reading | +| `Capacity__c` | Number(6,0) | Max notes the cassette holds | +| `Last_Replenishment__c` | Date | Used as sort key | + +Name field: AutoNumber `CS-{0000}`. + +### `Compliance_Check__c` + +Why: Banking equipment requires periodic audits (PCI-DSS for card readers, ADA for screen-reader and audio, internal policy for camera coverage). Last-audit-date drives whether this visit needs to include a compliance pass. + +| Field | Type | Notes | +|---|---|---| +| `Asset__c` | Lookup → Asset | `SetNull` | +| `Audit_Type__c` | Picklist (restricted) | PCI-DSS, ADA, Camera Coverage, Lighting, Internal Policy | +| `Last_Audit_Date__c` | Date | When was this audit type last completed | +| `Next_Audit_Due__c` | Date | When the next audit is due | +| `Status__c` | Picklist (restricted) | Compliant, Action Required, Failed | +| `Notes__c` | LongTextArea (500) | Findings from last audit | + +Name field: AutoNumber `CC-{0000}`. Enable history tracking (audit trail). + +## WorkOrder field additions + +| Field | Type | Notes | +|---|---|---| +| `Site_Access_Protocol__c` | Picklist (restricted) | Single Tech, Dual Control Required, Escort Required, After-Hours Only | +| `Cash_Handling_Required__c` | Checkbox | If true, the technician must hold a cash-handling certification | + +## Standard objects + fields to query + +| Object | Fields | +|---|---| +| Account | Name, Industry, Phone, Description | +| Asset | Name, SerialNumber, InstallDate, Description, Status | +| Contact | Name, Title, Phone, Email | +| ServiceAppointment | AppointmentNumber, SchedStartTime, SchedEndTime, ArrivalWindowStartTime, ArrivalWindowEndTime, Description, Status, Subject | + +## Flow structure + +Connector chain: `start → GetAccount → GetAsset → GetCassettes → GetComplianceChecks → GetServiceAppointment → GetContact → BuildPrompt`. + +`GetCassettes` filters by `Asset__c = $Input.WorkOrder.AssetId`, sorts by `Last_Replenishment__c ASC` (oldest first surfaces what needs attention). + +`GetComplianceChecks` filters by `Asset__c = $Input.WorkOrder.AssetId`, sorts by `Next_Audit_Due__c ASC` (most-urgent due-date first). + +Both use `getFirstRecordOnly=true` for the demo. For real deployments where one ATM has 4-6 cassettes, switch to a Loop pattern (see Known Limitations in main SKILL.md). + +## Prompt template section structure + +1. **Mission and contact** — work to perform, on-site contact, end with site security reminder. +2. **Site access protocol** — single tech vs. dual control, time window, escort requirements. If `Cash_Handling_Required__c = true`, instruct the technician to verify their cash-handling cert. +3. **Cash status** — current cassette levels by denomination, time since last replenishment. +4. **Compliance status** — most recently due audit type, last completion date, any action items from the prior audit. + +## Rules + +- Address the technician directly throughout. +- For dual-control sites, explicitly state "you are the second technician" or "you are the lead; confirm your partner is on-site before opening the vault." +- Never disclose actual cash amounts in the brief — use ranges ("low", "below 25%", "near capacity") to reduce risk if the device is screenshot or shoulder-surfed. +- For any field not grounded, write `[not provided]`. + +## Cadence example + +> "Perform cash replenishment and a routine PCI-DSS compliance check on ATM-12 at the customer's downtown branch. Your on-site contact is the branch operations manager, reachable at the provided phone number and email. Do not leave the site until both tasks are completed and you have confirmed the device is back in service. +> +> This site requires dual-control access — confirm your partner is on-site before opening the vault. Service window is 6 AM to 8 AM only, before the branch opens to customers. +> +> Cassette status as of last reading: $20 cassette is below 25% capacity, $50 cassette is near capacity, $100 cassette is mid-range. Replenishment was last performed 11 days ago. +> +> The PCI-DSS audit is due within 30 days. The last audit on 2025-12-04 found one finding: receipt printer paper feed was misaligned. Verify the finding has been addressed." + +## Test data sample + +Account: "First Coastal Bank — Downtown Branch" (Banking), with description noting branch hours and ATM count. + +Asset: "ATM-12 — Lobby", Serial `NCR-ATM-554821`, NCR SelfServ 84. + +ATM_Cassette records: 4 entries (one per denomination), with current note counts and replenishment dates. + +Compliance_Check records: 3 entries (PCI-DSS, ADA, Camera Coverage), with audit dates spanning the last 18 months. + +Contact: branch operations manager. + +Work Order: "Cash replenishment + PCI-DSS spot check — ATM-12", Medium priority. diff --git a/skills/customizing-pre-work-brief/templates/healthcare.md b/skills/customizing-pre-work-brief/templates/healthcare.md new file mode 100644 index 0000000..bffcbe4 --- /dev/null +++ b/skills/customizing-pre-work-brief/templates/healthcare.md @@ -0,0 +1,108 @@ +# Healthcare Medical Devices Seed Template + +Scaffold. Not yet verified end-to-end against a live org. High-stakes vertical — patient safety and FDA compliance shape every visit. + +## Business archetype + +This customer manufactures or services medical devices — infusion pumps, imaging equipment, dialysis machines, ventilators, surgical robots — at hospitals, clinics, and ambulatory surgery centers. Technicians perform calibration, software updates, recall remediation, and break/fix. Patient safety protocols (lockout-tagout during service, escorted access in surgical areas), FDA recall status, and last-calibration dates are critical grounding data. Many sites require the technician to coordinate with biomed engineering. + +## Recommended custom objects + +### `Device_Calibration__c` + +Why: Medical devices require periodic calibration with documented results. Walking into a service call without knowing the last-calibration date risks patient safety and regulatory exposure. + +| Field | Type | Notes | +|---|---|---| +| `Asset__c` | Lookup → Asset | `SetNull` | +| `Calibration_Type__c` | Picklist (restricted) | Routine, Annual, Post-Repair, Pre-Use Check | +| `Last_Calibration_Date__c` | Date | When the last calibration was performed | +| `Next_Calibration_Due__c` | Date | When the next calibration is due | +| `Result__c` | Picklist (restricted) | Pass, Pass with notes, Fail, Out of tolerance | +| `Calibrated_By__c` | Lookup → User | Who performed the calibration | +| `Notes__c` | LongTextArea (500) | Calibration findings, drift, adjustments made | + +Name field: AutoNumber `CAL-{0000}`. Enable history tracking. + +### `FDA_Compliance_Log__c` + +Why: FDA recalls, field safety notices, and corrective-action communications are mandatory grounding for any service visit on a regulated device. + +| Field | Type | Notes | +|---|---|---| +| `Asset__c` | Lookup → Asset | `SetNull` | +| `Notice_Type__c` | Picklist (restricted) | Recall (Class I/II/III), Field Safety Notice, Corrective Action, Voluntary Update | +| `Notice_Date__c` | Date | When the notice was issued | +| `Action_Required__c` | LongTextArea (1000) | What the technician must do | +| `Status__c` | Picklist (restricted) | Pending, In Progress, Completed, N/A | +| `FDA_Reference__c` | Text(50) | FDA recall number or notice ID | + +Name field: AutoNumber `FDA-{0000}`. Enable history tracking. + +## WorkOrder field additions + +| Field | Type | Notes | +|---|---|---| +| `Patient_Safety_Risk__c` | Picklist (restricted) | None, Low, Medium, High | +| `Biomed_Coordination_Required__c` | Checkbox | If true, the brief must instruct the technician to coordinate with hospital biomed before starting | +| `Lockout_Tagout_Required__c` | Checkbox | If true, the technician must confirm LOTO procedures before starting | + +## Standard objects + fields to query + +| Object | Fields | +|---|---| +| Account | Name, Industry, Phone, Description | +| Asset | Name, SerialNumber, InstallDate, Description, Status | +| Contact | Name, Title, Phone, Email | +| ServiceAppointment | AppointmentNumber, SchedStartTime, SchedEndTime, ArrivalWindowStartTime, ArrivalWindowEndTime, Description, Status, Subject | + +## Flow structure + +Connector chain: `start → GetAccount → GetAsset → GetMostRecentCalibration → GetActiveFDANotices → GetServiceAppointment → GetContact → BuildPrompt`. + +`GetMostRecentCalibration` sorts by `Last_Calibration_Date__c DESC LIMIT 1`. + +`GetActiveFDANotices` filters by `Asset__c = $Input.WorkOrder.AssetId AND Status__c IN ('Pending', 'In Progress')`. For a real deployment with multiple active notices, use a Loop pattern. + +## Prompt template section structure + +1. **Mission and contact** — work to perform, biomed contact + phone + email. End with patient-safety reminder. +2. **Patient safety protocol** — risk level, lockout-tagout requirement, biomed coordination requirement. If risk is Medium or High, list the specific safety steps. +3. **Active FDA notices** — any pending or in-progress notices for this asset, with the FDA reference and required action. If none, state "no active FDA notices." +4. **Calibration status** — last calibration date, result, days until next due. Call out overdue calibrations explicitly. +5. **Equipment context** — asset model, serial, install date, current status. + +## Rules + +- Address the technician directly throughout. +- If `Biomed_Coordination_Required__c = true`, the brief must explicitly instruct: "Confirm with hospital biomed before starting service. Do not power-cycle the device without their sign-off." +- If `Lockout_Tagout_Required__c = true`, the brief must list the LOTO steps from the asset's documented procedure. +- If an FDA recall has `Status__c = Pending`, this visit must include the recall remediation. Lead with that, not the customer's stated complaint. +- If the next calibration is overdue, the brief must state how many days overdue. +- For any field not grounded, write `[not provided]`. + +## Cadence example + +> "Perform a Class II FDA recall remediation on the customer's infusion pump (Serial INF-CN-220194). Your biomed contact is Dr. Patel, reachable at the provided phone and email. Do not leave the site until the recall remediation is complete and the device is signed off by biomed. +> +> This is a Medium patient-safety-risk visit. Lockout-tagout procedures are required: power off the device at the wall, apply your personal lock, verify the device is de-energized, and complete the LOTO log entry before opening the chassis. +> +> Active FDA notice: Class II Recall, FDA reference Z-2845-2026, issued 2026-04-12. Action required: install firmware version 7.4.1, verify pressure-sensor recalibration completes successfully, document the firmware checksum on the calibration log. +> +> Most recent calibration: 2026-02-10, result Pass with notes ("minor drift on flow sensor 2"). Next calibration is due in 6 days; if you have time after the recall remediation, perform an early calibration and update the log. +> +> The device is a 2023 model, currently in service. The customer's stated complaint (intermittent flow alarm) is likely related to the recall — the firmware update should resolve it." + +## Test data sample + +Account: "Mercy General Hospital — ICU Wing" (Healthcare). + +Asset: "Infusion Pump INF-CN-220194", installed 2023-04-22. + +Device_Calibration: most recent 2026-02-10, Pass with notes. + +FDA_Compliance_Log: 1 active Class II recall, 1 completed Field Safety Notice from 2025. + +Contact: Dr. Patel, biomed engineering lead. + +Work Order: "Recall remediation Z-2845-2026 — Infusion Pump INF-CN-220194", High priority, biomed coordination required, LOTO required, Medium patient safety risk. diff --git a/skills/customizing-pre-work-brief/templates/hvac.md b/skills/customizing-pre-work-brief/templates/hvac.md new file mode 100644 index 0000000..0d27575 --- /dev/null +++ b/skills/customizing-pre-work-brief/templates/hvac.md @@ -0,0 +1,107 @@ +# HVAC Commercial Maintenance Seed Template + +Canonical seed. Verified end-to-end against `afvuser` trial org, 2026-05-20: deployed 2 custom objects, 11 fields, a from-scratch flow, prompt template, permission set, and a test WO + SA. Brief renders on Field Service Mobile. + +## Business archetype + +This customer operates commercial HVAC systems — rooftop units, chillers, heat pumps, and associated controls — at retail, healthcare, education, grocery, and office locations. Technicians perform planned maintenance, emergency break/fix, and refrigerant handling. Refrigerant logs are EPA-required and must be referenced for any work involving the refrigerant circuit. Site access constraints (escort requirements, hours, tenant coordination) and SLA tier (response-time commitment) shape every visit. + +## Recommended custom objects + +### `Maintenance_Contract__c` + +Why: SLA tier and coverage scope are the most-referenced grounding context in commercial HVAC briefs. A Platinum-tier customer with a 4-hour response commitment needs the technician to know that before they leave. + +| Field | Type | Notes | +|---|---|---| +| `Account__c` | Lookup → Account | `SetNull`, not required | +| `SLA_Tier__c` | Picklist (restricted) | Platinum (4hr), Gold (8hr), Silver (24hr), Bronze (48hr) | +| `Coverage_Scope__c` | LongTextArea (1000) | Free-text scope of services covered | +| `Site_Access_Notes__c` | LongTextArea (500) | Gate codes, escort requirements, hours, tenant coordination | +| `Required_Certifications__c` | Text (255) | EPA Section 608 Universal, OSHA 10, etc. | + +Name field: AutoNumber `MC-{0000}`. + +### `Refrigerant_Log__c` + +Why: EPA-required documentation. Last-service-date and prior charge level inform whether the current visit is a leak diagnostic vs. routine top-up. + +| Field | Type | Notes | +|---|---|---| +| `Asset__c` | Lookup → Asset | `SetNull` | +| `Refrigerant_Type__c` | Picklist (unrestricted) | R-410A, R-454B, R-32, R-22 (legacy) | +| `Charge_Level_Lbs__c` | Number(6,2) | Pounds of refrigerant in system | +| `Last_Service_Date__c` | Date | Used as sort key (most recent first) | +| `Notes__c` | LongTextArea (500) | Leak detection results, top-off amounts | + +Name field: AutoNumber `RL-{0000}`. Enable history tracking (compliance audit trail). + +## WorkOrder field additions + +| Field | Type | Notes | +|---|---|---| +| `Maintenance_Contract__c` | Lookup → Maintenance_Contract__c | Not required; flow uses `$Input.WorkOrder.Maintenance_Contract__c` to fetch SLA + coverage | + +## Standard objects + fields to query + +| Object | Fields beyond the default flow's set | +|---|---| +| Account | Name, Industry, Phone, Description | +| Asset | Name, SerialNumber, InstallDate, Description, Status | +| Contact | Name, Title, Phone, Email | +| ServiceAppointment | AppointmentNumber, SchedStartTime, SchedEndTime, ArrivalWindowStartTime, ArrivalWindowEndTime, EarliestStartTime, DueDate, Description, Status, Subject | + +Avoid `Asset.ProductDescription` — it's read-only for non-admin profiles in some org configurations. Use `Asset.Description` for narrative text instead. + +## Flow structure + +Connector chain: `start → GetAccount → GetAsset → GetMaintenanceContract → GetRefrigerantLog → GetServiceAppointment → GetContact → BuildPrompt`. + +All record lookups use `getFirstRecordOnly=true`. The `BuildPrompt` assignment writes a structured `$Output.Prompt` with sections: Work Order, Customer, On-site Contact, Asset, Maintenance Contract, Most Recent Refrigerant Log, Service Appointment. + +`GetRefrigerantLog` sorts by `Last_Service_Date__c DESC` to surface the latest entry. + +## Prompt template section structure + +Four sections, in order: + +1. **Mission and contact** — opens with active-voice imperative ("Perform a refrigerant top-up..."), names contact + phone + email, ends with "do not leave the site until the issue is resolved." +2. **Customer and SLA** — customer name, SLA tier, coverage scope. Call out Platinum/Gold tiers as high-priority. +3. **Site access and certifications** — site access notes, required certifications. Instruct technician to verify they hold the certs before arrival. +4. **Equipment and refrigerant context** — asset (model, serial, install date, status), most recent refrigerant log (type, prior charge, last service date). Call out anomalies: service >12 months ago, charge below normal range. + +## Rules + +- Address the technician directly throughout (second person). +- Opening sentence must use an active-voice verb appropriate to the work. +- For any field not grounded, write `[not provided]`. +- All times in technician's local time zone. +- Brief under 300 words. +- Prose paragraphs, not bullet lists. +- If most recent refrigerant service >12 months ago OR charge level below typical spec for the unit, explicitly note this as a likely contributor to the current issue. + +## Cadence example + +> "Perform a refrigerant top-up and leak inspection on the building's rooftop unit RTU-3. The customer reports inadequate cooling during peak hours; you will verify charge level and locate any leaks before adjusting refrigerant. Your on-site contact is Maria Reyes, Facilities Manager, reachable at 415-555-0118 and m.reyes@example.com. Do not leave the site until the issue is resolved. +> +> This is a Platinum SLA customer (4-hour response commitment). Coverage scope includes preventive maintenance and emergency break/fix on all rooftop units in the building. +> +> Site access requires escort by building security; check in at the loading dock between 7 AM and 6 PM. Confirm before arrival that you hold an active EPA Section 608 Universal certification, which is required for any refrigerant handling on this site. +> +> The asset is a 10-ton rooftop unit installed 2018, currently in service. The most recent refrigerant log shows R-410A charge of 14.2 lbs, last serviced 2025-09-12. Charge level is below the typical 16 lb spec for this unit, which is consistent with the cooling complaint." + +## Test data sample + +Account: "Greenfield Grocery — Mission District" (Retail), with description noting refrigeration criticality. + +Asset: "Rooftop Unit RTU-3 — Produce Section", Serial `CR-RTU-388291`, Carrier 48HC 10-ton, installed 2018-06-12. + +Maintenance_Contract: Platinum (4hr), full coverage scope text, site access notes, required certs `EPA Section 608 Universal, OSHA 10`. + +Refrigerant_Log: R-410A, 14.2 lbs (below 16 lb spec), last service 2025-09-12. + +Contact: Maria Reyes, Store Facilities Manager. + +Work Order: "Refrigerant top-up and leak inspection — RTU-3", High priority, with description detailing the cooling complaint and required diagnostic steps. + +Service Appointment: today, 2-hour window, assigned to the chosen Service Resource. diff --git a/skills/customizing-pre-work-brief/templates/retail-merchandising.md b/skills/customizing-pre-work-brief/templates/retail-merchandising.md new file mode 100644 index 0000000..db5407a --- /dev/null +++ b/skills/customizing-pre-work-brief/templates/retail-merchandising.md @@ -0,0 +1,98 @@ +# Retail Merchandising Field Service Seed Template + +Scaffold. Not yet verified end-to-end against a live org. + +## Business archetype + +This customer provides field merchandising service to retail brands — restocking shelves, setting up promotional displays, capturing planogram-compliance photos, taking competitor pricing surveys — at grocery, drug, mass-merchant, and convenience-store locations. Technicians (often called "merchandisers" or "field reps") visit multiple stores per day with a list of pre-defined tasks per store. Planogram compliance, recent promotional activity, and store-specific access notes are the most useful grounding data. + +## Recommended custom objects + +### `Store_Visit_Plan__c` + +Why: Each store visit has a structured task list: shelf reset, promo build, photo capture, etc. The plan determines whether the visit is 30 minutes or 3 hours. + +| Field | Type | Notes | +|---|---|---| +| `Account__c` | Lookup → Account | `SetNull` | +| `Visit_Type__c` | Picklist (restricted) | Routine Maintenance, New Item Cut-In, Promo Build, Compliance Audit | +| `Estimated_Minutes__c` | Number(4,0) | Time budget for the visit | +| `Tasks__c` | LongTextArea (1000) | Bullet list of tasks to complete | +| `Photos_Required__c` | Number(2,0) | How many photos the rep must capture | +| `Submission_Deadline__c` | DateTime | When the visit report must be uploaded | + +Name field: AutoNumber `SVP-{0000}`. + +### `Planogram_Compliance__c` + +Why: A photo from the last visit showing what compliance looked like is the single most useful piece of grounding data — it tells the rep what the shelf should look like and what to fix. + +| Field | Type | Notes | +|---|---|---| +| `Account__c` | Lookup → Account | `SetNull` | +| `Visit_Date__c` | Date | When this compliance check was performed | +| `Compliance_Score__c` | Percent(5,2) | 0-100% compliance with the planogram | +| `Issues_Found__c` | LongTextArea (1000) | Free-text list of issues | +| `Photo_Url__c` | URL(255) | Link to the most recent shelf photo | +| `Resolved_By_Visit__c` | Lookup → WorkOrder | Which visit fixed the issues | + +Name field: AutoNumber `PG-{0000}`. Enable history tracking. + +## WorkOrder field additions + +| Field | Type | Notes | +|---|---|---| +| `Store_Visit_Plan__c` | Lookup → Store_Visit_Plan__c | The plan driving this visit | +| `Store_Access_Window__c` | Text(100) | "6 AM - 10 AM only", "Backroom only after 8 PM", etc. | + +## Standard objects + fields to query + +| Object | Fields | +|---|---| +| Account | Name, Industry, Phone, BillingAddress, Description | +| Contact | Name, Title, Phone, Email | +| ServiceAppointment | AppointmentNumber, SchedStartTime, SchedEndTime, Address, Description, Status, Subject | + +Note: this vertical typically does not use `Asset` (no specific equipment is being serviced). The "asset" is the shelf or display itself, which doesn't usually live as an Asset record. + +## Flow structure + +Connector chain: `start → GetAccount → GetVisitPlan → GetMostRecentCompliance → GetServiceAppointment → GetContact → BuildPrompt`. + +`GetMostRecentCompliance` sorts by `Visit_Date__c DESC LIMIT 1`. + +## Prompt template section structure + +1. **Mission and time budget** — visit type, estimated time, store name + location. End with submission deadline. +2. **Task list** — the tasks from the visit plan, formatted as a numbered list. Photos required count. +3. **Last visit context** — most recent compliance score, key issues found, link to the prior photo if available. +4. **Store access** — access window (6 AM-10 AM, etc.), store contact name + phone, any backroom or stockroom notes. + +## Rules + +- Address the rep directly throughout. (Note: "rep" or "merchandiser" not "technician" for this vertical.) +- Lead with the time budget so the rep knows whether they're tight or have slack. +- If the last visit's compliance score was below 70%, lead the task list with the issues that need fixing first. +- For any field not grounded, write `[not provided]`. + +## Cadence example + +> "Perform a routine maintenance visit at Stop & Shop store #4218 in Quincy, MA. Estimated time: 45 minutes. Submission deadline is end of day today. +> +> Today's tasks: (1) restock the snack endcap to planogram, (2) verify the new chip flavor is on the secondary display in aisle 7, (3) capture 3 photos of the snack endcap and the chip secondary, (4) submit before 6 PM. +> +> Last visit was 14 days ago. Compliance score was 82%. Issues at that time: the salty-snack endcap was out of stock on two SKUs, and the secondary display had old promotional signage. The store manager committed to backroom restock; verify those two SKUs are now on the shelf. +> +> Store access: front entrance only between 6 AM and 10 AM. Your store contact is Dana Liu, store manager, reachable at the phone and email on file." + +## Test data sample + +Account: "Stop & Shop #4218 — Quincy MA" (Retail). + +Store_Visit_Plan: routine maintenance, 45 minutes, 4 tasks, 3 photos required, end-of-day deadline. + +Planogram_Compliance: most recent 14 days ago, score 82%, two SKU OOS issues. + +Contact: Dana Liu, store manager. + +Work Order: "Snack endcap restock + secondary display photo — Stop & Shop #4218", routine priority. diff --git a/skills/customizing-pre-work-brief/templates/telecom.md b/skills/customizing-pre-work-brief/templates/telecom.md new file mode 100644 index 0000000..4ff88eb --- /dev/null +++ b/skills/customizing-pre-work-brief/templates/telecom.md @@ -0,0 +1,100 @@ +# Telecom / Fiber Install Seed Template + +Scaffold. Not yet verified end-to-end against a live org. + +## Business archetype + +This customer provides residential and small-business telecommunications service — fiber-to-the-home, broadband internet, voice — and dispatches technicians for new-customer installs, service drops, outage repair, and equipment swaps. Signal strength readings, ONT (optical network terminal) serial numbers, and recent outage history at the address are the most useful grounding data for a pre-work brief. + +## Recommended custom objects + +### `Service_Drop__c` + +Why: Each install or repair visit interacts with a specific service drop (the physical line from the street to the customer). Signal-strength history at the drop predicts whether the visit will be a quick swap or a longer troubleshoot. + +| Field | Type | Notes | +|---|---|---| +| `Account__c` | Lookup → Account | `SetNull` | +| `Asset__c` | Lookup → Asset | The ONT or modem at this drop | +| `Signal_Strength_Db__c` | Number(5,2) | Last measured signal strength in dBm | +| `Last_Reading_Date__c` | DateTime | When the signal was last measured | +| `ONT_Serial__c` | Text(50) | Optical network terminal serial number | +| `Connection_Type__c` | Picklist (restricted) | Fiber, Copper, Coax, Hybrid | + +Name field: AutoNumber `SD-{0000}`. + +### `Outage_History__c` + +Why: A pattern of outages at the address (vs. a one-off) suggests the technician is walking into a recurring problem, not an isolated incident. + +| Field | Type | Notes | +|---|---|---| +| `Account__c` | Lookup → Account | `SetNull` | +| `Outage_Start__c` | DateTime | When the outage began | +| `Duration_Minutes__c` | Number(6,0) | How long it lasted | +| `Resolution_Type__c` | Picklist (restricted) | Self-healed, Tech-resolved, CenOps-resolved, Carrier escalation | +| `Ticket_Reference__c` | Text(50) | Internal incident ticket number | +| `Notes__c` | LongTextArea (500) | What was found and fixed | + +Name field: AutoNumber `OH-{0000}`. Enable history tracking. + +## WorkOrder field additions + +| Field | Type | Notes | +|---|---|---| +| `Service_Drop__c` | Lookup → Service_Drop__c | Which drop this WO services | +| `Install_Type__c` | Picklist (restricted) | New Install, Upgrade, Repair, Disconnect | + +## Standard objects + fields to query + +| Object | Fields | +|---|---| +| Account | Name, Phone, BillingAddress, Description | +| Asset | Name, SerialNumber, InstallDate, Description, Status | +| Contact | Name, Phone, Email, MobilePhone | +| ServiceAppointment | AppointmentNumber, SchedStartTime, SchedEndTime, ArrivalWindowStartTime, ArrivalWindowEndTime, Address, Description, Status, Subject | + +## Flow structure + +Connector chain: `start → GetAccount → GetServiceDrop → GetAsset → GetOutageHistory → GetServiceAppointment → GetContact → BuildPrompt`. + +`GetOutageHistory` sorts by `Outage_Start__c DESC LIMIT 5` for the 5 most-recent incidents at the address. + +`GetServiceDrop` filters by `Id = $Input.WorkOrder.Service_Drop__c`. + +## Prompt template section structure + +1. **Mission and contact** — work to perform, customer name + phone + email. +2. **Service drop snapshot** — connection type, ONT serial, last signal reading + date. Call out signal degradation (>5 dBm drop from a typical -20 dBm baseline) as a likely root cause. +3. **Recent outage history** — count + dates of outages in the last 90 days. If 3+ outages in 30 days, flag as recurring problem. +4. **Appointment timing** — local arrival window, scheduled start/end. + +## Rules + +- Address the technician directly throughout. +- For "Repair" install types where outage history shows a recurring problem, recommend the technician check upstream (street-side) infrastructure before swapping customer equipment. +- For any field not grounded, write `[not provided]`. + +## Cadence example + +> "Perform a service-drop repair at the customer's residence. The customer reports intermittent outages over the last 10 days. Your contact is the account holder, reachable at the phone and email on file. Do not leave the site until you have confirmed sustained signal and tested speed at the ONT. +> +> The drop is a fiber connection terminating at ONT serial NS-FOH-882910. Last signal reading was -29.4 dBm on 2026-05-12, which is 9 dBm below the typical -20 dBm baseline for this neighborhood and is consistent with the customer's outage complaints. +> +> Outage history at this address: 4 incidents in the last 30 days, all resolved by the customer's modem auto-recovering after 2-15 minutes. This is a recurring pattern, not a one-off — check the street-side splitter and the OLT port assignment before swapping the ONT. +> +> Your appointment window is 1:00 PM to 5:00 PM today, with a scheduled start of 2:30 PM." + +## Test data sample + +Account: "Reyes Residence — 22 Chestnut St", with phone + billing address. + +Asset: "ONT NS-FOH-882910", installed 2023-08-15. + +Service_Drop: signal -29.4 dBm last reading, fiber connection. + +Outage_History: 4 entries in last 30 days, all self-healed. + +Contact: account holder. + +Work Order: "Recurring outage investigation at 22 Chestnut St", Medium priority, install type Repair. diff --git a/skills/setting-up-pre-work-brief/SKILL.md b/skills/setting-up-pre-work-brief/SKILL.md new file mode 100644 index 0000000..bc50e8a --- /dev/null +++ b/skills/setting-up-pre-work-brief/SKILL.md @@ -0,0 +1,1033 @@ +--- +name: setting-up-pre-work-brief +description: "Set up Pre-Work Brief end-to-end on Field Service Mobile in a Salesforce org. TRIGGER when: user asks to enable, configure, install, or set up Pre-Work Brief or PWB; user asks to turn on the AI brief on Work Order in the Field Service mobile app; user wants to deploy or activate the Field Service Pre-Work Brief prompt template; user wants to wire the Field Service Mobile: Generate Pre-Work Brief flow to a prompt template; user troubleshoots a missing or empty Pre-Work Brief on a Work Order. DO NOT TRIGGER when: user wants Voice to Form (separate skill); user wants Post-Work Summary; user wants to author a brand-new prompt template type from scratch (use Prompt Builder docs); user wants to build a generic Agentforce agent (use developing-agentforce)." +allowed-tools: Bash Read Write Edit Glob Grep +license: Apache-2.0 +metadata: + version: "0.1.0" + last_updated: "2026-05-13" + argument-hint: " [--technician ]" + compatibility: claude-code +--- + +# Setting up Pre-Work Brief + +Configure Pre-Work Brief on Field Service Mobile from a fresh Field Service org. Pre-Work Brief uses generative AI to give a mobile worker a concise summary of their upcoming Work Order, grounded in real Salesforce data. The brief renders in the **Overview tab of a Work Order** in the Field Service mobile app on iOS and Android. + +This skill walks through the complete setup path documented at `help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief.htm`, in the order Salesforce documents it. Each step is idempotent. Re-running the skill on an already-configured org applies zero changes. + +--- + +## Platform Notes + +- All `sf` commands assume Salesforce CLI 2.x or later. +- Replace `jq` with `python -c "import json,sys; ..."` if jq is not installed. +- `$ORG_ALIAS` throughout refers to the target org alias the user has set or supplied. +- Many setup steps are clicks-only in **Setup**. The skill prints exact Quick Find search terms and UI paths for those. + +--- + +## What Pre-Work Brief is + +Pre-Work Brief is built on three Salesforce primitives: + +1. A **prompt template** of type `Field Service Pre-Work Brief` in Prompt Builder. +2. The managed flow **Field Service Mobile: Generate Pre-Work Brief**, which gathers the grounding data (Account, Case, Contact, Work Order, Work Plan, Work Step, Service Appointment, and more) and feeds it to the prompt. +3. A field on the Work Order object — **Pre-Work Brief Prompt Template ID** — that points each Work Order at the prompt template it should run. + +When a mobile worker opens a Work Order in the Field Service app while connected to the internet, the brief is generated once and rendered in the Overview tab. It is regenerated only after 24 hours. + +**Editions and licensing:** Enterprise, Performance, or Unlimited Edition with the **Einstein for Field Service** add-on or the **Agentforce for Field Service** add-on. Also available in Einstein 1 Field Service Edition. Mobile workers must hold the Field Service Mobile license. + +**Salesforce branding note:** Field Service is now Agentforce Field Service and Operations. The product retains "Field Service" in many UI labels and API names. This skill follows the API names that exist in the org today. + +--- + +## Prerequisites + +Before running the setup sequence, confirm all four: + +1. **Edition + add-on.** The org has the Einstein for Field Service or Agentforce for Field Service add-on. See step 1 of the setup sequence. +2. **Lightning Data Service for Field Service Mobile is enabled.** Pre-Work Brief depends on LDS to render correctly on mobile. See step 2. +3. **Einstein generative AI is enabled.** Pre-Work Brief is one of several Einstein generative AI features and shares a base setup path with the rest of them. +4. **Admin user has the right permissions:** `Customize Application` to build and manage Pre-Work Brief, and `Manage Profiles and Permission Sets` to assign permission sets. + +--- + +## Setup Sequence + +Run steps in order. Each step prints its own check before applying changes. If a check fails, the step exits without modifying the org. + +### Step 0: Detect provisioning state and route + +Before walking the setup steps, run four detection queries to classify the org. The results route you into the right path: a fully-provisioned org skips ahead to the customization steps; a partially-provisioned org stops and contacts Salesforce for the AI flow templates; an unprovisioned org stops and contacts the account team for the license. + +```bash +echo "1. Edition + add-on PSL:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT DeveloperName FROM PermissionSetLicense + WHERE (DeveloperName LIKE '%FieldService%' OR MasterLabel LIKE '%Field Service%') + AND (DeveloperName LIKE '%Einstein%' OR DeveloperName LIKE '%Agentforce%' + OR MasterLabel LIKE '%Einstein%' OR MasterLabel LIKE '%Agentforce%')" \ + --json | jq -r '.result.totalSize as $n | + if $n > 0 then "PRESENT (\($n) row(s))" else "MISSING" end' + +echo "2. Three permission sets:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Name FROM PermissionSet + WHERE Name IN ('EinsteinFieldServiceUser','EinsteinGPTPromptTemplateManager','EinsteinGPTPromptTemplateUser')" \ + --json | jq -r '.result.totalSize as $n | + if $n == 3 then "PRESENT (all 3)" elif $n > 0 then "PARTIAL (\($n)/3)" else "MISSING" end' + +echo "3. Work Order PreWorkBriefPromptTemplate field:" +sf sobject describe --sobject WorkOrder --target-org "$ORG_ALIAS" --json | \ + jq -r '.result.fields | map(select(.name == "PreWorkBriefPromptTemplate")) | length as $n | + if $n > 0 then "PRESENT" else "MISSING" end' + +echo "4. Pre-Work Brief prompt template (Pre_Work_Brief or any with PWB type):" +sf org list metadata --metadata-type GenAiPromptTemplate --target-org "$ORG_ALIAS" --json | \ + jq -r '[.result[]? | select((.fullName // "") | test("PreWorkBrief|Pre_Work_Brief"; "i"))] | length as $n | + if $n > 0 then "PRESENT (\($n))" else "MISSING — deploy in step 5" end' + +echo "5. Lightning SDK for Field Service Mobile (enableLsdkMode):" +sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT FullName, Metadata FROM FieldServiceSettings" --json | \ + jq -r '.result.records[0].Metadata.enableLsdkMode as $v | + if $v == true then "ENABLED" elif $v == false then "DISABLED (auto-fixable, see Step 2)" else "UNKNOWN" end' +``` + +Interpret the five results to classify the org: + +| Diagnostic 1 (PSL) | Diagnostic 4 (template) | State | What to do | +|---|---|---|---| +| MISSING | any | **Unprovisioned** | Stop. Contact your Salesforce account executive to purchase the Einstein for Field Service or Agentforce for Field Service add-on. The skill cannot continue. | +| PRESENT | MISSING | **Template not yet deployed** | Continue to step 1. Step 5 deploys the Pre-Work Brief prompt template via metadata. The backing flow `sfdc_fieldservice__GenPreWorkBrief` is a platform-level managed flow that ships with the Einstein for Field Service add-on; it is not metadata-listed but is referenced by the template. | +| PRESENT | PRESENT | **Fully provisioned** | Continue to step 1. Step 5 will detect the existing template and skip re-deploy. | + +Diagnostics 2 and 3 are sub-signals. If diagnostic 1 is PRESENT but 2 or 3 are MISSING, the Einstein generative AI base setup (step 3 below) hasn't completed in this org yet. Run that first, then re-check. + +Diagnostic 5 is the Lightning SDK toggle. If it returns DISABLED, run step 2 — the skill enables it via a metadata deploy without requiring a Setup UI click. + +**Note on the managed flow.** Earlier versions of this skill checked for a Flow named `PreWorkBrief` in metadata listings. That check returns zero rows even in fully-working orgs, because the backing flow `sfdc_fieldservice__GenPreWorkBrief` is provisioned at the platform layer and is not exposed via the standard metadata API. The flow is real, addressable from Prompt Builder's Resource → Flows dropdown as `{!$Flow:sfdc_fieldservice__GenPreWorkBrief.Prompt}`, and used by the prompt template the skill deploys in step 5. Diagnostic 4 has been updated to check for the prompt template directly. + +### Step 0.5: Choose the target technician user + +Pre-Work Brief is a per-user feature. Each technician who should see the brief needs three things assigned: the Field Service Mobile license, the Einstein for Field Service PSL, and the Einstein for Field Service permission sets. The skill targets one technician at a time so the admin can pilot with a specific user before rolling out broadly. + +Ask the admin: **"Do you have a specific technician user you want to enable Pre-Work Brief for?"** + +**If yes:** the admin supplies a username or User Id. Validate that the user is active, has a `ServiceResource` record, and is `ResourceType = 'T'` (technician): + +```bash +TECH_USER="" + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, Name, Username, IsActive, Profile.Name FROM User WHERE (Username = '$TECH_USER' OR Id = '$TECH_USER') AND IsActive = true" \ + --json | jq -r '.result.records[]? | "User: \(.Name) | \(.Username) | \(.Id) | profile=\(.Profile.Name)"' + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, Name, ResourceType FROM ServiceResource WHERE RelatedRecordId IN (SELECT Id FROM User WHERE Username = '$TECH_USER' OR Id = '$TECH_USER') AND IsActive = true" \ + --json | jq -r '.result.records[]? | "ServiceResource: \(.Name) | \(.Id) | type=\(.ResourceType)"' +``` + +If neither query returns a row, the supplied user isn't a technician in this org. Stop and ask for a different one. + +**If no:** auto-pick a candidate. Pick the first active technician whose user is also active and is on a non-administrator profile, and surface the choice to the admin so they know which user the skill is configuring: + +```bash +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, Name, RelatedRecord.Id, RelatedRecord.Username, RelatedRecord.Name, RelatedRecord.Profile.Name FROM ServiceResource WHERE IsActive = true AND ResourceType = 'T' AND RelatedRecordId != null AND RelatedRecord.IsActive = true AND RelatedRecord.Profile.Name NOT IN ('System Administrator') AND (NOT RelatedRecord.Profile.Name LIKE '%Customer Community%') AND (NOT RelatedRecord.Profile.Name LIKE '%Partner Community%') ORDER BY Name LIMIT 1" \ + --json | jq -r '.result.records[]? | "Auto-selected technician for Pre-Work Brief setup:\n ServiceResource: \(.Name) (\(.Id))\n User: \(.RelatedRecord.Name) (\(.RelatedRecord.Username))\n User Id: \(.RelatedRecord.Id)\n Profile: \(.RelatedRecord.Profile.Name)\n\nThe skill will configure Pre-Work Brief for this user. To pick a different one, re-run with --technician ."' +``` + +If the auto-pick query returns zero rows, the org has no technicians on a non-administrator, non-community profile. The admin needs to either create a technician user, or supply a specific username via `--technician` and accept the profile. + +Capture the chosen `User.Id` as `$TECH_USER_ID` and `Username` as `$TECH_USERNAME` for use in step 4. + +### Step 1: Confirm target org and check the Einstein for Field Service license + +```bash +sf org display --target-org "$ORG_ALIAS" --json +``` + +Verify the org has one of the Einstein for Field Service permission set licenses. The DeveloperName values currently shipped are `EinsteinFieldServicePsl` ("Einstein for Field Service") and `EinsteinForFieldServiceMobilePsl` ("Einstein for Field Service Mobile"). Salesforce branding may shift to "Agentforce for Field Service" in future releases; the skill matches both. + +```bash +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT DeveloperName, MasterLabel, TotalLicenses, UsedLicenses + FROM PermissionSetLicense + WHERE (DeveloperName LIKE '%FieldService%' OR MasterLabel LIKE '%Field Service%') + AND (DeveloperName LIKE '%Einstein%' OR DeveloperName LIKE '%Agentforce%' + OR MasterLabel LIKE '%Einstein%' OR MasterLabel LIKE '%Agentforce%')" \ + --json +``` + +- **Pass:** At least one row returned with `TotalLicenses > 0`. +- **Fail:** No row, or all rows have `TotalLicenses = 0`. Stop. Contact your Salesforce account executive to purchase the Einstein for Field Service or Agentforce for Field Service add-on. + +### Step 2: Enable Lightning Data Service for the Field Service mobile app + +Pre-Work Brief renders in an LDS-aware container. Without LDS, the brief shows blank. + +LDS is enabled by default for new orgs and sandboxes from Winter '25. All orgs are auto-migrated by Spring '26. If diagnostic 5 in step 0 returned DISABLED for `enableLsdkMode`, enable it now. + +**Path A — CLI deploy (preferred, no Setup UI needed).** + +Create a tiny SFDX project and deploy a `FieldServiceSettings` metadata file that flips `enableLsdkMode` to `true`: + +```bash +mkdir -p /tmp/fs-lds-enable/force-app/main/default/settings +cat > /tmp/fs-lds-enable/sfdx-project.json <<'EOF' +{ + "packageDirectories": [{"path": "force-app", "default": true}], + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "62.0" +} +EOF +cat > /tmp/fs-lds-enable/force-app/main/default/settings/FieldService.settings-meta.xml <<'EOF' + + + true + true + +EOF +cd /tmp/fs-lds-enable +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 10 +``` + +Verify the toggle flipped: + +```bash +sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT FullName, Metadata FROM FieldServiceSettings" --json | \ + jq '.result.records[0].Metadata.enableLsdkMode' +``` + +Expect `true`. + +**Path B — Setup UI (manual fallback).** In Setup, in the Quick Find box, enter and select **Field Service Settings**. Under **Lightning SDK for Field Service Mobile**, select **Enable Lightning SDK for Field Service Mobile**. + +Reference: `help.salesforce.com/s/articleView?id=service.mfs_lightning_data_service.htm` + +### Step 3: Set up Einstein generative AI in the org + +Before configuring Pre-Work Brief, complete Salesforce's base Einstein generative AI setup. Do not skip the Data 360 portion of that setup. + +Reference: `help.salesforce.com/s/articleView?id=ai.generative_ai_enable.htm` + +### Step 4: Assign Einstein for Field Service PSL and permission sets + +Two users get assignments in this step: the **admin** running the setup (so they can see and create prompt templates in Prompt Builder) and the **technician** chosen in step 0.5 (so they can run the brief on their mobile device). The two roles get different permission sets. + +> **Critical:** if the admin doesn't have `EinsteinGPTPromptTemplateManager` and `EinsteinGPTPromptTemplateUser` assigned, the **Field Service Pre-Work Brief** prompt template type will not appear in Prompt Builder and step 5 will fail with no error message — just a missing option in a dropdown. Don't skip 4a. + +| Role | EinsteinFieldServicePsl | EinsteinFieldServiceUser | EinsteinGPTPromptTemplateManager | EinsteinGPTPromptTemplateUser | +|---|---|---|---|---| +| Admin (creates and manages the prompt template) | yes | yes | **yes** | yes | +| Technician (runs the brief on mobile) | yes | yes | no | yes | + +**4a. Verify the three permission sets exist in the org.** + +```bash +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Name, Label FROM PermissionSet + WHERE Name IN ('EinsteinFieldServiceUser','EinsteinGPTPromptTemplateManager','EinsteinGPTPromptTemplateUser')" \ + --json | jq -r '.result.records[] | "\(.Name) | \(.Label)"' +``` + +All three rows should return. If any are missing, the Einstein generative AI base setup (step 3) hasn't completed for this org. + +**4b. Identify the admin user.** + +The admin is whoever is running this skill — typically the user authenticated via `sf org login`. Capture their username and Id: + +```bash +ADMIN_USERNAME=$(sf org display --target-org "$ORG_ALIAS" --json | jq -r '.result.username') +ADMIN_USER_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id FROM User WHERE Username = '$ADMIN_USERNAME'" --json | \ + jq -r '.result.records[0].Id') +echo "Admin user: $ADMIN_USERNAME ($ADMIN_USER_ID)" +``` + +**4c. Assign PSLs to the admin.** + +```bash +sf org assign permsetlicense --name EinsteinFieldServicePsl --on-behalf-of "$ADMIN_USERNAME" --target-org "$ORG_ALIAS" +sf org assign permsetlicense --name EinsteinForFieldServiceMobilePsl --on-behalf-of "$ADMIN_USERNAME" --target-org "$ORG_ALIAS" 2>/dev/null || true +``` + +**4d. Assign all three permission sets to the admin.** + +The admin gets all three so they can manage prompt templates and run them for previewing. + +```bash +for ps in EinsteinFieldServiceUser EinsteinGPTPromptTemplateManager EinsteinGPTPromptTemplateUser; do + sf org assign permset --name "$ps" --on-behalf-of "$ADMIN_USERNAME" --target-org "$ORG_ALIAS" +done +``` + +**4e. Assign PSLs to the technician.** + +```bash +sf org assign permsetlicense --name EinsteinFieldServicePsl --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS" +sf org assign permsetlicense --name EinsteinForFieldServiceMobilePsl --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS" 2>/dev/null || true +``` + +**4f. Assign two permission sets to the technician.** + +The technician gets `EinsteinFieldServiceUser` (for the Field Service AI feature itself) and `EinsteinGPTPromptTemplateUser` (so they can run the prompt template). They do **not** get `EinsteinGPTPromptTemplateManager` — that's an admin-only permission for editing templates. + +```bash +for ps in EinsteinFieldServiceUser EinsteinGPTPromptTemplateUser; do + sf org assign permset --name "$ps" --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS" +done +``` + +**4g. Verify all assignments landed.** + +```bash +echo "Admin ($ADMIN_USERNAME) PSLs:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT PermissionSetLicense.DeveloperName FROM PermissionSetLicenseAssign + WHERE AssigneeId = '$ADMIN_USER_ID' + AND PermissionSetLicense.DeveloperName LIKE '%Einstein%FieldService%'" \ + --json | jq -r '.result.records[] | " \(.PermissionSetLicense.DeveloperName)"' + +echo "Admin permission sets:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT PermissionSet.Name FROM PermissionSetAssignment + WHERE AssigneeId = '$ADMIN_USER_ID' + AND PermissionSet.Name IN ('EinsteinFieldServiceUser','EinsteinGPTPromptTemplateManager','EinsteinGPTPromptTemplateUser')" \ + --json | jq -r '.result.records[] | " \(.PermissionSet.Name)"' + +echo "" +echo "Technician ($TECH_USERNAME) PSLs:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT PermissionSetLicense.DeveloperName FROM PermissionSetLicenseAssign + WHERE AssigneeId = '$TECH_USER_ID' + AND PermissionSetLicense.DeveloperName LIKE '%Einstein%FieldService%'" \ + --json | jq -r '.result.records[] | " \(.PermissionSetLicense.DeveloperName)"' + +echo "Technician permission sets:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT PermissionSet.Name FROM PermissionSetAssignment + WHERE AssigneeId = '$TECH_USER_ID' + AND PermissionSet.Name IN ('EinsteinFieldServiceUser','EinsteinGPTPromptTemplateManager','EinsteinGPTPromptTemplateUser')" \ + --json | jq -r '.result.records[] | " \(.PermissionSet.Name)"' +``` + +Expected admin output: 3 permission sets and at least `EinsteinFieldServicePsl`. Expected technician output: 2 permission sets (no `Manager`) and at least `EinsteinFieldServicePsl`. + +Reference: `help.salesforce.com/s/articleView?id=service.fs_einstein_gen_ai_setup.htm` + +### Step 5: Ensure exactly one Pre-Work Brief prompt template exists + +Pre-Work Brief is delivered as a `GenAiPromptTemplate` of type `einstein_gpt__fieldServicePreWorkBrief`. The template's content references the platform-level managed flow `sfdc_fieldservice__GenPreWorkBrief`, which ships with the Einstein for Field Service add-on and is addressable from Prompt Builder's Resource dropdown as `{!$Flow:sfdc_fieldservice__GenPreWorkBrief.Prompt}`. + +This step is **detect-then-deploy** so re-running the skill on an already-configured org applies zero changes. + +**5a. Detect existing PWB templates.** + +```bash +sf org list metadata --metadata-type GenAiPromptTemplate --target-org "$ORG_ALIAS" --json | \ + jq -r '.result[]? | select((.fullName // "") | test("PreWorkBrief|Pre_Work_Brief"; "i")) | " \(.fullName) | ns=\(.namespacePrefix // "none") | mgr=\(.manageableState // "?")"' +``` + +| CLI result | Action | +|---|---| +| **One row returned** | Template exists. Skip 5b and 5c. Note the `fullName` for use in step 8. | +| **Zero rows** | Continue to 5b. The skill deploys a reference template. | +| **Multiple rows** | Pick one to standardize on. Use the destructive-deploy command at 5d to remove the others. | + +After running this step, also open Prompt Builder in Setup (Quick Find: **Einstein Generative AI → Prompt Builder**) to visually confirm the template list matches CLI output. If Prompt Builder shows a template that didn't appear in CLI output, it may be a recently-viewed-items artifact rather than a real template — refresh the page to clear. + +**5b. (Only if 5a returned zero) Write the prompt template metadata.** + +Create a tiny SFDX project and write the template XML: + +```bash +mkdir -p /tmp/pwb-template/force-app/main/default/genAiPromptTemplates +cat > /tmp/pwb-template/sfdx-project.json <<'EOF' +{ + "packageDirectories": [{"path": "force-app", "default": true}], + "namespace": "", + "sfdcLoginUrl": "https://login.salesforce.com", + "sourceApiVersion": "62.0" +} +EOF +cat > /tmp/pwb-template/force-app/main/default/genAiPromptTemplates/Pre_Work_Brief.genAiPromptTemplate-meta.xml <<'EOF' + + + Pre_Work_Brief + Pre-Work Brief + + {!$Flow:sfdc_fieldservice__GenPreWorkBrief.Prompt} + + WorkOrder + SOBJECT://WorkOrder + Input:WorkOrder + true + + sfdc_ai__DefaultOpenAIGPT4OmniMini + Published + + flow://sfdc_fieldservice__GenPreWorkBrief + + SOBJECT://WorkOrder + true + WorkOrder + {!$Input:WorkOrder} + + Flow:sfdc_fieldservice__GenPreWorkBrief + + + einstein_gpt__fieldServicePreWorkBrief + Global + +EOF +``` + +Key elements: + +- `einstein_gpt__fieldServicePreWorkBrief` — the prompt template type. Required for Prompt Builder to recognize this as a Field Service Pre-Work Brief template. +- `` — the prompt body. The expression `{!$Flow:sfdc_fieldservice__GenPreWorkBrief.Prompt}` injects the prompt that the platform-level managed flow generates from the Work Order grounding data. +- `` — wires the flow up as the data source. The flow takes a `WorkOrder` SObject input and returns the grounded prompt. +- `sfdc_ai__DefaultOpenAIGPT4OmniMini` — the LLM the template runs on. The `DefaultOpenAIGPT4OmniMini` model is a Salesforce-managed routing alias and the standard for new Field Service prompt templates. +- `Published` — marks the template version as Published. **Note:** this is not the same as Active. After deploy, the template still needs a one-click activation in Prompt Builder UI before it appears in the runtime catalog and can be used by the mobile app. Salesforce does not currently expose a programmatic activation path (we tried Tooling REST PATCH on `IsActive`, Connect API `/activate` endpoints across v62-v66, Apex `ConnectApi.EinsteinLLM` methods, and metadata `activeVersionNumber` — none work). +- `Global` — exposes the template across the org. + +**5c. Deploy, then surface the activation link to the admin.** + +```bash +cd /tmp/pwb-template +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 10 + +# Verify it appears in metadata listing: +DEPLOYED=$(sf org list metadata --metadata-type GenAiPromptTemplate --target-org "$ORG_ALIAS" --json | \ + jq -r '.result[]? | select(.fullName == "Pre_Work_Brief") | .fullName') + +if [ "$DEPLOYED" = "Pre_Work_Brief" ]; then + echo "" + echo "✓ Prompt template Pre_Work_Brief deployed." + echo "" + # Resolve the template Id from the deploy report so we can build a deeplink + TEMPLATE_ID=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT Id FROM DeployRequest WHERE NumberComponentsDeployed > 0 ORDER BY CompletedDate DESC LIMIT 20" --json | \ + jq -r '.result.records[]?.Id' | while read did; do + cand=$(sf project deploy report --target-org "$ORG_ALIAS" --job-id "$did" --json 2>/dev/null | \ + jq -r '.result.details.componentSuccesses[]? | select(.componentType == "GenAiPromptTemplate" and .fullName == "Pre_Work_Brief") | .id' | head -1) + if [ -n "$cand" ]; then echo "$cand"; break; fi + done | head -1) + + echo " Template Id: $TEMPLATE_ID" + echo "" + echo " ⚠ Action required: Activate the template in Prompt Builder." + echo " The template is deployed and Published, but until activated, it" + echo " won't appear in the runtime catalog. The mobile app will fail with" + echo " 'We hit a snag' until activation is complete." + echo "" + echo " Generating sign-in link to the template (valid ~15 minutes):" + sf org open --target-org "$ORG_ALIAS" \ + --path "/lightning/setup/EinsteinPromptStudio/$TEMPLATE_ID/edit" \ + --url-only 2>&1 | grep -oE 'https://[^[:cntrl:][:space:]]+frontdoor[^[:cntrl:][:space:]]+' | head -1 | sed 's/\x1b\[[0-9;]*m//g' + echo "" + echo " Click the link above, then in Prompt Builder click the **Activate**" + echo " button (typically top-right of the editor). After activation," + echo " re-run the verification query below to confirm." + echo "" +fi + +# Verify activation took effect: +sf api request rest "/services/data/v62.0/einstein/prompt-templates?pageSize=200" --target-org "$ORG_ALIAS" 2>/dev/null | \ + python3 -c " +import sys, json +d = json.loads(sys.stdin.read()) +hit = [t for t in d.get('promptRecords', []) if t.get('fields', {}).get('DeveloperName', {}).get('value') == 'Pre_Work_Brief'] +print('✓ Active in runtime catalog' if hit else '✗ Not yet active — click Activate in Prompt Builder')" +``` + +Expect to see "✓ Active in runtime catalog" once the admin clicks Activate. To preview the template content before activation, click into the template, enter a Work Order Id under **Work Order**, and click Preview — the Resolution pane will show the prompt that will be generated. + +**Optional: customize the prompt content.** The `` block above is the minimal form — just the flow reference. To layer custom instructions on top of the flow's grounded data, replace `` with a multi-line block like: + +```xml +You are tasked with completing maintenance on Asset: {!$Input:WorkOrder.Asset.Name} +for {!$Input:WorkOrder.WorkOrderNumber}. + +Specific Job Information: +{!$Flow:sfdc_fieldservice__GenPreWorkBrief.Prompt} + +Always include a Summary section and an On-Site Considerations bullet list. +``` + +Tune as needed and re-deploy. + +**5d. (Only if cleanup needed) Remove an admin-deployed duplicate template.** + +If the org has both a platform-managed Pre-Work Brief template and a previously admin-deployed one (for example, from an earlier run of this skill), delete the admin-deployed template: + +```bash +mkdir -p /tmp/pwb-cleanup +cat > /tmp/pwb-cleanup/destructiveChanges.xml <<'EOF' + + + + Pre_Work_Brief + GenAiPromptTemplate + + 62.0 + +EOF +cat > /tmp/pwb-cleanup/empty-package.xml <<'EOF' + + + 62.0 + +EOF +cat > /tmp/pwb-cleanup/sfdx-project.json <<'EOF' +{"packageDirectories": [{"path": "force-app", "default": true}], "namespace": "", "sourceApiVersion": "62.0"} +EOF +mkdir -p /tmp/pwb-cleanup/force-app +cd /tmp/pwb-cleanup +sf project deploy start --target-org "$ORG_ALIAS" --manifest empty-package.xml --post-destructive-changes destructiveChanges.xml --wait 10 +``` + +Replace `Pre_Work_Brief` with the developer name of whichever template you're removing. Platform-managed templates (those without an `mgr=unmanaged` flag in the CLI listing) cannot be deleted this way; if you want to suppress those, hide them via Prompt Builder UI access controls instead. + +### Step 6: Verify the data the flow uses exists in your org + +The default Pre-Work Brief flow grounds on a fixed set of objects and fields documented at `help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief_data.htm`. The objects are: Account, Case, Contact, Pricebook 2, Service Appointment, Work Order, Work Order Line Item, Work Plan, Work Step. + +If the default flow doesn't run, walk the field list in the Help article above. For any field that doesn't exist in your org, edit the flow to remove that field. For each field that does exist, confirm field-level security is set to **Visible** for the relevant profiles, and that mobile workers have access. + +```bash +# Read the default field list: +sf sobject describe --sobject WorkOrder --target-org "$ORG_ALIAS" --json | \ + jq '.fields[] | {name, label, type}' | head -40 +``` + +Repeat for Account, Case, Contact, Pricebook2, ServiceAppointment, WorkOrderLineItem, WorkPlan, WorkStep. + +### Step 7: Add the Pre-Work Brief Prompt Template ID field to the Work Order layout + +The brief is gated per Work Order by a field on the Work Order object: `PreWorkBriefPromptTemplate` (label "Pre-Work Brief Prompt Template ID"). The field exists in the org once the Einstein for Field Service add-on is provisioned; it just needs to be added to the page layout assigned to the technician's profile. + +**7a. Identify the layout assigned to the technician's profile.** + +```bash +TECH_PROFILE_NAME=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Profile.Name FROM User WHERE Username = '$TECH_USERNAME'" --json | \ + jq -r '.result.records[0].Profile.Name') + +LAYOUT_NAME=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT Layout.Name FROM ProfileLayout WHERE Profile.Name = '$TECH_PROFILE_NAME' AND Layout.TableEnumOrId = 'WorkOrder'" --json | \ + jq -r '.result.records[0].Layout.Name') + +LAYOUT_FULLNAME="WorkOrder-$LAYOUT_NAME" +echo "Updating layout: $LAYOUT_FULLNAME (assigned to $TECH_PROFILE_NAME)" +``` + +**7b. Retrieve, edit, and re-deploy the layout.** + +```bash +mkdir -p /tmp/wo-layout/force-app/main/default +cat > /tmp/wo-layout/sfdx-project.json <<'EOF' +{"packageDirectories":[{"path":"force-app","default":true}],"namespace":"","sourceApiVersion":"62.0"} +EOF +cat > /tmp/wo-layout/package.xml < + + + $LAYOUT_FULLNAME + Layout + + 62.0 + +EOF +cd /tmp/wo-layout +sf project retrieve start --manifest package.xml --target-org "$ORG_ALIAS" + +# Insert PreWorkBriefPromptTemplate as a new layoutItem in the first column +# of the first layoutSection (typically labeled "Information"): +LAYOUT_FILE="force-app/main/default/layouts/$LAYOUT_FULLNAME.layout-meta.xml" +python3 - <<'PY' "$LAYOUT_FILE" +import sys, re +path = sys.argv[1] +with open(path, "r") as f: + text = f.read() +if "PreWorkBriefPromptTemplate" in text: + print("Field already on layout; no change.") + sys.exit(0) +# Inject before the first : +inject = """ + Edit + PreWorkBriefPromptTemplate + + """ +new_text = text.replace(" ", inject, 1) +with open(path, "w") as f: + f.write(new_text) +print("Field inserted.") +PY + +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 5 +``` + +**7c. Verify the field is on the layout.** + +```bash +cd /tmp/wo-layout && sf project retrieve start --manifest package.xml --target-org "$ORG_ALIAS" +grep -c "PreWorkBriefPromptTemplate" "force-app/main/default/layouts/$LAYOUT_FULLNAME.layout-meta.xml" +``` + +Expect `1`. The field is now visible to anyone whose profile is assigned this layout. + +**7d. Grant field-level security via a scoped permission set.** + +The `PreWorkBriefPromptTemplate` field needs Read/Edit access on every user who interacts with Pre-Work Brief: the admin (to set the field on test Work Orders) and the technician (so the field renders correctly on the mobile record page). + +Rather than editing the technician's Profile XML directly (risky — Profile deploys can stomp on unrelated FLS settings), the skill deploys a tiny dedicated permission set and assigns it to both users. Idempotent and scoped. + +```bash +mkdir -p /tmp/pwb-fls/force-app/main/default/permissionsets +cat > /tmp/pwb-fls/sfdx-project.json <<'EOF' +{"packageDirectories":[{"path":"force-app","default":true}],"namespace":"","sourceApiVersion":"62.0"} +EOF +cat > /tmp/pwb-fls/force-app/main/default/permissionsets/PreWorkBrief_Field_Access.permissionset-meta.xml <<'EOF' + + + Grants Read/Edit on Work Order.PreWorkBriefPromptTemplate. Assign to admins (so they can set the field on test Work Orders) and to mobile technicians (so the field is readable when the brief renders). + false + + + true + WorkOrder.PreWorkBriefPromptTemplate + true + + +EOF + +cd /tmp/pwb-fls +sf project deploy start --target-org "$ORG_ALIAS" --source-dir force-app --wait 5 + +# Assign to admin and technician: +sf org assign permset --name PreWorkBrief_Field_Access --on-behalf-of "$ADMIN_USERNAME" --target-org "$ORG_ALIAS" +sf org assign permset --name PreWorkBrief_Field_Access --on-behalf-of "$TECH_USERNAME" --target-org "$ORG_ALIAS" + +# Verify: +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Assignee.Username FROM PermissionSetAssignment WHERE PermissionSet.Name = 'PreWorkBrief_Field_Access'" \ + --json | jq -r '.result.records[]? | " \(.Assignee.Username)"' +``` + +Expect both `$ADMIN_USERNAME` and `$TECH_USERNAME` to appear in the verify output. + +### Step 8: Wire the prompt template to a Work Order for testing + +Each Work Order points at a prompt template via the `PreWorkBriefPromptTemplate` field. The skill gives the admin two paths: **8-existing** mutates a Work Order already in the org, or **8-fresh** creates a new test Work Order plus an auto-scheduled Service Appointment assigned to the chosen technician. Path 8-fresh is the recommended default — the admin gets a known-good test artifact without touching live records. + +Ask the admin: **"Create a fresh test Work Order, or set the prompt template Id on an existing one?"** + +- Fresh (recommended): jump to **Step 8-fresh**. +- Existing: continue with **Step 8-existing**. + +### Step 8-existing: Set the prompt template Id on an existing Work Order + +Use this path when the admin wants to point a real, in-pipeline Work Order at the prompt template — for example, to test against a job whose data the technician already knows. + +**8a. Get the prompt template Id from a deploy record.** + +`GenAiPromptTemplate` is not queryable via SOQL or standard REST, so the Id has to come from a side channel. The most reliable CLI path is to read it from the deploy record that created the template (the deploy report records the component Id Salesforce assigned). + +```bash +TEMPLATE_DEV_NAME="Pre_Work_Brief" # or whatever exists in the org + +# Find recent deploys, scan each for a GenAiPromptTemplate component matching the developer name: +DEPLOY_IDS=$(sf data query --target-org "$ORG_ALIAS" --use-tooling-api \ + --query "SELECT Id FROM DeployRequest WHERE NumberComponentsDeployed > 0 ORDER BY CompletedDate DESC LIMIT 50" \ + --json | jq -r '.result.records[]?.Id') + +TEMPLATE_ID="" +for did in $DEPLOY_IDS; do + cand=$(sf project deploy report --target-org "$ORG_ALIAS" --job-id "$did" --json 2>/dev/null | \ + jq -r ".result.details.componentSuccesses[]? | select(.componentType == \"GenAiPromptTemplate\" and .fullName == \"$TEMPLATE_DEV_NAME\") | .id" | head -1) + if [ -n "$cand" ]; then + TEMPLATE_ID="$cand" + break + fi +done +echo "Template Id: $TEMPLATE_ID" +``` + +If `TEMPLATE_ID` is empty, the template wasn't deployed via this org's recent deploy history. Two fallbacks: + +- **Re-deploy** the template (step 5) so a new deploy record carries the Id. +- **Read the Id from Prompt Builder URL.** In Setup, Quick Find **Einstein Generative AI → Prompt Builder**, click into the template, copy the Id from the URL after `EinsteinPromptStudio/`. Example URL `.../lightning/setup/EinsteinPromptStudio/0hfSG000001SUFVYA4/edit?...` → Id is `0hfSG000001SUFVYA4`. + +```bash +# Manual fallback if CLI lookup didn't find it: +TEMPLATE_ID="" +``` + +**8b. Choose the target Work Order.** + +Ask the admin: **"Do you have a specific Work Order you want to add Pre-Work Brief to for testing?"** + +**If yes:** the admin supplies a Work Order Id or Number. Validate: + +```bash +WO_INPUT="" + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, WorkOrderNumber, Subject, Status, Account.Name FROM WorkOrder + WHERE (Id = '$WO_INPUT' OR WorkOrderNumber = '$WO_INPUT') LIMIT 1" \ + --json | jq -r '.result.records[]? | + "Test Work Order:\n Id: \(.Id)\n Number: \(.WorkOrderNumber)\n Subject: \(.Subject)\n Account: \(.Account.Name)\n Status: \(.Status)"' + +WO_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id FROM WorkOrder WHERE Id = '$WO_INPUT' OR WorkOrderNumber = '$WO_INPUT' LIMIT 1" --json | \ + jq -r '.result.records[0].Id') +``` + +If validation returns nothing, stop and ask for a different Id or Number. + +**If no:** auto-pick a Work Order. Pick one that has groundable data (Account, Subject, Status of New / Scheduled / In Progress) and surface it to the admin: + +```bash +WO_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id FROM WorkOrder + WHERE AccountId != null AND Subject != null + AND Status IN ('New','Scheduled','Dispatched','In Progress') + ORDER BY CreatedDate DESC LIMIT 1" --json | \ + jq -r '.result.records[0].Id') + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, WorkOrderNumber, Subject, Account.Name, Status FROM WorkOrder WHERE Id = '$WO_ID'" \ + --json | jq -r '.result.records[]? | + "Auto-selected Work Order for Pre-Work Brief test:\n Id: \(.Id)\n Number: \(.WorkOrderNumber)\n Subject: \(.Subject)\n Account: \(.Account.Name)\n Status: \(.Status)\n\nThe skill will set the Pre-Work Brief Prompt Template Id on this Work Order. To pick a different one, re-run with --workorder ."' +``` + +**8c. Set the field on the Work Order.** + +```bash +sf data update record --target-org "$ORG_ALIAS" \ + --sobject WorkOrder --record-id "$WO_ID" \ + --values "PreWorkBriefPromptTemplate=$TEMPLATE_ID" +``` + +**8d. Schedule the Work Order's Service Appointment for today.** + +The Field Service Mobile app shows technicians their schedule for today. Even if a Work Order has the prompt template Id set, the technician won't see it on their mobile schedule unless the related Service Appointment falls in today's date window. The skill updates the Service Appointment's `SchedStartTime` and `SchedEndTime` to a 2-hour window in the next few hours, and also sets the Work Order's `StartDate` and `EndDate` to today. + +```bash +SA_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id FROM ServiceAppointment WHERE ParentRecordId = '$WO_ID' LIMIT 1" --json | \ + jq -r '.result.records[0].Id // empty') + +# Compute timestamps in UTC. Window: 1 hour from now, 2 hours long. +# macOS / BSD date: +START_TIME=$(date -u -v+1H +"%Y-%m-%dT%H:00:00.000Z" 2>/dev/null || \ + # GNU date fallback: + date -u -d '+1 hour' +"%Y-%m-%dT%H:00:00.000Z") +END_TIME=$(date -u -v+3H +"%Y-%m-%dT%H:00:00.000Z" 2>/dev/null || \ + date -u -d '+3 hours' +"%Y-%m-%dT%H:00:00.000Z") +TODAY=$(date -u +"%Y-%m-%d") + +echo "Scheduling Work Order $WO_ID for today:" +echo " WO StartDate / EndDate: $TODAY" +echo " SA SchedStartTime: $START_TIME" +echo " SA SchedEndTime: $END_TIME" + +# Update Work Order date window: +sf data update record --target-org "$ORG_ALIAS" \ + --sobject WorkOrder --record-id "$WO_ID" \ + --values "StartDate=$TODAY EndDate=$TODAY" + +# Update Service Appointment if one exists: +if [ -n "$SA_ID" ]; then + sf data update record --target-org "$ORG_ALIAS" \ + --sobject ServiceAppointment --record-id "$SA_ID" \ + --values "SchedStartTime=$START_TIME SchedEndTime=$END_TIME" +else + echo " Note: no Service Appointment found for this Work Order. The technician" + echo " may not see the WO in today's schedule. Either pick a Work Order that" + echo " has a Service Appointment, or create one manually before testing." +fi +``` + +Some Field Service orgs assign the Service Appointment to a specific Service Resource; if that's the case, the Work Order will only appear on that resource's schedule. If the auto-picked Work Order's appointment is assigned to a different resource than the chosen technician, either pick a Work Order whose appointment is already assigned to the technician, or assign one manually. + +**8e. Verify all updates.** + +```bash +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT WorkOrderNumber, PreWorkBriefPromptTemplate, StartDate, EndDate FROM WorkOrder WHERE Id = '$WO_ID'" \ + --json | jq -r '.result.records[0]' + +if [ -n "$SA_ID" ]; then + sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT AppointmentNumber, SchedStartTime, SchedEndTime, Status FROM ServiceAppointment WHERE Id = '$SA_ID'" \ + --json | jq -r '.result.records[0]' +fi +``` + +Confirm `PreWorkBriefPromptTemplate` matches `$TEMPLATE_ID`, dates fall on today, and the Service Appointment time window is in the next few hours. + +**8e. (Optional) Roll out to many Work Orders.** Once the test Work Order works on-device (step 9), set the field on additional Work Orders. Two patterns: + +- **Bulk update by CSV.** Export a list of Work Order Ids, build a CSV with `Id,PreWorkBriefPromptTemplate`, and use `sf data import bulk`. +- **Auto-fill via a Flow.** Salesforce's documented production pattern: a record-triggered Flow on Work Order creation that sets `PreWorkBriefPromptTemplate` based on Work Type, Subject, or any other rule. Use the afv-library `generating-flow` skill to build the Flow. See `help.salesforce.com/s/articleView?id=platform.flow.htm`. + +### Step 8-fresh: Create a fresh test Work Order + Service Appointment + +A fresh record is cleaner than mutating one in the org's existing pipeline. Existing operations keep working, and the admin gets a clearly-labeled artifact to validate Pre-Work Brief end-to-end. + +The path needs a Service Resource so the auto-created Service Appointment lands on a real technician's schedule. The skill defaults to the technician chosen in Step 0.5; if that user doesn't have a `ServiceResource` row, the admin supplies one. + +**8-fresh-a. Resolve the Service Resource.** + +```bash +SERVICE_RESOURCE_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id FROM ServiceResource WHERE RelatedRecordId = '$TECH_USER_ID' AND IsActive = true LIMIT 1" --json | \ + jq -r '.result.records[0].Id // empty') + +if [ -z "$SERVICE_RESOURCE_ID" ]; then + echo "No active ServiceResource found for technician $TECH_USERNAME." + echo "Available active technician resources:" + sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, Name, RelatedRecord.Username FROM ServiceResource WHERE IsActive = true AND ResourceType = 'T' AND RelatedRecord.IsActive = true ORDER BY Name LIMIT 10" \ + --json | jq -r '.result.records[]? | " \(.Id) | \(.Name) | \(.RelatedRecord.Username)"' + echo "" + echo "Re-run with --service-resource to pick one, or assign a ServiceResource to $TECH_USERNAME first." + exit 1 +fi + +echo "Service Resource: $SERVICE_RESOURCE_ID" +``` + +**8-fresh-b. Pick an Account to attach the test WO to.** + +```bash +TEST_ACCOUNT_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT Id, Name FROM Account WHERE IsDeleted = false ORDER BY CreatedDate DESC LIMIT 1" --json | \ + jq -r '.result.records[0].Id') + +echo "Attaching test WO to Account Id: $TEST_ACCOUNT_ID" +``` + +If the org has no Accounts, ask the admin to supply one or create a quick test Account first. + +**8-fresh-c. Create the Work Order with the prompt template Id pre-filled.** + +```bash +TODAY=$(date -u +"%Y-%m-%d") +START_TIME=$(date -u -v+1H +"%Y-%m-%dT%H:00:00.000Z" 2>/dev/null || date -u -d '+1 hour' +"%Y-%m-%dT%H:00:00.000Z") +END_TIME=$(date -u -v+3H +"%Y-%m-%dT%H:00:00.000Z" 2>/dev/null || date -u -d '+3 hours' +"%Y-%m-%dT%H:00:00.000Z") + +WO_ID=$(sf data create record --target-org "$ORG_ALIAS" --sobject WorkOrder \ + --values "Subject='PWB Test (auto-created)' AccountId=$TEST_ACCOUNT_ID Status='New' StartDate=$TODAY EndDate=$TODAY PreWorkBriefPromptTemplate=$TEMPLATE_ID" \ + --json | jq -r '.result.id') + +echo "✓ Work Order: $WO_ID" + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT WorkOrderNumber, Subject, PreWorkBriefPromptTemplate FROM WorkOrder WHERE Id = '$WO_ID'" \ + --json | jq -r '.result.records[0]' +``` + +If the create fails on `PreWorkBriefPromptTemplate` not being writable, assign the `PreWorkBrief_Field_Access` permission set (deployed in Step 7d) to the admin running this skill, then retry. + +**8-fresh-d. Create the Service Appointment scheduled for today.** + +The Service Appointment needs a Service Territory in most orgs (the field is technically nillable but populated territories are the norm in real configurations and several orgs require one). Resolve the territory the chosen Service Resource is a member of and pass it on create. + +```bash +SERVICE_TERRITORY_ID=$(sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT ServiceTerritoryId FROM ServiceTerritoryMember WHERE ServiceResourceId = '$SERVICE_RESOURCE_ID' AND EffectiveEndDate = null LIMIT 1" --json | \ + jq -r '.result.records[0].ServiceTerritoryId // empty') + +echo "Service Territory: ${SERVICE_TERRITORY_ID:-}" + +SA_VALUES="ParentRecordId=$WO_ID Subject='PWB Test (auto-created)' SchedStartTime=$START_TIME SchedEndTime=$END_TIME EarliestStartTime=$START_TIME DueDate=$END_TIME Status=Scheduled" +[ -n "$SERVICE_TERRITORY_ID" ] && SA_VALUES="$SA_VALUES ServiceTerritoryId=$SERVICE_TERRITORY_ID" + +SA_ID=$(sf data create record --target-org "$ORG_ALIAS" --sobject ServiceAppointment \ + --values "$SA_VALUES" --json | jq -r '.result.id') + +echo "✓ Service Appointment: $SA_ID" +``` + +If the create still fails with a Service Territory error and the resource has no `ServiceTerritoryMember`, ask the admin to either pick a different resource or add the resource to a territory in Setup → Service Territories. + +**8-fresh-e. Assign the Service Appointment to the Service Resource.** + +```bash +AR_ID=$(sf data create record --target-org "$ORG_ALIAS" --sobject AssignedResource \ + --values "ServiceAppointmentId=$SA_ID ServiceResourceId=$SERVICE_RESOURCE_ID" \ + --json | jq -r '.result.id') + +echo "✓ AssignedResource: $AR_ID" +``` + +**8-fresh-f. Verify the chain end-to-end.** + +```bash +echo "" +echo "Test artifact summary:" +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT WorkOrderNumber, Subject, Account.Name, PreWorkBriefPromptTemplate, StartDate FROM WorkOrder WHERE Id = '$WO_ID'" \ + --json | jq -r '.result.records[0]' + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT AppointmentNumber, SchedStartTime, SchedEndTime, Status FROM ServiceAppointment WHERE Id = '$SA_ID'" \ + --json | jq -r '.result.records[0]' + +sf data query --target-org "$ORG_ALIAS" \ + --query "SELECT ServiceResource.Name, ServiceResource.RelatedRecord.Username FROM AssignedResource WHERE Id = '$AR_ID'" \ + --json | jq -r '.result.records[0]' +``` + +The admin can now sign in as `$TECH_USERNAME` on the Field Service mobile app and find the auto-created Work Order on today's schedule. Skip ahead to Step 9 for on-device verification. + +### Step 9: Verify on a mobile device + +Open the Field Service mobile app on iOS or Android. Sign in as a user who holds the **Einstein Field Service User** and **Prompt Template User** permission sets, plus the Field Service Mobile license. Connect the device to the internet. + +1. Open a Work Order whose **Pre-Work Brief Prompt Template ID** field is set. +2. Confirm the Pre-Work Brief renders in the **Overview tab** of the Work Order. +3. Confirm the brief content references the actual Work Order, not generic boilerplate. +4. After 24 hours, re-open the same Work Order and confirm the brief regenerates. + +If the brief is missing or empty, see **Common Issues** below. + +--- + +## Optional: Modify the brief + +The default flow and prompt are a starting point. You can clone them and customize: + +1. In Setup, Quick Find **Process Automation → Flows**. +2. Open **Field Service Mobile: Generate Pre-Work Brief**. +3. Click **Save As** to clone the flow. +4. Edit the cloned flow: + - Modify the **Serialize Pre-Work Brief Records** action to include additional objects. + - Add fields to the **Get Contacts** element. + - Add elements for custom fields used in your org. +5. To modify the prompt itself, open the **Add Pre-Work Brief Prompt Instructions** element. Add or remove instructions. Examples: + - "Make sure the brief is no longer than 200 words." + - "Present the brief in bullet points." +6. Create a new prompt template (step 5 again) that points at the cloned flow. +7. Update the Work Order field to use the new prompt template ID. + +For different scenarios — for example, an emergency-appointment brief versus a routine-appointment brief — create multiple flow + prompt template pairs and set the Work Order field per record. + +> Avoid telling the prompt to always include a specific field. If the value is missing, generative AI can hallucinate inaccurate results. +> +> Add examples of what you expect the brief to look like rather than rigid rules. + +--- + +## Configuration Reference + +| Concern | Where it lives | How to change | +|---|---|---| +| Who can see Pre-Work Brief | `Einstein Field Service User` + `Prompt Template User` permission sets, plus the Field Service Mobile license | Setup → Permission Sets → Manage Assignments (step 4) | +| Who can edit prompts | `Prompt Template Manager` permission set | Setup → Permission Sets → Manage Assignments (step 4) | +| What the brief says | The `Field Service Pre-Work Brief` prompt template, plus the linked Flow | Prompt Builder + Flow Builder (steps 5, 6, optional Modify) | +| Which prompt template runs for a given Work Order | `Pre-Work Brief Prompt Template ID` field on Work Order | Manual edit, CLI update, or a Flow that sets it (step 8) | +| Which LLM generates the brief | Salesforce-managed | Not customer-configurable | +| Where the brief renders | **Overview tab of the Work Order** in the FS Mobile app | Not configurable | +| When the brief regenerates | First view online, then every 24 hours | Not configurable | + +--- + +## Common Issues + +**The brief is missing entirely on a Work Order.** + +Check in this order: + +1. The Work Order's **Pre-Work Brief Prompt Template ID** field is set to a valid prompt template ID. +2. The signed-in user holds **Einstein Field Service User** and **Prompt Template User** permission sets. +3. Lightning Data Service is enabled for Field Service Mobile. +4. The mobile device was online when the Work Order was opened. The brief is generated only on first online view; offline opens do not generate it. + +**The brief renders but is generic and doesn't reference the actual job.** + +The Flow grounding is failing. Walk the field list in `mfs_einstein_pre_work_brief_data.htm`: + +- Some fields the Flow expects don't exist in your org. Edit the cloned Flow to remove them. +- Field-level security is hiding fields from the running user. Set the relevant fields to **Visible** for the user's profile. + +**The prompt template fails to activate.** + +The Resource lookup couldn't find **Field Service Mobile: Generate Pre-Work Brief**. Confirm the managed Field Service flow templates are deployed in the org. If they are missing, the org may not have the Einstein for Field Service add-on fully provisioned — return to step 1. + +**The brief shows on desktop but not on mobile.** + +Almost always a Lightning Data Service issue. Confirm step 2 was completed. Then confirm the mobile user has the LDS permission set assigned per `mfs_lightning_data_service.htm`. + +**The brief never regenerates after I change the Flow.** + +Pre-Work Brief regenerates only after 24 hours, or when the Work Order's prompt template ID changes. To force regeneration during testing, point the Work Order at a different (or newly created) prompt template. + +--- + +## Verification Checklist + +Run before declaring Pre-Work Brief enabled in production: + +- [ ] `sf org display --target-org "$ORG_ALIAS"` returns the expected org. +- [ ] At least one Einstein for Field Service PSL is present with `TotalLicenses > 0`. +- [ ] Lightning Data Service for Field Service Mobile is enabled. +- [ ] Einstein generative AI base setup is complete, including Data 360. +- [ ] `EinsteinFieldServiceUser`, `EinsteinGPTPromptTemplateManager`, and `EinsteinGPTPromptTemplateUser` permission sets exist in the org and are assigned to the appropriate users. +- [ ] The managed flow **Field Service Mobile: Generate Pre-Work Brief** is present in the org (visible via `sf org list metadata --metadata-type Flow`). +- [ ] A `Field Service Pre-Work Brief` prompt template exists, points at that flow, and is activated. +- [ ] The Work Order field `PreWorkBriefPromptTemplate` (label "Pre-Work Brief Prompt Template ID") is on the layouts assigned to mobile workers and admins, and is set to a valid prompt template ID on at least one test Work Order. +- [ ] On-device test: a real mobile worker opens that Work Order online and the brief renders in the Overview tab with job-specific content. + +--- + +## Conventions + +- **Idempotent.** Re-running the full sequence on an already-configured org applies zero changes. Each step's check passes silently if the change is already in place. +- **Production safety.** Prompt for explicit confirmation before running any `sf data update record` against a production org. +- **Read API names back from the org.** PSL `DeveloperName` and the prompt template ID are read from the target org rather than hard-coded. +- **Single source of truth.** Where Salesforce Help documents a setup step, this skill cites the article rather than reproducing or contradicting it. +- **No external dependencies.** The skill uses the Salesforce CLI, POSIX shell, and `jq`. It runs unchanged on any machine with `sf` installed. + +--- + +## Related afv-library Skills + +This skill composes with several existing afv-library skills: + +- `generating-flow` — recommended for step 8's "Flow that auto-fills the prompt template ID" pattern. Also useful when cloning and customizing the **Field Service Mobile: Generate Pre-Work Brief** flow in the optional Modify section. +- `generating-permission-set` — useful if you want to bundle the three Einstein for Field Service permission sets into a single org-specific permission set for assignment. +- `developing-agentforce` — for related Agentforce features in Field Service (Service Agent, Customer-Initiated Scheduling). Pre-Work Brief itself is not built as an Agentforce agent; it is a prompt template + flow, no agent required. + +--- + +## References + +External (Salesforce Help): + +- Pre-Work Brief setup: `https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief.htm` +- Pre-Work Brief data and grounding fields: `https://help.salesforce.com/s/articleView?id=service.mfs_einstein_pre_work_brief_data.htm` +- Set Up Permissions for Einstein for Field Service Mobile: `https://help.salesforce.com/s/articleView?id=service.fs_einstein_gen_ai_setup.htm` +- Lightning Data Service for Field Service Mobile: `https://help.salesforce.com/s/articleView?id=service.mfs_lightning_data_service.htm` +- Set Up Einstein Generative AI: `https://help.salesforce.com/s/articleView?id=ai.generative_ai_enable.htm` +- Prompt Builder: `https://help.salesforce.com/s/articleView?id=ai.prompt_builder_about.htm` +- Agentforce and Einstein for Field Service feature catalog: `https://help.salesforce.com/s/articleView?id=service.fs_einstein_setup_parent.htm` + +--- + +## Known Limitations + +- The brief regenerates only after 24 hours. There is no customer-facing setting to shorten this interval. +- Mobile workers must be online for the first view of a Work Order to generate the brief. +- Pre-Work Brief is a one-shot summary, not a conversation. There is no built-in handoff to a conversational agent today. +- Pre-Work Brief targets the Work Order Overview tab. It does not render on the Service Appointment record page. +- Mobile Extension Toolkit (MET) is not supported alongside Lightning Data Service. If your org uses MET extensions, plan for migration before enabling Pre-Work Brief.