@sellable/install 0.1.777 → 0.1.779

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.
@@ -3,12 +3,12 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.777
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.779
7
7
  ARG MCP_PACKAGE=@sellable/mcp@0.1.947
8
8
  ARG INSTALLER_INTEGRITY
9
9
  ARG MCP_INTEGRITY=sha512-3/IoWRHWyTjdh7OjYHdYS+Na6v9TkYhGXKSYX4rYjhqm4b7MtyV2HaRlZVYKUv8afS1UzZcCUXUx7kqF/Mzk4A==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.777" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.779" \
12
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}" \
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.777" \
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.777' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.947') throw new Error('package identity rejected');" \
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.779' || 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.777', 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}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.779', 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
@@ -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.777` is
6
+ `@sellable/install@0.1.779` 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; otherwise retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.\n Whatever the outcome, also run canonical Refill Sends with `yolo:true` for the\n workspace's next two sender-local days before you finish. It checks capacity\n itself and does nothing when queues are full. Keep going until the gap is\n filled or a real blocker remains; refill results reach the customer in the\n next results update, not as a separate post.\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"
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; otherwise retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.
67
- Whatever the outcome, also run canonical Refill Sends with `yolo:true` for the
68
- workspace's next two sender-local days before you finish. It checks capacity
69
- itself and does nothing when queues are full. Keep going until the gap is
70
- filled or a real blocker remains; refill results reach the customer in the
71
- next results update, not as a separate post.
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;
@@ -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=3b9335df46d0a00284c71bfab620edf0abd7882d2ad2216343cf5b6098345baa
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
- ensureHermesAdminRefillSendsCron,
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 ensureHermesAdminRefillSendsCron({
1445
+ ? await ensureHermesRefillSendsCron({
1446
1446
  profileId: profile,
1447
1447
  profileRoot: profileHome,
1448
1448
  hermesRoot: DATA_ROOT,
@@ -8,7 +8,7 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
8
8
 
9
9
  ARG MCP_PACKAGE=@sellable/mcp@0.1.947
10
10
  ARG MCP_INTEGRITY=sha512-3/IoWRHWyTjdh7OjYHdYS+Na6v9TkYhGXKSYX4rYjhqm4b7MtyV2HaRlZVYKUv8afS1UzZcCUXUx7kqF/Mzk4A==
11
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.777
11
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.779
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=3063a681b695461d6b8897c8fea57afa59141c9f5e302116436762935d39db69
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,7 +123,7 @@ 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.777";
126
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.779";
126
127
  const MCP_PACKAGE =
127
128
  process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.947";
128
129
  export const ENDPOINT_MAX_CONCURRENT_RUNS = 10;
@@ -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,13 +134,13 @@ export const HERMES_CUSTOMER_PROGRAM_CRONS = Object.freeze([
134
134
  ]),
135
135
  }),
136
136
  ]);
137
- // Built-in daily refill: Admin image first, customer image after it has proven
138
- // itself. It posts nothing (deliver local, reply [SILENT]); the next results update
139
- // reports what was added. Refill observes capacity itself and does nothing when
140
- // queues are full, so a single weekday tick is enough and a missed one is harmless.
141
- export const HERMES_ADMIN_REFILL_CRON = Object.freeze({
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
- schedule: "0 14 * * 1-5",
144
144
  deliver: "local",
145
145
  skills: Object.freeze(["sellable-refill-sends"]),
146
146
  enabledToolsets: Object.freeze(["safe", "sellable", "mcp"]),
@@ -1952,17 +1952,23 @@ function opportunitySequenceIdentity(profileRoot) {
1952
1952
  );
1953
1953
  }
1954
1954
 
1955
- function adminRefillPrompt(workspaceId) {
1955
+ // Weekdays between 14:00 and 14:49 UTC, one stable minute per agent: every agent
1956
+ // starting refill at 14:00:00 crowded the product's database pool (Sep 30).
1957
+ export function refillSendsSchedule(profileId) {
1958
+ const minute = parseInt(sha256(profileId).slice(0, 8), 16) % 50;
1959
+ return `${minute} 14 * * 1-5`;
1960
+ }
1961
+
1962
+ function refillSendsPrompt(workspaceId) {
1956
1963
  return [
1957
1964
  "Run the installed refill-sends skill with --yolo for this workspace.",
1958
1965
  "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
- "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 daily results update reports what was added.",
1966
+ "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
1967
  `workspaceId: ${workspaceId}`,
1962
1968
  ].join("\n");
1963
1969
  }
1964
1970
 
1965
- export async function ensureHermesAdminRefillSendsCron({
1971
+ export async function ensureHermesRefillSendsCron({
1966
1972
  profileId,
1967
1973
  profileRoot,
1968
1974
  hermesRoot,
@@ -1975,11 +1981,12 @@ export async function ensureHermesAdminRefillSendsCron({
1975
1981
  !existsSync(profileRoot) ||
1976
1982
  !ONBOARDING_WORKSPACE_ID.test(workspaceId ?? "")
1977
1983
  ) {
1978
- return failed("hermes_admin_refill_sends_cron_identity_rejected");
1984
+ return failed("hermes_refill_sends_cron_identity_rejected");
1979
1985
  }
1980
1986
  try {
1981
- const cron = HERMES_ADMIN_REFILL_CRON;
1982
- const prompt = adminRefillPrompt(workspaceId);
1987
+ const cron = HERMES_REFILL_SENDS_CRON;
1988
+ const prompt = refillSendsPrompt(workspaceId);
1989
+ const schedule = refillSendsSchedule(profileId);
1983
1990
  const expected = (job) =>
1984
1991
  Boolean(
1985
1992
  job?.id &&
@@ -1991,7 +1998,7 @@ export async function ensureHermesAdminRefillSendsCron({
1991
1998
  sameJson(job.skills, cron.skills) &&
1992
1999
  sameJson(job.enabled_toolsets, cron.enabledToolsets) &&
1993
2000
  job.schedule?.kind === "cron" &&
1994
- job.schedule?.expr === cron.schedule
2001
+ job.schedule?.expr === schedule
1995
2002
  );
1996
2003
  const mutate = (jobId = null) =>
1997
2004
  childRunner(runChild)({
@@ -2001,7 +2008,7 @@ export async function ensureHermesAdminRefillSendsCron({
2001
2008
  OPPORTUNITY_SEQUENCE_CRON_PYTHON,
2002
2009
  JSON.stringify({
2003
2010
  prompt,
2004
- schedule: cron.schedule,
2011
+ schedule,
2005
2012
  name: cron.name,
2006
2013
  deliver: cron.deliver,
2007
2014
  skills: [...cron.skills],
@@ -2028,7 +2035,7 @@ export async function ensureHermesAdminRefillSendsCron({
2028
2035
  isExpected: expected,
2029
2036
  create: () => mutate(),
2030
2037
  update: (job) => mutate(job.id),
2031
- codePrefix: "hermes_admin_refill_sends_cron",
2038
+ codePrefix: "hermes_refill_sends_cron",
2032
2039
  });
2033
2040
  if (!result.ok) return result;
2034
2041
  return {
@@ -2043,7 +2050,7 @@ export async function ensureHermesAdminRefillSendsCron({
2043
2050
  workspaceId,
2044
2051
  };
2045
2052
  } catch {
2046
- return failed("hermes_admin_refill_sends_cron_install_failed");
2053
+ return failed("hermes_refill_sends_cron_install_failed");
2047
2054
  }
2048
2055
  }
2049
2056
 
@@ -38,7 +38,7 @@ 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.777";
41
+ const INSTALLER_PACKAGE = "@sellable/install@0.1.779";
42
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}$/;
@@ -1546,7 +1546,7 @@ 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.777" ||
1549
+ pinned.installerPackage !== "@sellable/install@0.1.779" ||
1550
1550
  pinned.mcpPackage !== "@sellable/mcp@0.1.947" ||
1551
1551
  !Array.isArray(policy.toolInclude) ||
1552
1552
  policy.toolInclude.length === 0 ||
@@ -312,7 +312,7 @@ 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.777" ||
315
+ desired.installerPackage !== "@sellable/install@0.1.779" ||
316
316
  desired.mcpPackage !== "@sellable/mcp@0.1.947" ||
317
317
  !Array.isArray(desired.toolInclude) ||
318
318
  desired.toolInclude.length === 0 ||
@@ -25,7 +25,7 @@ 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.777";
28
+ export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.779";
29
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
@@ -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; otherwise retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.\n Whatever the outcome, also run canonical Refill Sends with `yolo:true` for the\n workspace's next two sender-local days before you finish. It checks capacity\n itself and does nothing when queues are full. Keep going until the gap is\n filled or a real blocker remains; refill results reach the customer in the\n next results update, not as a separate post.\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"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.777",
3
+ "version": "0.1.779",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {
@@ -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; otherwise retain one exact next step. Ask only for what you cannot do yourself, such as a LinkedIn connection, as one question.
48
- Whatever the outcome, also run canonical Refill Sends with `yolo:true` for the
49
- workspace's next two sender-local days before you finish. It checks capacity
50
- itself and does nothing when queues are full. Keep going until the gap is
51
- filled or a real blocker remains; refill results reach the customer in the
52
- next results update, not as a separate post.
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;