@sellable/install 0.1.430 → 0.1.431-wip.146.20260728003748
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/bin/sellable-install.mjs +4 -2
- package/container/Dockerfile +7 -7
- package/container/README.md +2 -2
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/fly-customer-image/Dockerfile +3 -3
- package/lib/sellable-agent/fly-customer-image/customer-runtime.mjs +2 -2
- package/lib/sellable-agent/host-bootstrap.mjs +3 -2
- package/lib/sellable-agent/host-worker.mjs +3 -3
- package/lib/sellable-agent/profile-materializer.mjs +4 -4
- package/lib/sellable-agent/provisioning-adapter.mjs +7 -6
- package/package.json +1 -1
- package/skill-templates/refill-sends-evergreen.md +24 -0
- package/skill-templates/refill-sends-v2.md +207 -0
- package/skill-templates/refill-sends.md +63 -14
package/bin/sellable-install.mjs
CHANGED
|
@@ -40,7 +40,7 @@ function getInstallVersion() {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
const CODEX_PLUGIN_VERSION = "0.1.
|
|
43
|
+
const CODEX_PLUGIN_VERSION = "0.1.71";
|
|
44
44
|
const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
45
45
|
"0.1.8",
|
|
46
46
|
"0.1.9",
|
|
@@ -96,7 +96,6 @@ const CODEX_PLUGIN_COMPAT_VERSIONS = [
|
|
|
96
96
|
"0.1.68",
|
|
97
97
|
"0.1.69",
|
|
98
98
|
"0.1.70",
|
|
99
|
-
"0.1.71",
|
|
100
99
|
"0.1.70-phase981.20260711223346+codex.20260712010246",
|
|
101
100
|
];
|
|
102
101
|
const RAW_INSTALL_PACKAGE_SPEC =
|
|
@@ -1194,6 +1193,9 @@ const CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS = [
|
|
|
1194
1193
|
];
|
|
1195
1194
|
|
|
1196
1195
|
const REFILL_SENDS_ALLOWED_TOOLS = [
|
|
1196
|
+
"mcp__sellable__refill_v3_sender_date_eligibility",
|
|
1197
|
+
"mcp__sellable__refill_v3_supply_assessment",
|
|
1198
|
+
"mcp__sellable__refill_v3_workspace_inventory",
|
|
1197
1199
|
"mcp__sellable__refill_sends",
|
|
1198
1200
|
"mcp__sellable__get_auth_status",
|
|
1199
1201
|
"mcp__sellable__start_cli_login",
|
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.418
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.644
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
|
-
ARG MCP_INTEGRITY=sha512-
|
|
9
|
+
ARG MCP_INTEGRITY=sha512-Q2+bBeQCKy/tZ9hMslOZ1Tw/kTLKBjiv77zQqX38IFrNevLiOhRG4rsxvVPqfHkOIIOSKFl/ECTgFD0pcmhAXg==
|
|
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.418" \
|
|
12
|
+
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.644" \
|
|
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.430" \
|
|
|
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.418' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.644') 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.418', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.644', 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,13 +3,13 @@
|
|
|
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.418` is
|
|
7
7
|
published:
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
docker build --platform linux/amd64 \
|
|
11
11
|
--build-arg INSTALLER_INTEGRITY='sha512-…' \
|
|
12
|
-
--tag sellable-agent-host:0.1.
|
|
12
|
+
--tag sellable-agent-host:0.1.418 .
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
|
|
@@ -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.644";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -3,9 +3,9 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
7
|
-
ARG MCP_INTEGRITY=sha512-
|
|
8
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
6
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.755
|
|
7
|
+
ARG MCP_INTEGRITY=sha512-ryrP8FpK+4LjnqqeCTnP8+6bZwPk9u7Owtw/orEJVM9SOLJ7q8FjA/ox5ySLq79GTtAlIID5HC6vOpsvLqhQtA==
|
|
8
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.418
|
|
9
9
|
ARG HERMES_VERSION=0.19.0
|
|
10
10
|
ARG MCP_TRUST_ROOT_B64
|
|
11
11
|
ARG TIRITH_VERSION=0.3.3
|
|
@@ -62,9 +62,9 @@ const RUNTIME_GENERATION = Number(
|
|
|
62
62
|
const BOOT_SESSION_ID = randomUUID();
|
|
63
63
|
const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.19.0";
|
|
64
64
|
const INSTALLER_PACKAGE =
|
|
65
|
-
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.
|
|
65
|
+
process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.418";
|
|
66
66
|
const MCP_PACKAGE =
|
|
67
|
-
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.
|
|
67
|
+
process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.755";
|
|
68
68
|
|
|
69
69
|
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
|
70
70
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -38,8 +38,9 @@ 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 =
|
|
42
|
-
|
|
41
|
+
const INSTALLER_PACKAGE =
|
|
42
|
+
"@sellable/install@0.1.418";
|
|
43
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.644";
|
|
43
44
|
const HERMES_VERSION = "0.18.0";
|
|
44
45
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
46
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -1513,9 +1513,9 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1513
1513
|
]) ||
|
|
1514
1514
|
pinned.hermesCli !== "hermes" ||
|
|
1515
1515
|
pinned.hermesVersion !== "0.18.0" ||
|
|
1516
|
-
pinned.installerPackage !==
|
|
1517
|
-
|
|
1518
|
-
|
|
1516
|
+
pinned.installerPackage !==
|
|
1517
|
+
"@sellable/install@0.1.418" ||
|
|
1518
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.644" ||
|
|
1519
1519
|
!Array.isArray(policy.toolInclude) ||
|
|
1520
1520
|
policy.toolInclude.length === 0 ||
|
|
1521
1521
|
!Number.isSafeInteger(slack.generation) ||
|
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
relative,
|
|
24
24
|
resolve,
|
|
25
25
|
} from "node:path";
|
|
26
|
+
import { HERMES_AGENT_BRIDGE_CONTRACT } from "./hermes-bridge.mjs";
|
|
26
27
|
import {
|
|
27
28
|
FLY_CUSTOMER_MODEL_ID,
|
|
28
29
|
FLY_CUSTOMER_MODEL_PROVIDER,
|
|
29
30
|
FLY_CUSTOMER_MODEL_REASONING_EFFORT,
|
|
30
31
|
} from "./fly-customer-model.mjs";
|
|
31
|
-
import { HERMES_AGENT_BRIDGE_CONTRACT } from "./hermes-bridge.mjs";
|
|
32
32
|
|
|
33
33
|
export const PROFILE_MATERIALIZER_PORTS = Object.freeze({
|
|
34
34
|
hostAppSecret: ["resolveHostSecret"],
|
|
@@ -192,9 +192,9 @@ function validateDesired(desired) {
|
|
|
192
192
|
desired.serviceCredentialGeneration < 1 ||
|
|
193
193
|
desired.hermesCli !== "hermes" ||
|
|
194
194
|
desired.hermesVersion !== "0.18.0" ||
|
|
195
|
-
desired.installerPackage !==
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
desired.installerPackage !==
|
|
196
|
+
"@sellable/install@0.1.418" ||
|
|
197
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.644" ||
|
|
198
198
|
!Array.isArray(desired.toolInclude) ||
|
|
199
199
|
desired.toolInclude.length === 0 ||
|
|
200
200
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
@@ -25,9 +25,9 @@ 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 =
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
export const PINNED_INSTALL_PACKAGE =
|
|
29
|
+
"@sellable/install@0.1.418";
|
|
30
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.644";
|
|
31
31
|
|
|
32
32
|
export function deriveAgentProfileId(workspaceId, agentId, hostId) {
|
|
33
33
|
return deriveContainedProfileId(workspaceId, agentId, hostId);
|
|
@@ -612,7 +612,8 @@ function inspectProvisionedProfile({
|
|
|
612
612
|
!matchesReadback(manifest, request) ||
|
|
613
613
|
manifest.serviceCredentialReference !== serviceCredentialReference ||
|
|
614
614
|
manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
|
|
615
|
-
manifest.installerVersion !==
|
|
615
|
+
manifest.installerVersion !==
|
|
616
|
+
"0.1.418" ||
|
|
616
617
|
manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
|
|
617
618
|
manifest.credentialKeyVersion !== credentialKeyVersion ||
|
|
618
619
|
!/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
|
|
@@ -826,7 +827,7 @@ function successReceipt({
|
|
|
826
827
|
subject: `agent-profile:${observed.profileId}`,
|
|
827
828
|
cli: {
|
|
828
829
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
829
|
-
installVersion: "0.1.
|
|
830
|
+
installVersion: "0.1.418",
|
|
830
831
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
831
832
|
command: "hermes profile bootstrap",
|
|
832
833
|
},
|
|
@@ -874,7 +875,7 @@ function failure(code, stages, extra = {}, request = null) {
|
|
|
874
875
|
: "agent-profile:unbound",
|
|
875
876
|
cli: {
|
|
876
877
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
877
|
-
installVersion: "0.1.
|
|
878
|
+
installVersion: "0.1.418",
|
|
878
879
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
879
880
|
command: "hermes profile bootstrap",
|
|
880
881
|
},
|
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refill-sends-evergreen
|
|
3
|
+
description: Deprecated alias for refill-sends-v2.
|
|
4
|
+
visibility: public
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- mcp__sellable__get_subskill_prompt
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Refill Sends Evergreen
|
|
10
|
+
|
|
11
|
+
Superseded by refill-sends-v2. The prompt name now redirects through
|
|
12
|
+
`DEPRECATED_SUBSKILL_PROMPT_REPLACEMENTS`; do not call this deprecated prompt
|
|
13
|
+
directly.
|
|
14
|
+
|
|
15
|
+
Use `refill-sends-v2` instead:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
get_subskill_prompt({ subskillName: "refill-sends-v2" })
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The alias must not strip exact `targetConfig`, server `runHandle`,
|
|
22
|
+
`reportingContext`, canonical `refill_reporting.v2`, or an explicit
|
|
23
|
+
`messageTemplateRevision`. It never infers revision authority from
|
|
24
|
+
`forceRerun`.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: refill-sends-v2
|
|
3
|
+
description: Execute refill sends v2 through the fenced evergreen refill loop, with dry-run and resume support.
|
|
4
|
+
visibility: public
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- mcp__sellable__refill_sends_v2
|
|
7
|
+
- mcp__sellable__get_refill_plan_v2
|
|
8
|
+
- mcp__sellable__get_subskill_prompt
|
|
9
|
+
- mcp__sellable__get_subskill_asset
|
|
10
|
+
- mcp__sellable__get_auth_status
|
|
11
|
+
- mcp__sellable__get_active_workspace
|
|
12
|
+
- mcp__sellable__list_workspaces
|
|
13
|
+
- mcp__sellable__get_workspace
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Refill Sends V2
|
|
17
|
+
|
|
18
|
+
Use this command for refill requests such as "refill sends", "fill evergreen
|
|
19
|
+
senders", "load everyone up", "run Damiano", or "show a dry run for this
|
|
20
|
+
workspace".
|
|
21
|
+
|
|
22
|
+
Host command names:
|
|
23
|
+
|
|
24
|
+
- Claude Code: `/sellable:refill-sends-v2`
|
|
25
|
+
- Codex: `$sellable:refill-sends-v2`
|
|
26
|
+
|
|
27
|
+
`refill_sends_v2` is the execution surface. In real-run mode it starts or
|
|
28
|
+
resumes a fenced refill run, reads a fresh packet, executes only the packet's
|
|
29
|
+
named bounded work, verifies the result, and returns either a terminal report, a
|
|
30
|
+
blocked report, or an in-progress resume handle. In dry-run mode it stays
|
|
31
|
+
read-only and writes only a dry-run journal.
|
|
32
|
+
|
|
33
|
+
## Entry
|
|
34
|
+
|
|
35
|
+
Resolve the explicit `workspaceId` from the request, automation config, or
|
|
36
|
+
active workspace readback. Scheduled or autonomous usage must carry
|
|
37
|
+
`workspaceId` on every tool call. If the workspace is missing or ambiguous, stop
|
|
38
|
+
with `WORKSPACE_REQUIRED`; do not switch the shared active workspace.
|
|
39
|
+
|
|
40
|
+
For a real refill run:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
refill_sends_v2({ workspaceId, intent:"auto", senderIds?, approvalMode? })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For read-only inspection:
|
|
47
|
+
|
|
48
|
+
```text
|
|
49
|
+
refill_sends_v2({ workspaceId, dryRun:true, intent:"auto", senderIds? })
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To resume an in-progress run, pass the handle back exactly:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
refill_sends_v2({ workspaceId, runHandle:{runId,fence}, targetConfig, reportingContext, messageTemplateRevision? })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`targetConfig` and the server-issued `runHandle` are separate authorities. Keep
|
|
59
|
+
both byte-for-byte through every continuation and takeover; never fold the
|
|
60
|
+
rotating fence into target configuration.
|
|
61
|
+
|
|
62
|
+
An approved copy change is reachable only through an explicit
|
|
63
|
+
`messageTemplateRevision` object with `version:1`, `source:"user_approved"`,
|
|
64
|
+
the approved `templateMarkdown`, `messageTemplateRevision` identity,
|
|
65
|
+
`priorTemplateAuthorityDigest`, `nextTemplateAuthorityDigest`, 1–500 exact
|
|
66
|
+
`cohortRowIds`, and `requestId`, `effectId`, and `revisionOperationId`. Pass it
|
|
67
|
+
with the exact `targetConfig` and `reportingContext`; the command obtains or
|
|
68
|
+
resumes the server run handle and lets the planner name
|
|
69
|
+
`revise_message_template_and_rerun`. Missing or partial input means no revision.
|
|
70
|
+
`forceRerun:true` on a queue operation never authorizes or implies a template
|
|
71
|
+
change.
|
|
72
|
+
|
|
73
|
+
After a committed and dispatched revision receipt, continue only with the
|
|
74
|
+
planner-provided revision-derived `approve_messages` action. It must preserve
|
|
75
|
+
the exact revision cohort as `rowSelector:{type:"rowIds",rowIds:[...]}` plus
|
|
76
|
+
`approvalMode:"approve"`, the compiled readiness identity, and the canonical
|
|
77
|
+
`reportingContext`, even when the ordinary sender lane is ineligible. Do not
|
|
78
|
+
replace it with `queue_campaign_cells` on Approved cells; that path cannot mint
|
|
79
|
+
the authoritative approval/lane-scope receipt. Require `laneScope` in the
|
|
80
|
+
bounded preparation receipt before the lane is considered approved. A
|
|
81
|
+
disconnected or zero-capacity sender still blocks scheduling and does not
|
|
82
|
+
authorize reconnecting, campaign start, or direct send.
|
|
83
|
+
|
|
84
|
+
If a stale handle loses the lease, the tool reports the holder status and the
|
|
85
|
+
approximately 10 minute lockout window. Reinvoke with the current handle or wait
|
|
86
|
+
for lease expiry; never guess a fence.
|
|
87
|
+
|
|
88
|
+
The default `intent:"auto"` inspects `managed_waterfall`,
|
|
89
|
+
`dashboard_evergreen`, and `active_campaign` lane sources. Report the lane
|
|
90
|
+
source and lane chain as proof for every selected sender.
|
|
91
|
+
Connection-only evergreen lanes are invite-only refill targets. When the packet
|
|
92
|
+
or campaign proof shows exactly `["send_invite"]`, report and execute only
|
|
93
|
+
`selectedLane:"send_invite"` work; do not infer DM, InMail, Sales Nav cascade,
|
|
94
|
+
paid-credit refresh, message generation, follow-up, sequence mutation,
|
|
95
|
+
launch/start beyond packet authority, scheduler writes, or direct sends.
|
|
96
|
+
|
|
97
|
+
If membership blocks a workspace read, report the structured
|
|
98
|
+
`workspace_access` blocker instead of retrying auth or switching workspaces.
|
|
99
|
+
|
|
100
|
+
## Authority
|
|
101
|
+
|
|
102
|
+
The run may execute only packet-named bounded work:
|
|
103
|
+
|
|
104
|
+
- approve generated messages that are ready for review (`approve_messages`);
|
|
105
|
+
- enrich the next batch of unenriched rows (`enrich_more`);
|
|
106
|
+
- copy leads already found into the campaign table (`reconcile_source_copy`);
|
|
107
|
+
- re-run errored enrichment/message cells (`rerun_errored_cells`);
|
|
108
|
+
- find more leads via this campaign's original source (`add_leads_same_source`)
|
|
109
|
+
only for mechanical same-source continuations;
|
|
110
|
+
- start the paused campaign so ready rows can schedule (`start_campaign`) only
|
|
111
|
+
when the campaign is in the packet's pinned lane chain;
|
|
112
|
+
- refresh paid InMail credit facts during bootstrap and once per scheduler-wait
|
|
113
|
+
entry when the packet requires it.
|
|
114
|
+
- apply `revise_message_template_and_rerun` only when the packet contains the
|
|
115
|
+
complete explicit user-approved revision envelope and exact fenced target.
|
|
116
|
+
- after that revision commits, apply only its exact-row, readiness-bound
|
|
117
|
+
`approve_messages` continuation and require the returned lane-scope receipt.
|
|
118
|
+
|
|
119
|
+
The loop records planned -> did -> outcome before and after every foreign
|
|
120
|
+
mutation. It refuses stale packets by fingerprint and validates packet
|
|
121
|
+
coherence before dispatch. The planner owns repair decisions: if a batch needs
|
|
122
|
+
repair, the next packet names the repair rung; the loop bounds and refuses, it
|
|
123
|
+
does not improvise fixes.
|
|
124
|
+
|
|
125
|
+
Do not create campaigns, switch provider families, lower thresholds, write
|
|
126
|
+
scheduler fields, send messages, archive/delete campaigns, or mutate
|
|
127
|
+
brief/filter/message/sequence/sender config unless a separate exact approval
|
|
128
|
+
packet explicitly authorizes that work. In short: no campaign creation, no
|
|
129
|
+
provider-family switch, no threshold lowering, no scheduler writes, no
|
|
130
|
+
launch/send/archive/delete, and no brief/filter/message/sequence/sender
|
|
131
|
+
mutation.
|
|
132
|
+
|
|
133
|
+
## Continuations
|
|
134
|
+
|
|
135
|
+
Sales Nav and Prospeo source continuations that are not yolo-safe are blocked
|
|
136
|
+
as executable scoped handoffs. The report will include text shaped like:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
$sellable:create-campaign [campaignId]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
That handoff is run in a separate thread only after explicit approval. Scope is
|
|
143
|
+
strict: add leads through this campaign's existing source, keep the same
|
|
144
|
+
saved-search identity, import mode `add` into the same source list, and do
|
|
145
|
+
source work only. It is never a new campaign, provider-family switch, brief
|
|
146
|
+
change, rubric change, message change, sequence change, or sender change.
|
|
147
|
+
|
|
148
|
+
## Verification
|
|
149
|
+
|
|
150
|
+
The loop verifies whole rows before replanning. It reattaches to an own active
|
|
151
|
+
prep job instead of re-dispatching, waits on foreign prep jobs without adopting
|
|
152
|
+
their outcomes, waits on active enrichment/import work by rereading the packet,
|
|
153
|
+
and treats capped scheduler capacity as complete.
|
|
154
|
+
|
|
155
|
+
Scheduler waits are cross-invocation. Each tool call polls briefly under the
|
|
156
|
+
host guard, heartbeats the run lease, and may return:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
{ status:"in_progress", runId, fence, gate, guidance }
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Reinvoke with that handle. The approximately five minute scheduler budget is at
|
|
163
|
+
the run level, not one tool call. A window-closed or loaded-awaiting scheduler
|
|
164
|
+
report must include remaining-ready count, exact expected pickup time, and the
|
|
165
|
+
resume handle; never treat bare "awaiting scheduler" copy as a final answer.
|
|
166
|
+
Scheduler-run receipt interpretation still applies to terminal progress:
|
|
167
|
+
`cellsConsidered is allocation-attempt count`, not total ready supply, while
|
|
168
|
+
`readyCellsFound` is ready inventory found before prefilters. Inspect
|
|
169
|
+
`campaignScopeSummary` before assuming the selected refill campaign/table was
|
|
170
|
+
included; if absent, do not infer that target was ready-but-blocked. Interpret
|
|
171
|
+
`prefiltered`, `skipped`, and `deferred` separately. For ready closed-InMail
|
|
172
|
+
cells with stale paid-credit reasons, route to
|
|
173
|
+
`refresh_paid_inmail_credits_then_rerun` once, then rerun/status.
|
|
174
|
+
`wait_for_capacity_or_window` means report loaded/capped/waiting and do not
|
|
175
|
+
source or prep more rows; `no_ready_cells_continue_refill_prep` means return to
|
|
176
|
+
the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
|
|
177
|
+
|
|
178
|
+
After the first prep batch, read the conversion-verdict journal line. It is
|
|
179
|
+
rendered from packet facts: `supplyCensus`, `censusReason`, and
|
|
180
|
+
`pipelineDiagnosis.firstFailing`. If the verdict is honest rubric failure, the
|
|
181
|
+
human decision is to update the rubric or change lead source; refill does not
|
|
182
|
+
mutate those automatically.
|
|
183
|
+
|
|
184
|
+
## Results
|
|
185
|
+
|
|
186
|
+
Terminal reasons are:
|
|
187
|
+
|
|
188
|
+
- `complete`
|
|
189
|
+
- `capped_by_scheduler`
|
|
190
|
+
- `loaded_awaiting_scheduler`
|
|
191
|
+
- `lanes_exhausted`
|
|
192
|
+
- `blocked`
|
|
193
|
+
- `no_refillable_campaigns`
|
|
194
|
+
- `not_an_evergreen_workspace`
|
|
195
|
+
|
|
196
|
+
Report campaign names and sender names first, ids second. Include the terminal
|
|
197
|
+
reason or resume handle, lane source, lane chain, chosen label with token
|
|
198
|
+
secondary, plan revision, journal path, blocked continuation packets, and
|
|
199
|
+
firstFailing checklist when present.
|
|
200
|
+
|
|
201
|
+
Preserve `refill_reporting.v2` exactly in progress, continuation, replay, and
|
|
202
|
+
terminal results. Do not recompute its semantic counts or drop live-preparation
|
|
203
|
+
epoch, snapshot sequence, reset reason, watermark, blockers, compatibility, or
|
|
204
|
+
terminal fields. Never surface raw prospect or message-copy fields.
|
|
205
|
+
|
|
206
|
+
Real-run journals live under `~/.sellable/refill/runs` by default and append an
|
|
207
|
+
index line. Dry runs include the dry marker and do not create run records.
|
|
@@ -3,6 +3,9 @@ name: refill-sends
|
|
|
3
3
|
description: Refill projected sends across a workspace or selected senders through the canonical fenced coordinator.
|
|
4
4
|
visibility: public
|
|
5
5
|
allowed-tools:
|
|
6
|
+
- mcp__sellable__refill_v3_sender_date_eligibility
|
|
7
|
+
- mcp__sellable__refill_v3_supply_assessment
|
|
8
|
+
- mcp__sellable__refill_v3_workspace_inventory
|
|
6
9
|
- mcp__sellable__refill_sends
|
|
7
10
|
- mcp__sellable__get_refill_target_plan
|
|
8
11
|
- mcp__sellable__get_scheduler_fill_capacity
|
|
@@ -30,6 +33,53 @@ Host commands:
|
|
|
30
33
|
- Claude Code: `/sellable:refill-sends`
|
|
31
34
|
- Codex: `$sellable:refill-sends`
|
|
32
35
|
|
|
36
|
+
## Refill v3 Calls 1–3a — progressive observation gate
|
|
37
|
+
|
|
38
|
+
For every new fill or refill intent, use this gate instead of the legacy flow
|
|
39
|
+
below while the Refill v3 observation rollout is current:
|
|
40
|
+
|
|
41
|
+
1. Call `get_auth_status`. If Sellable authentication is not valid, run only
|
|
42
|
+
the returned login flow and recheck authentication.
|
|
43
|
+
2. Load
|
|
44
|
+
`get_subskill_asset({ subskillName: "refill-sends", assetPath: "core/flow.v3.json" })`
|
|
45
|
+
completely. Require `workflow:"refill-sends-v3"` and `version:3`. Follow
|
|
46
|
+
only the current state's `allowedTools`, `doNotLoad`, and transition.
|
|
47
|
+
3. Resolve the exact workspace by explicit ID, exact name, install mapping, or
|
|
48
|
+
an unambiguous accessible-workspace match using `get_active_workspace` and
|
|
49
|
+
`list_workspaces`. Never change the active workspace.
|
|
50
|
+
4. Call
|
|
51
|
+
`refill_v3_workspace_inventory({ workspaceId, detail: "concise" })` exactly
|
|
52
|
+
once.
|
|
53
|
+
5. For each globally eligible sender and requested sender-local date, call
|
|
54
|
+
`refill_v3_sender_date_eligibility` exactly once with the server-issued
|
|
55
|
+
`runId` and `fence`. Never call it for a globally ineligible sender.
|
|
56
|
+
6. Only for a positively eligible lane, call `refill_v3_supply_assessment`
|
|
57
|
+
exactly once for each campaign/lane tuple issued by inventory. Pass the
|
|
58
|
+
backend-applied quota as the assessment bound; never invent or substitute a
|
|
59
|
+
campaign, lane, sender, date, run identity, or fence.
|
|
60
|
+
7. Render the complete inventory, sender/date eligibility, and supply result,
|
|
61
|
+
including every typed blocker, then stop at `observed` or `blocked`.
|
|
62
|
+
|
|
63
|
+
A sender whose LinkedIn account is connected but whose Sales Navigator status
|
|
64
|
+
is disconnected remains eligible for real non-Sales-Nav lanes. For Thomas or
|
|
65
|
+
any equivalent sender, paid InMail must return `sales_nav_disconnected` at
|
|
66
|
+
eligibility and transition directly to terminal `blocked`: do not call supply,
|
|
67
|
+
refresh a provider, invoke Sales Navigator, or schedule or place anything.
|
|
68
|
+
|
|
69
|
+
Do not load `refill-sends-workflow`, call `get_refill_target_plan`, or call
|
|
70
|
+
`refill_sends` for a new fill or refill intent at this rollout stage. Calls 1–3a
|
|
71
|
+
are backend-owned observation calls; they may append their fenced decision
|
|
72
|
+
ledger and Call 2 may perform its bounded single-flight paid-credit cache
|
|
73
|
+
refresh. They never approve or enrich rows, acquire supply, change campaign
|
|
74
|
+
state, invoke a Sales Navigator provider action, schedule, place, or send.
|
|
75
|
+
Between calls, retain the server-issued `runId` and `fence`; never reconstruct
|
|
76
|
+
downstream policy in the host.
|
|
77
|
+
|
|
78
|
+
The legacy sections below remain reference documentation for explicitly
|
|
79
|
+
authorized legacy execution only. `refill_sends` remains the only mutation
|
|
80
|
+
execution owner on that legacy path; this observation gate does not introduce
|
|
81
|
+
another mutation owner.
|
|
82
|
+
|
|
33
83
|
## Entry and exact scope
|
|
34
84
|
|
|
35
85
|
Accepted request fields are `--yolo`, `workspaceId`, `--sender`/`senderIds`/
|
|
@@ -76,7 +126,7 @@ get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flo
|
|
|
76
126
|
|
|
77
127
|
Continue chunks until `hasMore:false`; require `workflow:"refill-sends-workflow"`
|
|
78
128
|
and a COMPATIBLE version: same major (`v1`) and minor at or above the minimum
|
|
79
|
-
`v1.
|
|
129
|
+
`v1.8` — so `v1.8`, `v1.9`, `v1.10` all pass and `v2.x` does not. Report the
|
|
80
130
|
verified version before D1. A missing, unparseable, wrong-major, or
|
|
81
131
|
below-minimum version is `workflow_version_mismatch`: stop before
|
|
82
132
|
`get_refill_target_plan` or mutation. Do NOT require exact string equality: the
|
|
@@ -400,7 +450,7 @@ flowchart TD
|
|
|
400
450
|
DATE -- No --> WAIT["Enter coordinator-owned bounded scheduler wait"]
|
|
401
451
|
SWEEP --> RECEIPT{"Receipt state?"}
|
|
402
452
|
RECEIPT -- Terminal --> READ["Canonical target-plan and request/effect readback"]
|
|
403
|
-
RECEIPT -- Active --> POLL["
|
|
453
|
+
RECEIPT -- Active --> POLL["Poll only issued continuation until wait.deadlineAt"]
|
|
404
454
|
RECEIPT -- uncertain_outcome --> RECON["Reconcile same request key read-only; never redispatch"]
|
|
405
455
|
POLL --> RECEIPT
|
|
406
456
|
RECON --> TERM["Terminal: blocked_retryable with request identity intact"]
|
|
@@ -410,7 +460,7 @@ flowchart TD
|
|
|
410
460
|
FULL -- No --> READY{"Ready buffer still covers gap?"}
|
|
411
461
|
READY -- Yes --> OTHER{"Any other workspace target still needs preparation or an executable gate?"}
|
|
412
462
|
OTHER -- Yes --> PREP["Continue next planner-ranked exact target"]
|
|
413
|
-
OTHER -- No -->
|
|
463
|
+
OTHER -- No --> LOADED["Terminal: loaded_awaiting_scheduler"]
|
|
414
464
|
READY -- No --> PREP["Return to row lifecycle"]
|
|
415
465
|
```
|
|
416
466
|
|
|
@@ -422,11 +472,7 @@ allocation-attempt count`; `readyCellsFound` is prefilter inventory. Inspect
|
|
|
422
472
|
closed-InMail cells with stale paid-credit evidence, use
|
|
423
473
|
`refresh_paid_inmail_credits_then_rerun`. `wait_for_capacity_or_window` means do
|
|
424
474
|
not source or prep more rows; `no_ready_cells_continue_refill_prep` returns to
|
|
425
|
-
the row lifecycle. `cellsScheduled:0` alone is not failure.
|
|
426
|
-
dispatched, a host wait budget yields an `in_progress` continuation carrying
|
|
427
|
-
the same run identity. Keep polling that request key until its official
|
|
428
|
-
terminal receipt, then reread the full target plan. Never convert an active
|
|
429
|
-
sweep into `loaded_awaiting_scheduler`.
|
|
475
|
+
the row lifecycle. `cellsScheduled:0` alone is not failure.
|
|
430
476
|
|
|
431
477
|
## Explicit message-template revision
|
|
432
478
|
|
|
@@ -491,12 +537,15 @@ flowchart TD
|
|
|
491
537
|
```
|
|
492
538
|
|
|
493
539
|
One scheduler-loaded exact lane is not a workspace terminal: skip it and
|
|
494
|
-
continue other planner-ranked lanes.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
540
|
+
continue other planner-ranked lanes. Do not keep polling after the coordinator
|
|
541
|
+
returns workspace `loaded_awaiting_scheduler`. That terminal is admissible by
|
|
542
|
+
either path: every remaining exact target is complete or scheduler-owned and
|
|
543
|
+
workspace `remainingReadyOrProjectedGap` is zero, OR every gap-bearing sender
|
|
544
|
+
lane is already prepared and its residue is `ready_to_schedule` surplus, so the
|
|
545
|
+
remaining gap is scheduler/pacing-bound rather than unexposed supply — on that
|
|
546
|
+
second path `remainingReadyOrProjectedGap` is not necessarily zero. Neither path
|
|
547
|
+
means unexposed actionable supply, and neither is permission for a host-owned
|
|
548
|
+
loop. The sole refill-ladder expansion
|
|
500
549
|
that asks the user in `--yolo` is `new_campaign_required`; refill itself never
|
|
501
550
|
creates that campaign. `new_campaign_required` is also the truthful terminal
|
|
502
551
|
when receipts prove every existing lane is spent but the planner has no
|