@sellable/install 0.1.479 → 0.1.480

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- ARG HERMES_IMAGE=nousresearch/hermes-agent@sha256:0ef5c9e17523c9eeb3a9482537ee08bc195e54f39b1588a6b44e66d40ff937fb
1
+ ARG HERMES_IMAGE=nousresearch/hermes-agent@sha256:5316c2c2534c49f5e1b1691e1a2115f1230d900033c3b35d1c8fc2e47352b26d
2
2
  FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
@@ -1796,14 +1796,14 @@ async function flushHermesSnapshot({ client, profileId, profileRoot }) {
1796
1796
  profileId,
1797
1797
  profileRoot,
1798
1798
  hermesRoot: DATA_ROOT,
1799
- hermesVersion: "0.19.0",
1799
+ hermesVersion: "0.19.1",
1800
1800
  debounceMs: 0,
1801
1801
  upload: ({ archiveDigest, archiveBytes, sourceProfileId, bytes }) =>
1802
1802
  client.putMemorySnapshot(
1803
1803
  {
1804
1804
  archiveDigest,
1805
1805
  archiveBytes,
1806
- hermesVersion: "0.19.0",
1806
+ hermesVersion: "0.19.1",
1807
1807
  sourceProfileId,
1808
1808
  },
1809
1809
  bytes
@@ -2426,13 +2426,13 @@ async function runAdminProduction() {
2426
2426
  profileId: activeLaunch.profile.profile,
2427
2427
  profileRoot: activeLaunch.profile.profileHome,
2428
2428
  hermesRoot: DATA_ROOT,
2429
- hermesVersion: "0.19.0",
2429
+ hermesVersion: "0.19.1",
2430
2430
  upload: ({ archiveDigest, archiveBytes, sourceProfileId, bytes }) =>
2431
2431
  client.putMemorySnapshot(
2432
2432
  {
2433
2433
  archiveDigest,
2434
2434
  archiveBytes,
2435
- hermesVersion: "0.19.0",
2435
+ hermesVersion: "0.19.1",
2436
2436
  sourceProfileId,
2437
2437
  },
2438
2438
  bytes
@@ -2554,7 +2554,7 @@ async function runAdminProduction() {
2554
2554
  profileId: basename(profileContract.profileHome),
2555
2555
  profileRoot: profileContract.profileHome,
2556
2556
  hermesRoot: DATA_ROOT,
2557
- hermesVersion: "0.19.0",
2557
+ hermesVersion: "0.19.1",
2558
2558
  fetchLatest: () => client.getMemorySnapshot(),
2559
2559
  });
2560
2560
  if (!memoryRestore.ok) throw new Error(memoryRestore.code);
@@ -1,4 +1,4 @@
1
- ARG HERMES_IMAGE=nousresearch/hermes-agent@sha256:0ef5c9e17523c9eeb3a9482537ee08bc195e54f39b1588a6b44e66d40ff937fb
1
+ ARG HERMES_IMAGE=nousresearch/hermes-agent@sha256:5316c2c2534c49f5e1b1691e1a2115f1230d900033c3b35d1c8fc2e47352b26d
2
2
  FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
@@ -7,7 +7,7 @@ ARG MCP_PACKAGE=@sellable/mcp@0.1.804
7
7
  ARG MCP_INTEGRITY=sha512-3bhenU9lVPXZuxmiPJYSBSlhEfUJTCPdB6RRL40RDaGYKdWoOiKNUhZLTgzc9F6C4uTMGBhT8T2DFmDKL37O/g==
8
8
  ARG INSTALLER_PACKAGE=@sellable/install@0.1.479
9
9
  ARG TUS_JS_CLIENT_VERSION=4.3.1
10
- ARG HERMES_VERSION=0.19.0
10
+ ARG HERMES_VERSION=0.19.1
11
11
  ARG MCP_TRUST_ROOT_B64
12
12
  ARG TIRITH_VERSION=0.3.3
13
13
  ARG PLAYWRIGHT_VERSION=1.61.1
@@ -67,7 +67,7 @@ const RUNTIME_GENERATION = Number(
67
67
  "0"
68
68
  );
69
69
  const BOOT_SESSION_ID = randomUUID();
70
- const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.19.0";
70
+ const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.19.1";
71
71
  const INSTALLER_PACKAGE =
72
72
  process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.479";
73
73
  const MCP_PACKAGE =
@@ -810,7 +810,7 @@ export function createFlyCustomerControlClient({
810
810
  Number.isSafeInteger(value?.archiveBytes) &&
811
811
  value.archiveBytes > 0 &&
812
812
  value.archiveBytes <= 128 * 1024 * 1024 &&
813
- value.hermesVersion === "0.19.0" &&
813
+ value.hermesVersion === "0.19.1" &&
814
814
  /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(value?.sourceProfileId ?? "");
815
815
  const signedUrlAccepted = (value) => {
816
816
  try {
@@ -142,6 +142,155 @@ def _sellable_agent_slack_api_kwargs():
142
142
  return {"base_url": origin.rstrip("/") + "/api/"}
143
143
  `;
144
144
 
145
+ const SLACK_MESSAGE_UNFURL_FILE_HELPER = `
146
+ # ${HERMES_AGENT_BRIDGE_MARKER}: recover files from bounded allowed-channel Slack message unfurls.
147
+ async def _sellable_agent_recover_slack_message_unfurl_files(
148
+ adapter,
149
+ attachments,
150
+ current_channel_id,
151
+ team_id,
152
+ existing_files=None,
153
+ ):
154
+ if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
155
+ return []
156
+ if not current_channel_id or not team_id:
157
+ return []
158
+
159
+ message_unfurls = [
160
+ attachment
161
+ for attachment in list(attachments or [])
162
+ if isinstance(attachment, dict) and attachment.get("is_msg_unfurl") is True
163
+ ][:3]
164
+ if not message_unfurls:
165
+ return []
166
+
167
+ client = adapter._team_clients.get(team_id)
168
+ if client is None:
169
+ logger.warning(
170
+ "[Slack] Refusing message-unfurl file recovery: exact workspace client is unavailable"
171
+ )
172
+ return []
173
+
174
+ allowed_channel_ids = {
175
+ channel_id.strip().upper()
176
+ for channel_id in os.getenv("SLACK_ALLOWED_CHANNELS", "").split(",")
177
+ if channel_id.strip()
178
+ }
179
+ if not allowed_channel_ids:
180
+ logger.warning(
181
+ "[Slack] Refusing message-unfurl file recovery: allowed channels are unavailable"
182
+ )
183
+ return []
184
+
185
+ seen_file_ids = {
186
+ str(file_obj.get("id"))
187
+ for file_obj in (existing_files or [])
188
+ if isinstance(file_obj, dict) and file_obj.get("id")
189
+ }
190
+ recovered = []
191
+ for attachment in message_unfurls:
192
+ target_channel_id = str(attachment.get("channel_id") or "")
193
+ target_ts = str(attachment.get("ts") or "")
194
+ if (
195
+ target_channel_id.upper() not in allowed_channel_ids
196
+ or re.fullmatch(r"\\d+\\.\\d{6}", target_ts) is None
197
+ ):
198
+ continue
199
+
200
+ thread_ts = str(attachment.get("thread_ts") or "")
201
+ if thread_ts and re.fullmatch(r"\\d+\\.\\d{6}", thread_ts) is None:
202
+ thread_ts = ""
203
+
204
+ permalink = str(
205
+ attachment.get("from_url") or attachment.get("title_link") or ""
206
+ )
207
+ if permalink:
208
+ try:
209
+ from html import unescape
210
+ from urllib.parse import parse_qs, urlsplit
211
+
212
+ parsed = urlsplit(unescape(permalink))
213
+ path_match = re.fullmatch(
214
+ r"/archives/([CGD][A-Z0-9]{8,})/p(\\d{16})",
215
+ parsed.path,
216
+ re.IGNORECASE,
217
+ )
218
+ if (
219
+ parsed.scheme != "https"
220
+ or not parsed.hostname
221
+ or not parsed.hostname.lower().endswith(".slack.com")
222
+ or parsed.username
223
+ or parsed.password
224
+ or parsed.port is not None
225
+ or path_match is None
226
+ or path_match.group(1).upper() != target_channel_id
227
+ ):
228
+ continue
229
+ packed_ts = path_match.group(2)
230
+ permalink_ts = f"{packed_ts[:-6]}.{packed_ts[-6:]}"
231
+ if permalink_ts != target_ts:
232
+ continue
233
+ permalink_thread_ts = (parse_qs(parsed.query).get("thread_ts") or [""])[0]
234
+ if re.fullmatch(r"\\d+\\.\\d{6}", permalink_thread_ts or ""):
235
+ thread_ts = permalink_thread_ts
236
+ except (TypeError, ValueError):
237
+ continue
238
+
239
+ if attachment.get("is_reply_unfurl") and not thread_ts:
240
+ logger.warning(
241
+ "[Slack] Refusing reply-unfurl file recovery without an exact thread root"
242
+ )
243
+ continue
244
+
245
+ try:
246
+ if thread_ts and thread_ts != target_ts:
247
+ response = await client.conversations_replies(
248
+ channel=target_channel_id,
249
+ ts=thread_ts,
250
+ oldest=target_ts,
251
+ latest=target_ts,
252
+ inclusive=True,
253
+ limit=1,
254
+ )
255
+ else:
256
+ response = await client.conversations_history(
257
+ channel=target_channel_id,
258
+ oldest=target_ts,
259
+ latest=target_ts,
260
+ inclusive=True,
261
+ limit=1,
262
+ )
263
+ except Exception as exc:
264
+ logger.warning(
265
+ "[Slack] Message-unfurl file recovery failed for %s: %s",
266
+ target_ts,
267
+ type(exc).__name__,
268
+ )
269
+ continue
270
+
271
+ linked_message = next(
272
+ (
273
+ message
274
+ for message in ((response or {}).get("messages") or [])
275
+ if isinstance(message, dict) and str(message.get("ts") or "") == target_ts
276
+ ),
277
+ None,
278
+ )
279
+ if linked_message is None:
280
+ continue
281
+ for file_obj in linked_message.get("files") or []:
282
+ if not isinstance(file_obj, dict):
283
+ continue
284
+ file_id = str(file_obj.get("id") or "")
285
+ if re.fullmatch(r"F[A-Z0-9]{8,}", file_id) is None or file_id in seen_file_ids:
286
+ continue
287
+ seen_file_ids.add(file_id)
288
+ recovered.append(file_obj)
289
+ if len(recovered) >= 10:
290
+ return recovered
291
+ return recovered
292
+ `;
293
+
145
294
  const MCP_META_HELPER = `
146
295
  # ${HERMES_AGENT_BRIDGE_MARKER}: request-scoped Sellable MCP metadata.
147
296
  def _sellable_agent_call_meta():
@@ -556,7 +705,7 @@ export function patchHermesSourceFile(patchKind, source) {
556
705
  let patched = replaceExactlyOnce(
557
706
  source,
558
707
  "logger = logging.getLogger(__name__)\n",
559
- `logger = logging.getLogger(__name__)\n${SECRET_FD_HELPER}${SLACK_DEDICATED_TEST_API_HELPER}`,
708
+ `logger = logging.getLogger(__name__)\n${SECRET_FD_HELPER}${SLACK_DEDICATED_TEST_API_HELPER}${SLACK_MESSAGE_UNFURL_FILE_HELPER}`,
560
709
  "v0.19 dedicated Slack helper",
561
710
  );
562
711
  patched = replaceExactlyOnce(
@@ -580,14 +729,36 @@ export function patchHermesSourceFile(patchKind, source) {
580
729
  );
581
730
  patched = replaceExactlyOnce(
582
731
  patched,
583
- " self._app = AsyncApp(token=primary_token)",
584
- " self._app = AsyncApp(client=AsyncWebClient(token=primary_token, **_sellable_agent_slack_api_kwargs()))",
732
+ [
733
+ " primary_client = AsyncWebClient(",
734
+ " token=primary_token,",
735
+ " user_agent_prefix=_HERMES_SLACK_USER_AGENT_PREFIX,",
736
+ " )",
737
+ ].join("\n"),
738
+ [
739
+ " primary_client = AsyncWebClient(",
740
+ " token=primary_token,",
741
+ " user_agent_prefix=_HERMES_SLACK_USER_AGENT_PREFIX,",
742
+ " **_sellable_agent_slack_api_kwargs(),",
743
+ " )",
744
+ ].join("\n"),
585
745
  "v0.19 dedicated Slack app API client",
586
746
  );
587
747
  patched = replaceExactlyOnce(
588
748
  patched,
589
- " client = AsyncWebClient(token=token)",
590
- " client = AsyncWebClient(token=token, **_sellable_agent_slack_api_kwargs())",
749
+ [
750
+ " client = AsyncWebClient(",
751
+ " token=token,",
752
+ " user_agent_prefix=_HERMES_SLACK_USER_AGENT_PREFIX,",
753
+ " )",
754
+ ].join("\n"),
755
+ [
756
+ " client = AsyncWebClient(",
757
+ " token=token,",
758
+ " user_agent_prefix=_HERMES_SLACK_USER_AGENT_PREFIX,",
759
+ " **_sellable_agent_slack_api_kwargs(),",
760
+ " )",
761
+ ].join("\n"),
591
762
  "v0.19 dedicated Slack workspace API client",
592
763
  );
593
764
  patched = replaceExactlyOnce(
@@ -610,6 +781,19 @@ export function patchHermesSourceFile(patchKind, source) {
610
781
  ].join("\n"),
611
782
  "v0.19 dedicated Slack request identity",
612
783
  );
784
+ patched = replaceExactlyOnce(
785
+ patched,
786
+ ' files = event.get("files", [])',
787
+ [
788
+ ' files = list(event.get("files") or [])',
789
+ " files.extend(",
790
+ " await _sellable_agent_recover_slack_message_unfurl_files(",
791
+ " self, slack_attachments, channel_id, team_id, files",
792
+ " )",
793
+ " )",
794
+ ].join("\n"),
795
+ "v0.19 dedicated Slack message-unfurl files",
796
+ );
613
797
  return patched;
614
798
  }
615
799
 
@@ -800,29 +984,29 @@ export const HERMES_AGENT_BRIDGE_V019_CONTRACT = normalizeContract({
800
984
  export const HERMES_AGENT_BRIDGE_CONTRACT_V019 = HERMES_AGENT_BRIDGE_V019_CONTRACT;
801
985
 
802
986
  export const HERMES_AGENT_BRIDGE_DEDICATED_V019_CONTRACT = normalizeContract({
803
- hermesVersion: "0.19.0",
987
+ hermesVersion: "0.19.1",
804
988
  versionFile: {
805
989
  path: "hermes_cli/__init__.py",
806
- sha256: "d9805993b3f400e9891c9dc97acb2ff1554f54c78061d57dc48a80493e244157",
990
+ sha256: "653172511583adb549dc18fa71d6dde11dcbf2b85db8513ce896f8631e94fffd",
807
991
  },
808
992
  files: [
809
993
  {
810
994
  path: "gateway/config.py",
811
995
  patchKind: "gateway-config-dedicated-v019",
812
- sourceSha256: "7221ee05798566ca7cf570035615a9b29034cf92ce5a6eaa5eec0693040c08aa",
813
- patchedSha256: "59ab91683bcbc2fdcc6423655f6b6f40a2ba592e712ed5468930188540a5049d",
996
+ sourceSha256: "c5138c0dce57ee148399b01fc4ec56f6cf1d8d0229b16c338b01254b59816e7f",
997
+ patchedSha256: "6970e3fec4c0fbe3357f8f8e03950f129e3f1c98178308bfb7f2ab99dfbc020e",
814
998
  },
815
999
  {
816
1000
  path: "plugins/platforms/slack/adapter.py",
817
1001
  patchKind: "slack-adapter-dedicated-v019",
818
- sourceSha256: "eedd1841d0e7b7d707389cdffcf13c7875b08c479acb71ced12075202b551d9f",
819
- patchedSha256: "c7bc49ae6f631bde3043b33054bbe7897ec42c8a70232faa6844fb15830302f2",
1002
+ sourceSha256: "a58a82ce2af5137bf2452c89a5aaed6d6af562eef6fdad5597be17e15a4227ce",
1003
+ patchedSha256: "5b43bcb637f6cd602b2bd8642310091b4633b0934d14149d87665a91b512bba8",
820
1004
  },
821
1005
  {
822
1006
  path: "tools/mcp_tool.py",
823
1007
  patchKind: "mcp-tool",
824
- sourceSha256: "bb0f9c20474472dee74c1ae5e29506210c406fe6a8c13b7e1d9791876fc9d313",
825
- patchedSha256: "18eb28760145c1226a160ff2cb49b8930a5aa1fde8c6122bd30547b8ec9eeae2",
1008
+ sourceSha256: "562e48fa192562000c852103c4f9f2e939dd41398605731b709389e03fd4f22d",
1009
+ patchedSha256: "9259001873878c6d696e6ad811e3bf42204c07d421f0685382656f913504f497",
826
1010
  },
827
1011
  ],
828
1012
  });
@@ -45,7 +45,7 @@ export const HERMES_SNAPSHOT_CRON = Object.freeze({
45
45
  });
46
46
 
47
47
  const HERMES = "/opt/hermes/.venv/bin/hermes";
48
- const VERSION = "0.19.0";
48
+ const VERSION = "0.19.1";
49
49
  const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
50
50
  const SHA256 = /^[a-f0-9]{64}$/;
51
51
  const MAX_BYTES = 128 * 1024 * 1024;
@@ -2081,7 +2081,7 @@ function validateNativeSlackDesired(desired) {
2081
2081
  !SHA256.test(desired.policyHash ?? "") ||
2082
2082
  !SHA256.test(desired.accessPolicyHash ?? "") ||
2083
2083
  !SHA256.test(desired.runtimeDesiredHash ?? "") ||
2084
- desired.hermesVersion !== "0.19.0" ||
2084
+ desired.hermesVersion !== "0.19.1" ||
2085
2085
  !/^@sellable\/install@\d+\.\d+\.\d+(?:[-.A-Za-z0-9]+)?$/.test(
2086
2086
  desired.installerPackage ?? ""
2087
2087
  ) ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.479",
3
+ "version": "0.1.480",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {