@sellable/install 0.1.776 → 0.1.778
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/container/Dockerfile +7 -7
- package/container/README.md +1 -1
- package/lib/sellable-agent/default-profile-bundles/shared/manifest.fragment.json +1 -1
- package/lib/sellable-agent/default-profile-bundles/shared/skills/sellable/sellable-campaign-daily-review/SKILL.md +9 -6
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-admin-image/Dockerfile +1 -1
- package/lib/sellable-agent/fly-admin-image/admin-runtime.mjs +2 -2
- package/lib/sellable-agent/fly-customer-image/Dockerfile +4 -4
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +11 -2
- package/lib/sellable-agent/hermes-memory-snapshot.mjs +14 -13
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +2 -2
- package/lib/sellable-agent/profile-materializer.mjs +2 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +2 -2
- package/lib/sellable-agent/public-skills.json +1 -1
- package/package.json +1 -1
- package/skill-templates/campaign-daily-review.md +9 -6
package/container/Dockerfile
CHANGED
|
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
7
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.778
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.947
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
|
-
ARG MCP_INTEGRITY=sha512-
|
|
9
|
+
ARG MCP_INTEGRITY=sha512-3/IoWRHWyTjdh7OjYHdYS+Na6v9TkYhGXKSYX4rYjhqm4b7MtyV2HaRlZVYKUv8afS1UzZcCUXUx7kqF/Mzk4A==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
12
|
-
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.778" \
|
|
12
|
+
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.947" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
15
15
|
&& test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.776" \
|
|
|
23
23
|
&& mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
|
|
24
24
|
&& npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
|
|
25
25
|
&& npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
26
|
-
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.
|
|
26
|
+
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.778' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.947') throw new Error('package identity rejected');" \
|
|
27
27
|
&& node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
|
|
28
|
-
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.
|
|
28
|
+
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.778', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.947', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
|
|
29
29
|
&& npm cache clean --force
|
|
30
30
|
|
|
31
31
|
COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
|
package/container/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
|
|
4
4
|
|
|
5
5
|
Build from this directory after
|
|
6
|
-
`@sellable/install@0.1.
|
|
6
|
+
`@sellable/install@0.1.778` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"ownership": "system_managed",
|
|
30
30
|
"path": "skills/sellable/sellable-campaign-daily-review/SKILL.md",
|
|
31
31
|
"mode": "0444",
|
|
32
|
-
"content": "---\nname: sellable-campaign-daily-review\ndescription: Review existing Sellable campaigns and move exactly one useful next step forward.\nvisibility: public\nallowed-tools:\n - mcp_sellable_get_auth_status\n - mcp_sellable_customer_program\n - mcp_sellable_get_campaigns\n - mcp_sellable_get_campaign\n - mcp_sellable_get_campaign_context\n - mcp_sellable_get_campaign_refill_state\n - mcp_sellable_refill_sends\n---\n\n# Sellable Campaign Daily Review\n\n## Installed Host Contract\n\nThis installed skill is running in Hermes Agent. When the shared workflow body\nor fallback text mentions Claude Code, Codex, or Hermes for internal parity,\nchoose the Hermes instruction for customer-facing language and host functions.\n\n- Customer-facing command: `/sellable-campaign-daily-review`\n- MCP tool naming: Hermes exposes Sellable tools as `mcp_sellable_<tool>`;\n when shared instructions show `mcp_sellable_<tool>`, call the matching\n `mcp_sellable_<tool>` tool instead.\n- Structured questions: ask plainly in chat unless a Hermes-native approval or\n question tool is visible in the current session.\n- Bootstrap host label: `host: \"Hermes\"`\n- Install/reload blocker label: Hermes install/reload problem\n- Reload instruction: restart Hermes, or run `/reload-mcp` in the active\n Hermes session after install\n\nDo not tell Hermes users to run Codex or Claude command forms, use Codex/Claude structured-question APIs, or restart Codex Desktop or Claude Code. Do not describe this run as Claude Code or Codex.\n\nHelp the customer keep the campaign they already have healthy and moving. This\nis one continuous workflow: understand current workspace state, choose one\nuseful next step, use the existing campaign owner, observe the result, and save\nthe exact wait or outcome.\n\n## Run the review\n\n1. Call `get_auth_status`, then obtain a fenced `customer_program` review claim\n and load the complete home-workspace campaign inventory. For a scheduled\n `execute` or `resume` wake, use the scheduled trigger and the wake gate's\n exact lease owner. For a direct customer request, use the interactive\n trigger. For a `reconcile` wake, skip campaign work and follow the delivery\n recovery below. Keep each strict tool input separate: claim with only\n `action`, `trigger`, and `leaseOwner`; never add the wake's `effectId` or\n `fence` to the claim call.\n2. Infer the relationship stage from that workspace state. Keep campaign health\n and wait state separate from the relationship stage.\n3. Assess each campaign from approved ICP, source, message and actual outcome\n evidence. `ACTIVE` or a full queue does not prove suitability. Sparse results\n do not prove poor quality. Unavailable data is not an empty workspace.\n4. Inspect supply and capacity, then choose exactly one outcome: `maintain`,\n `refill`, `continue`, `edit`, `propose_create`, `input`, or `blocker`.\n5. Execute through the existing owner:\n - suitable campaign with a real supply gap: call canonical Refill Sends with `yolo:true` and keep its returned run token. Keep going until the gap is filled or a real blocker remains; when a source runs dry, refill widens it. Do not stop to ask the customer for leads;\n - unfinished campaign: continue the same campaign through Create Campaign;\n - supported quality issue: edit the same campaign through Create Campaign;\n - no campaign: enter Create Campaign, asking only for the LinkedIn profile\n first when it is missing;\n - healthy supplied campaign: maintain it and stay quiet;\n - manual pause: respect it;\n - missing approval, connection or decision: with `launchConsent.mode: \"autopilot\"` approve messages that passed the quality check and start or resume the campaign yourself
|
|
32
|
+
"content": "---\nname: sellable-campaign-daily-review\ndescription: Review existing Sellable campaigns and move exactly one useful next step forward.\nvisibility: public\nallowed-tools:\n - mcp_sellable_get_auth_status\n - mcp_sellable_customer_program\n - mcp_sellable_get_campaigns\n - mcp_sellable_get_campaign\n - mcp_sellable_get_campaign_context\n - mcp_sellable_get_campaign_refill_state\n - mcp_sellable_refill_sends\n---\n\n# Sellable Campaign Daily Review\n\n## Installed Host Contract\n\nThis installed skill is running in Hermes Agent. When the shared workflow body\nor fallback text mentions Claude Code, Codex, or Hermes for internal parity,\nchoose the Hermes instruction for customer-facing language and host functions.\n\n- Customer-facing command: `/sellable-campaign-daily-review`\n- MCP tool naming: Hermes exposes Sellable tools as `mcp_sellable_<tool>`;\n when shared instructions show `mcp_sellable_<tool>`, call the matching\n `mcp_sellable_<tool>` tool instead.\n- Structured questions: ask plainly in chat unless a Hermes-native approval or\n question tool is visible in the current session.\n- Bootstrap host label: `host: \"Hermes\"`\n- Install/reload blocker label: Hermes install/reload problem\n- Reload instruction: restart Hermes, or run `/reload-mcp` in the active\n Hermes session after install\n\nDo not tell Hermes users to run Codex or Claude command forms, use Codex/Claude structured-question APIs, or restart Codex Desktop or Claude Code. Do not describe this run as Claude Code or Codex.\n\nHelp the customer keep the campaign they already have healthy and moving. This\nis one continuous workflow: understand current workspace state, choose one\nuseful next step, use the existing campaign owner, observe the result, and save\nthe exact wait or outcome.\n\n## Run the review\n\n1. Call `get_auth_status`, then obtain a fenced `customer_program` review claim\n and load the complete home-workspace campaign inventory. For a scheduled\n `execute` or `resume` wake, use the scheduled trigger and the wake gate's\n exact lease owner. For a direct customer request, use the interactive\n trigger. For a `reconcile` wake, skip campaign work and follow the delivery\n recovery below. Keep each strict tool input separate: claim with only\n `action`, `trigger`, and `leaseOwner`; never add the wake's `effectId` or\n `fence` to the claim call.\n2. Infer the relationship stage from that workspace state. Keep campaign health\n and wait state separate from the relationship stage.\n3. Assess each campaign from approved ICP, source, message and actual outcome\n evidence. `ACTIVE` or a full queue does not prove suitability. Sparse results\n do not prove poor quality. Unavailable data is not an empty workspace.\n4. Inspect supply and capacity, then choose exactly one outcome: `maintain`,\n `refill`, `continue`, `edit`, `propose_create`, `input`, or `blocker`.\n5. Execute through the existing owner:\n - suitable campaign with a real supply gap: call canonical Refill Sends with `yolo:true` and keep its returned run token. Keep going until the gap is filled or a real blocker remains; when a source runs dry, refill widens it. Do not stop to ask the customer for leads;\n - unfinished campaign: continue the same campaign through Create Campaign;\n - supported quality issue: edit the same campaign through Create Campaign;\n - no campaign: enter Create Campaign, asking only for the LinkedIn profile\n first when it is missing;\n - healthy supplied campaign: maintain it and stay quiet;\n - manual pause: respect it;\n - missing approval, connection or decision: with `launchConsent.mode: \"autopilot\"` approve messages that passed the quality check and start or resume the campaign yourself. When `launchConsent` has no answer yet, ask the one-time question instead of asking for approval: \"I can approve messages that pass my quality check and launch for you, then keep your sends filled. Reply review first if you would rather approve.\" Save the answer with `save_context` as Create Campaign's Standing Consent does. With `review_first`, retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.\n A built-in weekday refill job fills sends before this review; do not run\n Refill Sends again unless `refill` is this review's one outcome. Read\n `refill_v3_world_state` for the next two sender-local days: a row whose\n reason names a disconnected sender (`sender_disconnected`), a disconnected\n Sales Navigator (`sales_nav_disconnected`) or\n `paid_inmail_connection_health_recheck_required` is a fix only the customer\n can make. Make it this review's `input`: name the sender and say reconnect\n LinkedIn or reconnect Sales Navigator in Settings.\n6. Observe the owned operation, then record the outcome against the same review\n effect and fence. The `record_review_outcome` call contains only its action,\n effect, fence, review outcome, summary, and optional decision/operation refs;\n do not include trigger or lease fields. Never mark a pending or uncertain\n effect complete. On a scheduled actionable outcome, saving the outcome is\n not completion. Inspect `deliveryRequired`; when it is `true`, continue\n immediately to the required `stage_delivery` call. Never return while the recorded run still has `deliveryState: \"pending\"`. Write the outcome `summary` in plain customer words with the verified numbers (leads added, messages approved, sends scheduled): the next results update reads it as \"What Sellable did\".\n\n## Write the Slack message\n\nPost only new work or a real need, never a status. Use real line breaks: a bold title, a blank line between sections, one item per line, never one long paragraph. Write ordinary Markdown; delivery converts it for Slack.\n\n**{Workspace} · Campaign update · {Weekday Mon D}**\n\n**What I did** says in plain words what changed: leads added, messages approved,\nsends scheduled, campaign started, or a source widened.\n\n**Next step** and **Options** appear only when the customer must act: one specific\naction, then at most two options they can reply with. Offer only what you can really\ndo (refill, continue or edit the campaign) or what they can do (add leads, approve\nmessages, reconnect a sender).\n\nLead with what Sellable did to grow their sends. Never tell the customer what they\nhave not done, and never post a scoreboard of zeros. Plain words: no run tokens,\njob or effect IDs, error codes, tool names or file paths. Unknown is never zero.\nAbout 20-60 words.\n\n## Stop and delivery rules\n\nAn internal system fault (a run token, an error code, refill machinery) is not\na customer decision or blocker: do not post it.\n\nIf a tool call fails or its result can't be read, return `[SILENT]` and stop.\nNever post an error, an apology or a guess; the open claim lets the next\nscheduled window retry.\n\nStop on settled state, a human wait, a retained async wait, a typed blocker or\nthe existing bounded operation budget. Never restart a pending effect. Follow a\nnew customer reply or product completion immediately instead of waiting for the\nnext daily tick.\n\nPost a useful new decision or blocker once to the verified current main channel.\nRoutine refill belongs in the next results brief. A healthy no-op is inspectable\nand silent. On a scheduled actionable outcome, compose the complete final Slack\ntext, call `customer_program` with only `action: \"stage_delivery\"`, the exact\neffect and fence, and `text`, then inspect `delivery.deliverNow`. Only when it is `true`, return\n`delivery.text` verbatim as the native job's final response. When it is\n`false`, return `[SILENT]`; that effect is already uncertain or delivered,\nand the managed reconciliation tick owns the next step. Do not stage an interactive response.\n\nBefore any scheduled final response, check the recorded run one last time. A\n`pending` delivery is an unfinished cron turn: call `stage_delivery` now. Only\n`accepted_uncertain`, `delivered`, `failed`, or `not_applicable` may end the\nturn.\n\nOn a scheduled `reconcile` wake, call `reconcile_delivery` with only its action\nand the exact effect.\nIf trusted Slack history confirms the text, or retry is not yet allowed, return\n`[SILENT]`. If retry is allowed, call `retry_delivery` with the current fence and\nreturn its stored `delivery.text` verbatim. Never regenerate it. The native\nmanaged job remains the only Slack sender; the product owns the claim and\ndelivery ledger. After three unconfirmed attempts the product closes the run as\nfailed and later ticks stay silent. Never create another post path or fall back\nto another channel.\n\nDo not create a second campaign engine, refill algorithm, scheduler, scoring\nframework, intake questionnaire or generic morning approval. Do not rewrite\nstrategy or messages merely because another day passed. Initial campaign launch\nstill requires the existing human Start.\n"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"id": "campaign-daily-results",
|
|
@@ -63,12 +63,15 @@ the exact wait or outcome.
|
|
|
63
63
|
first when it is missing;
|
|
64
64
|
- healthy supplied campaign: maintain it and stay quiet;
|
|
65
65
|
- manual pause: respect it;
|
|
66
|
-
- missing approval, connection or decision: with `launchConsent.mode: "autopilot"` approve messages that passed the quality check and start or resume the campaign yourself
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
- missing approval, connection or decision: with `launchConsent.mode: "autopilot"` approve messages that passed the quality check and start or resume the campaign yourself. When `launchConsent` has no answer yet, ask the one-time question instead of asking for approval: "I can approve messages that pass my quality check and launch for you, then keep your sends filled. Reply review first if you would rather approve." Save the answer with `save_context` as Create Campaign's Standing Consent does. With `review_first`, retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.
|
|
67
|
+
A built-in weekday refill job fills sends before this review; do not run
|
|
68
|
+
Refill Sends again unless `refill` is this review's one outcome. Read
|
|
69
|
+
`refill_v3_world_state` for the next two sender-local days: a row whose
|
|
70
|
+
reason names a disconnected sender (`sender_disconnected`), a disconnected
|
|
71
|
+
Sales Navigator (`sales_nav_disconnected`) or
|
|
72
|
+
`paid_inmail_connection_health_recheck_required` is a fix only the customer
|
|
73
|
+
can make. Make it this review's `input`: name the sender and say reconnect
|
|
74
|
+
LinkedIn or reconnect Sales Navigator in Settings.
|
|
72
75
|
6. Observe the owned operation, then record the outcome against the same review
|
|
73
76
|
effect and fence. The `record_review_outcome` call contains only its action,
|
|
74
77
|
effect, fence, review outcome, summary, and optional decision/operation refs;
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
|
|
30
30
|
const MCP_PACKAGE = "@sellable/mcp";
|
|
31
|
-
const MCP_VERSION = "0.1.
|
|
31
|
+
const MCP_VERSION = "0.1.947";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -44,7 +44,7 @@ ARG STRIPE_CLI_PACKAGE=@stripe/cli-linux-x64@1.45.0
|
|
|
44
44
|
ARG STRIPE_CLI_INTEGRITY=sha512-1ZhoPpoweYfynqsvhCLlTjZ7lPz5IKtxqAMT5MBy6zrwLRTP9x0T6r8h/jLYL8lRH1c7zC/6X6/XUu2V+r9Bog==
|
|
45
45
|
ARG DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION=1
|
|
46
46
|
ARG DEFAULT_PROFILE_BUNDLE_VERSION=4
|
|
47
|
-
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=
|
|
47
|
+
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=e24677e470c289ac5b27d279693deca5fba10b8ff1d02b218bf523cc39bc1a13
|
|
48
48
|
|
|
49
49
|
COPY --from=slack-pp-cli-builder --chmod=0555 /out/slack-pp-cli /usr/local/bin/slack-pp-cli
|
|
50
50
|
|
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
HERMES_SNAPSHOT_HOOK_ALLOWLIST_BYTES,
|
|
89
89
|
applyHermesSellableHostedPolicy,
|
|
90
90
|
ensureHermesClientRelationshipsCrons,
|
|
91
|
-
|
|
91
|
+
ensureHermesRefillSendsCron,
|
|
92
92
|
ensureHermesCustomerProgramCrons,
|
|
93
93
|
ensureHermesOpportunitySequencesCron,
|
|
94
94
|
ensureHermesSnapshotCron,
|
|
@@ -1442,7 +1442,7 @@ async function materializeAdminProfile({
|
|
|
1442
1442
|
// NON-FATAL like its siblings: a missed refill tick is harmless (refill
|
|
1443
1443
|
// no-ops when queues are full) and must not block an Admin promotion.
|
|
1444
1444
|
const adminRefillCron = slack
|
|
1445
|
-
? await
|
|
1445
|
+
? await ensureHermesRefillSendsCron({
|
|
1446
1446
|
profileId: profile,
|
|
1447
1447
|
profileRoot: profileHome,
|
|
1448
1448
|
hermesRoot: DATA_ROOT,
|
|
@@ -6,9 +6,9 @@ USER root
|
|
|
6
6
|
|
|
7
7
|
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
|
|
8
8
|
|
|
9
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
10
|
-
ARG MCP_INTEGRITY=sha512-
|
|
11
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
9
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.947
|
|
10
|
+
ARG MCP_INTEGRITY=sha512-3/IoWRHWyTjdh7OjYHdYS+Na6v9TkYhGXKSYX4rYjhqm4b7MtyV2HaRlZVYKUv8afS1UzZcCUXUx7kqF/Mzk4A==
|
|
11
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.778
|
|
12
12
|
ARG ADMIN_MCP_PACKAGE=@sellable/admin-mcp@0.1.202
|
|
13
13
|
ARG ADMIN_MCP_INTEGRITY=sha512-ym3aye/0vQ8tfULJlKz9JFOxvIQsl1G+n3np+TTgK/tecwNblBo/j3AkTnRqH9iomKzPbZzP+n4tEQsvsU8ByA==
|
|
14
14
|
ARG TUS_JS_CLIENT_VERSION=4.3.1
|
|
@@ -22,7 +22,7 @@ ARG PLAYWRIGHT_BROWSER_REVISION=1243
|
|
|
22
22
|
ARG TIRITH_ARCHIVE_SHA256=6cdbe35e8f9ccf42e70ad95b501c93cd218ac18201c3df958d54f6ba0d995ce2
|
|
23
23
|
ARG DEFAULT_PROFILE_BUNDLE_FIXTURE_VERSION=1
|
|
24
24
|
ARG DEFAULT_PROFILE_BUNDLE_VERSION=4
|
|
25
|
-
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=
|
|
25
|
+
ARG DEFAULT_PROFILE_BUNDLE_DIGEST=bd97b34dc228ee1d91deaafc52283f555e34165786abba26bad398202c6e087c
|
|
26
26
|
|
|
27
27
|
RUN --mount=type=secret,id=mcp_trust_root,required=true \
|
|
28
28
|
test -s /run/secrets/mcp_trust_root \
|
|
@@ -54,6 +54,7 @@ import { ensureHermesFirstWelcome } from "../hermes-first-welcome.mjs";
|
|
|
54
54
|
import {
|
|
55
55
|
ensureHermesCustomerProgramCrons,
|
|
56
56
|
ensureHermesFirstCampaignOnboardingCron,
|
|
57
|
+
ensureHermesRefillSendsCron,
|
|
57
58
|
ensureHermesSnapshotCron,
|
|
58
59
|
markHermesSnapshotDirty,
|
|
59
60
|
migrateLegacyHermesUserState,
|
|
@@ -122,9 +123,9 @@ const RUNTIME_GENERATION = Number(
|
|
|
122
123
|
const BOOT_SESSION_ID = randomUUID();
|
|
123
124
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.21.5";
|
|
124
125
|
const INSTALLER_PACKAGE =
|
|
125
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
126
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.778";
|
|
126
127
|
const MCP_PACKAGE =
|
|
127
|
-
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.
|
|
128
|
+
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.947";
|
|
128
129
|
export const ENDPOINT_MAX_CONCURRENT_RUNS = 10;
|
|
129
130
|
const DEFAULT_PROFILE_BUNDLE_ROOT =
|
|
130
131
|
"/usr/local/share/sellable-agent/default-profile-bundle";
|
|
@@ -3883,6 +3884,14 @@ async function processOperation({
|
|
|
3883
3884
|
channelId: desired.defaultChannelId,
|
|
3884
3885
|
});
|
|
3885
3886
|
if (!onboardingCron.ok) throw new Error(onboardingCron.code);
|
|
3887
|
+
// NON-FATAL: a missed refill tick is harmless (refill no-ops when queues
|
|
3888
|
+
// are full and the next weekday run continues), so it never blocks serving.
|
|
3889
|
+
await ensureHermesRefillSendsCron({
|
|
3890
|
+
profileId: desired.profileId,
|
|
3891
|
+
profileRoot,
|
|
3892
|
+
hermesRoot: DATA_ROOT,
|
|
3893
|
+
workspaceId: desired.workspaceId,
|
|
3894
|
+
});
|
|
3886
3895
|
});
|
|
3887
3896
|
markHermesSnapshotDirty(profileRoot);
|
|
3888
3897
|
const onboardingMemorySnapshot = await phaseTelemetry.run(
|
|
@@ -134,11 +134,12 @@ export const HERMES_CUSTOMER_PROGRAM_CRONS = Object.freeze([
|
|
|
134
134
|
]),
|
|
135
135
|
}),
|
|
136
136
|
]);
|
|
137
|
-
// Built-in
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
137
|
+
// Built-in weekday refill on every agent (proven on the Admin image first). It
|
|
138
|
+
// posts nothing (deliver local, reply [SILENT]); the daily review names anything
|
|
139
|
+
// only the customer can fix and the results update reports what was added.
|
|
140
|
+
// Refill observes capacity itself and does nothing when queues are full, so a
|
|
141
|
+
// single weekday tick is enough and a missed one is harmless.
|
|
142
|
+
export const HERMES_REFILL_SENDS_CRON = Object.freeze({
|
|
142
143
|
name: "sellable-default-refill-sends",
|
|
143
144
|
schedule: "0 14 * * 1-5",
|
|
144
145
|
deliver: "local",
|
|
@@ -1952,17 +1953,17 @@ function opportunitySequenceIdentity(profileRoot) {
|
|
|
1952
1953
|
);
|
|
1953
1954
|
}
|
|
1954
1955
|
|
|
1955
|
-
function
|
|
1956
|
+
function refillSendsPrompt(workspaceId) {
|
|
1956
1957
|
return [
|
|
1957
1958
|
"Run the installed refill-sends skill with --yolo for this workspace.",
|
|
1958
1959
|
"Call refill_v3_advance with yolo true and scope {kind: sender_local_horizon, days: 2}, then continue exactly as the skill says until the run is complete, awaiting an external change, or blocked.",
|
|
1959
1960
|
"Budget: make at most five refill_v3_advance calls, then stop even if the run is not complete; the next weekday run continues.",
|
|
1960
|
-
"Post nothing: reply [SILENT] whatever the outcome. The next
|
|
1961
|
+
"Post nothing: reply [SILENT] whatever the outcome. The daily review names anything only the customer can fix, and the next results update reports what was added.",
|
|
1961
1962
|
`workspaceId: ${workspaceId}`,
|
|
1962
1963
|
].join("\n");
|
|
1963
1964
|
}
|
|
1964
1965
|
|
|
1965
|
-
export async function
|
|
1966
|
+
export async function ensureHermesRefillSendsCron({
|
|
1966
1967
|
profileId,
|
|
1967
1968
|
profileRoot,
|
|
1968
1969
|
hermesRoot,
|
|
@@ -1975,11 +1976,11 @@ export async function ensureHermesAdminRefillSendsCron({
|
|
|
1975
1976
|
!existsSync(profileRoot) ||
|
|
1976
1977
|
!ONBOARDING_WORKSPACE_ID.test(workspaceId ?? "")
|
|
1977
1978
|
) {
|
|
1978
|
-
return failed("
|
|
1979
|
+
return failed("hermes_refill_sends_cron_identity_rejected");
|
|
1979
1980
|
}
|
|
1980
1981
|
try {
|
|
1981
|
-
const cron =
|
|
1982
|
-
const prompt =
|
|
1982
|
+
const cron = HERMES_REFILL_SENDS_CRON;
|
|
1983
|
+
const prompt = refillSendsPrompt(workspaceId);
|
|
1983
1984
|
const expected = (job) =>
|
|
1984
1985
|
Boolean(
|
|
1985
1986
|
job?.id &&
|
|
@@ -2028,7 +2029,7 @@ export async function ensureHermesAdminRefillSendsCron({
|
|
|
2028
2029
|
isExpected: expected,
|
|
2029
2030
|
create: () => mutate(),
|
|
2030
2031
|
update: (job) => mutate(job.id),
|
|
2031
|
-
codePrefix: "
|
|
2032
|
+
codePrefix: "hermes_refill_sends_cron",
|
|
2032
2033
|
});
|
|
2033
2034
|
if (!result.ok) return result;
|
|
2034
2035
|
return {
|
|
@@ -2043,7 +2044,7 @@ export async function ensureHermesAdminRefillSendsCron({
|
|
|
2043
2044
|
workspaceId,
|
|
2044
2045
|
};
|
|
2045
2046
|
} catch {
|
|
2046
|
-
return failed("
|
|
2047
|
+
return failed("hermes_refill_sends_cron_install_failed");
|
|
2047
2048
|
}
|
|
2048
2049
|
}
|
|
2049
2050
|
|
|
@@ -38,8 +38,8 @@ import { installAgentHostServices } from "./service-installer.mjs";
|
|
|
38
38
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
39
39
|
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
40
|
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
|
-
const INSTALLER_PACKAGE = "@sellable/install@0.1.
|
|
42
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.778";
|
|
42
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.947";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
45
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -1546,8 +1546,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1546
1546
|
]) ||
|
|
1547
1547
|
pinned.hermesCli !== "hermes" ||
|
|
1548
1548
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1549
|
-
pinned.installerPackage !== "@sellable/install@0.1.
|
|
1550
|
-
pinned.mcpPackage !== "@sellable/mcp@0.1.
|
|
1549
|
+
pinned.installerPackage !== "@sellable/install@0.1.778" ||
|
|
1550
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.947" ||
|
|
1551
1551
|
!Array.isArray(policy.toolInclude) ||
|
|
1552
1552
|
policy.toolInclude.length === 0 ||
|
|
1553
1553
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -312,8 +312,8 @@ function validateDesired(desired) {
|
|
|
312
312
|
desired.serviceCredentialGeneration < 1 ||
|
|
313
313
|
desired.hermesCli !== "hermes" ||
|
|
314
314
|
desired.hermesVersion !== "0.18.0" ||
|
|
315
|
-
desired.installerPackage !== "@sellable/install@0.1.
|
|
316
|
-
desired.mcpPackage !== "@sellable/mcp@0.1.
|
|
315
|
+
desired.installerPackage !== "@sellable/install@0.1.778" ||
|
|
316
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.947" ||
|
|
317
317
|
!Array.isArray(desired.toolInclude) ||
|
|
318
318
|
desired.toolInclude.length === 0 ||
|
|
319
319
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
@@ -25,8 +25,8 @@ import { fileURLToPath } from "node:url";
|
|
|
25
25
|
import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
26
26
|
|
|
27
27
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
28
|
-
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.
|
|
29
|
-
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.778";
|
|
29
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.947";
|
|
30
30
|
const PINNED_INSTALL_VERSION = PINNED_INSTALL_PACKAGE.slice(
|
|
31
31
|
PINNED_INSTALL_PACKAGE.lastIndexOf("@") + 1
|
|
32
32
|
);
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
{
|
|
47
47
|
"path": "skills/sellable/sellable-campaign-daily-review/SKILL.md",
|
|
48
|
-
"content": "---\nname: sellable-campaign-daily-review\ndescription: Review existing Sellable campaigns and move exactly one useful next step forward.\nvisibility: public\nallowed-tools:\n - mcp_sellable_get_auth_status\n - mcp_sellable_customer_program\n - mcp_sellable_get_campaigns\n - mcp_sellable_get_campaign\n - mcp_sellable_get_campaign_context\n - mcp_sellable_get_campaign_refill_state\n - mcp_sellable_refill_sends\n---\n\n# Sellable Campaign Daily Review\n\n## Installed Host Contract\n\nThis installed skill is running in Hermes Agent. When the shared workflow body\nor fallback text mentions Claude Code, Codex, or Hermes for internal parity,\nchoose the Hermes instruction for customer-facing language and host functions.\n\n- Customer-facing command: `/sellable-campaign-daily-review`\n- MCP tool naming: Hermes exposes Sellable tools as `mcp_sellable_<tool>`;\n when shared instructions show `mcp_sellable_<tool>`, call the matching\n `mcp_sellable_<tool>` tool instead.\n- Structured questions: ask plainly in chat unless a Hermes-native approval or\n question tool is visible in the current session.\n- Bootstrap host label: `host: \"Hermes\"`\n- Install/reload blocker label: Hermes install/reload problem\n- Reload instruction: restart Hermes, or run `/reload-mcp` in the active\n Hermes session after install\n\nDo not tell Hermes users to run Codex or Claude command forms, use Codex/Claude structured-question APIs, or restart Codex Desktop or Claude Code. Do not describe this run as Claude Code or Codex.\n\nHelp the customer keep the campaign they already have healthy and moving. This\nis one continuous workflow: understand current workspace state, choose one\nuseful next step, use the existing campaign owner, observe the result, and save\nthe exact wait or outcome.\n\n## Run the review\n\n1. Call `get_auth_status`, then obtain a fenced `customer_program` review claim\n and load the complete home-workspace campaign inventory. For a scheduled\n `execute` or `resume` wake, use the scheduled trigger and the wake gate's\n exact lease owner. For a direct customer request, use the interactive\n trigger. For a `reconcile` wake, skip campaign work and follow the delivery\n recovery below. Keep each strict tool input separate: claim with only\n `action`, `trigger`, and `leaseOwner`; never add the wake's `effectId` or\n `fence` to the claim call.\n2. Infer the relationship stage from that workspace state. Keep campaign health\n and wait state separate from the relationship stage.\n3. Assess each campaign from approved ICP, source, message and actual outcome\n evidence. `ACTIVE` or a full queue does not prove suitability. Sparse results\n do not prove poor quality. Unavailable data is not an empty workspace.\n4. Inspect supply and capacity, then choose exactly one outcome: `maintain`,\n `refill`, `continue`, `edit`, `propose_create`, `input`, or `blocker`.\n5. Execute through the existing owner:\n - suitable campaign with a real supply gap: call canonical Refill Sends with `yolo:true` and keep its returned run token. Keep going until the gap is filled or a real blocker remains; when a source runs dry, refill widens it. Do not stop to ask the customer for leads;\n - unfinished campaign: continue the same campaign through Create Campaign;\n - supported quality issue: edit the same campaign through Create Campaign;\n - no campaign: enter Create Campaign, asking only for the LinkedIn profile\n first when it is missing;\n - healthy supplied campaign: maintain it and stay quiet;\n - manual pause: respect it;\n - missing approval, connection or decision: with `launchConsent.mode: \"autopilot\"` approve messages that passed the quality check and start or resume the campaign yourself
|
|
48
|
+
"content": "---\nname: sellable-campaign-daily-review\ndescription: Review existing Sellable campaigns and move exactly one useful next step forward.\nvisibility: public\nallowed-tools:\n - mcp_sellable_get_auth_status\n - mcp_sellable_customer_program\n - mcp_sellable_get_campaigns\n - mcp_sellable_get_campaign\n - mcp_sellable_get_campaign_context\n - mcp_sellable_get_campaign_refill_state\n - mcp_sellable_refill_sends\n---\n\n# Sellable Campaign Daily Review\n\n## Installed Host Contract\n\nThis installed skill is running in Hermes Agent. When the shared workflow body\nor fallback text mentions Claude Code, Codex, or Hermes for internal parity,\nchoose the Hermes instruction for customer-facing language and host functions.\n\n- Customer-facing command: `/sellable-campaign-daily-review`\n- MCP tool naming: Hermes exposes Sellable tools as `mcp_sellable_<tool>`;\n when shared instructions show `mcp_sellable_<tool>`, call the matching\n `mcp_sellable_<tool>` tool instead.\n- Structured questions: ask plainly in chat unless a Hermes-native approval or\n question tool is visible in the current session.\n- Bootstrap host label: `host: \"Hermes\"`\n- Install/reload blocker label: Hermes install/reload problem\n- Reload instruction: restart Hermes, or run `/reload-mcp` in the active\n Hermes session after install\n\nDo not tell Hermes users to run Codex or Claude command forms, use Codex/Claude structured-question APIs, or restart Codex Desktop or Claude Code. Do not describe this run as Claude Code or Codex.\n\nHelp the customer keep the campaign they already have healthy and moving. This\nis one continuous workflow: understand current workspace state, choose one\nuseful next step, use the existing campaign owner, observe the result, and save\nthe exact wait or outcome.\n\n## Run the review\n\n1. Call `get_auth_status`, then obtain a fenced `customer_program` review claim\n and load the complete home-workspace campaign inventory. For a scheduled\n `execute` or `resume` wake, use the scheduled trigger and the wake gate's\n exact lease owner. For a direct customer request, use the interactive\n trigger. For a `reconcile` wake, skip campaign work and follow the delivery\n recovery below. Keep each strict tool input separate: claim with only\n `action`, `trigger`, and `leaseOwner`; never add the wake's `effectId` or\n `fence` to the claim call.\n2. Infer the relationship stage from that workspace state. Keep campaign health\n and wait state separate from the relationship stage.\n3. Assess each campaign from approved ICP, source, message and actual outcome\n evidence. `ACTIVE` or a full queue does not prove suitability. Sparse results\n do not prove poor quality. Unavailable data is not an empty workspace.\n4. Inspect supply and capacity, then choose exactly one outcome: `maintain`,\n `refill`, `continue`, `edit`, `propose_create`, `input`, or `blocker`.\n5. Execute through the existing owner:\n - suitable campaign with a real supply gap: call canonical Refill Sends with `yolo:true` and keep its returned run token. Keep going until the gap is filled or a real blocker remains; when a source runs dry, refill widens it. Do not stop to ask the customer for leads;\n - unfinished campaign: continue the same campaign through Create Campaign;\n - supported quality issue: edit the same campaign through Create Campaign;\n - no campaign: enter Create Campaign, asking only for the LinkedIn profile\n first when it is missing;\n - healthy supplied campaign: maintain it and stay quiet;\n - manual pause: respect it;\n - missing approval, connection or decision: with `launchConsent.mode: \"autopilot\"` approve messages that passed the quality check and start or resume the campaign yourself. When `launchConsent` has no answer yet, ask the one-time question instead of asking for approval: \"I can approve messages that pass my quality check and launch for you, then keep your sends filled. Reply review first if you would rather approve.\" Save the answer with `save_context` as Create Campaign's Standing Consent does. With `review_first`, retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.\n A built-in weekday refill job fills sends before this review; do not run\n Refill Sends again unless `refill` is this review's one outcome. Read\n `refill_v3_world_state` for the next two sender-local days: a row whose\n reason names a disconnected sender (`sender_disconnected`), a disconnected\n Sales Navigator (`sales_nav_disconnected`) or\n `paid_inmail_connection_health_recheck_required` is a fix only the customer\n can make. Make it this review's `input`: name the sender and say reconnect\n LinkedIn or reconnect Sales Navigator in Settings.\n6. Observe the owned operation, then record the outcome against the same review\n effect and fence. The `record_review_outcome` call contains only its action,\n effect, fence, review outcome, summary, and optional decision/operation refs;\n do not include trigger or lease fields. Never mark a pending or uncertain\n effect complete. On a scheduled actionable outcome, saving the outcome is\n not completion. Inspect `deliveryRequired`; when it is `true`, continue\n immediately to the required `stage_delivery` call. Never return while the recorded run still has `deliveryState: \"pending\"`. Write the outcome `summary` in plain customer words with the verified numbers (leads added, messages approved, sends scheduled): the next results update reads it as \"What Sellable did\".\n\n## Write the Slack message\n\nPost only new work or a real need, never a status. Use real line breaks: a bold title, a blank line between sections, one item per line, never one long paragraph. Write ordinary Markdown; delivery converts it for Slack.\n\n**{Workspace} · Campaign update · {Weekday Mon D}**\n\n**What I did** says in plain words what changed: leads added, messages approved,\nsends scheduled, campaign started, or a source widened.\n\n**Next step** and **Options** appear only when the customer must act: one specific\naction, then at most two options they can reply with. Offer only what you can really\ndo (refill, continue or edit the campaign) or what they can do (add leads, approve\nmessages, reconnect a sender).\n\nLead with what Sellable did to grow their sends. Never tell the customer what they\nhave not done, and never post a scoreboard of zeros. Plain words: no run tokens,\njob or effect IDs, error codes, tool names or file paths. Unknown is never zero.\nAbout 20-60 words.\n\n## Stop and delivery rules\n\nAn internal system fault (a run token, an error code, refill machinery) is not\na customer decision or blocker: do not post it.\n\nIf a tool call fails or its result can't be read, return `[SILENT]` and stop.\nNever post an error, an apology or a guess; the open claim lets the next\nscheduled window retry.\n\nStop on settled state, a human wait, a retained async wait, a typed blocker or\nthe existing bounded operation budget. Never restart a pending effect. Follow a\nnew customer reply or product completion immediately instead of waiting for the\nnext daily tick.\n\nPost a useful new decision or blocker once to the verified current main channel.\nRoutine refill belongs in the next results brief. A healthy no-op is inspectable\nand silent. On a scheduled actionable outcome, compose the complete final Slack\ntext, call `customer_program` with only `action: \"stage_delivery\"`, the exact\neffect and fence, and `text`, then inspect `delivery.deliverNow`. Only when it is `true`, return\n`delivery.text` verbatim as the native job's final response. When it is\n`false`, return `[SILENT]`; that effect is already uncertain or delivered,\nand the managed reconciliation tick owns the next step. Do not stage an interactive response.\n\nBefore any scheduled final response, check the recorded run one last time. A\n`pending` delivery is an unfinished cron turn: call `stage_delivery` now. Only\n`accepted_uncertain`, `delivered`, `failed`, or `not_applicable` may end the\nturn.\n\nOn a scheduled `reconcile` wake, call `reconcile_delivery` with only its action\nand the exact effect.\nIf trusted Slack history confirms the text, or retry is not yet allowed, return\n`[SILENT]`. If retry is allowed, call `retry_delivery` with the current fence and\nreturn its stored `delivery.text` verbatim. Never regenerate it. The native\nmanaged job remains the only Slack sender; the product owns the claim and\ndelivery ledger. After three unconfirmed attempts the product closes the run as\nfailed and later ticks stay silent. Never create another post path or fall back\nto another channel.\n\nDo not create a second campaign engine, refill algorithm, scheduler, scoring\nframework, intake questionnaire or generic morning approval. Do not rewrite\nstrategy or messages merely because another day passed. Initial campaign launch\nstill requires the existing human Start.\n"
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
"path": "skills/sellable/sellable-campaign-daily-results/SKILL.md",
|
package/package.json
CHANGED
|
@@ -44,12 +44,15 @@ the exact wait or outcome.
|
|
|
44
44
|
first when it is missing;
|
|
45
45
|
- healthy supplied campaign: maintain it and stay quiet;
|
|
46
46
|
- manual pause: respect it;
|
|
47
|
-
- missing approval, connection or decision: with `launchConsent.mode: "autopilot"` approve messages that passed the quality check and start or resume the campaign yourself
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
- missing approval, connection or decision: with `launchConsent.mode: "autopilot"` approve messages that passed the quality check and start or resume the campaign yourself. When `launchConsent` has no answer yet, ask the one-time question instead of asking for approval: "I can approve messages that pass my quality check and launch for you, then keep your sends filled. Reply review first if you would rather approve." Save the answer with `save_context` as Create Campaign's Standing Consent does. With `review_first`, retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.
|
|
48
|
+
A built-in weekday refill job fills sends before this review; do not run
|
|
49
|
+
Refill Sends again unless `refill` is this review's one outcome. Read
|
|
50
|
+
`refill_v3_world_state` for the next two sender-local days: a row whose
|
|
51
|
+
reason names a disconnected sender (`sender_disconnected`), a disconnected
|
|
52
|
+
Sales Navigator (`sales_nav_disconnected`) or
|
|
53
|
+
`paid_inmail_connection_health_recheck_required` is a fix only the customer
|
|
54
|
+
can make. Make it this review's `input`: name the sender and say reconnect
|
|
55
|
+
LinkedIn or reconnect Sales Navigator in Settings.
|
|
53
56
|
6. Observe the owned operation, then record the outcome against the same review
|
|
54
57
|
effect and fence. The `record_review_outcome` call contains only its action,
|
|
55
58
|
effect, fence, review outcome, summary, and optional decision/operation refs;
|