@sellable/install 0.1.578 → 0.1.579

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.
@@ -606,6 +606,21 @@ function installAdminModelConfig(profileRoot, modelConfig) {
606
606
  modelConfig.credential_pool_strategies
607
607
  );
608
608
  document.set("fallback_providers", modelConfig.fallback_providers);
609
+ if (document.get("provider_routing") === undefined) {
610
+ // OpenRouter provider selection defaults to highest throughput; an
611
+ // explicit admin config value wins.
612
+ document.set("provider_routing", { sort: "throughput" });
613
+ }
614
+ const currentAuxiliary = document.toJS().auxiliary ?? {};
615
+ if (!currentAuxiliary.vision) {
616
+ // Non-vision main models need a working vision auxiliary; the Nous
617
+ // portal has no credentials on these machines, so image input would
618
+ // silently fail without this default.
619
+ document.set("auxiliary", {
620
+ ...currentAuxiliary,
621
+ vision: { provider: "openrouter", model: "google/gemini-3.5-flash" },
622
+ });
623
+ }
609
624
  atomicPrivateFile(path, document.toString());
610
625
  const readback = parseDocument(readFileSync(path, "utf8")).toJS();
611
626
  if (
@@ -93,11 +93,9 @@ const RUNTIME_GENERATION = Number(
93
93
  const BOOT_SESSION_ID = randomUUID();
94
94
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
95
95
  const INSTALLER_PACKAGE =
96
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
97
- "@sellable/install@0.1.577";
96
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.577";
98
97
  const MCP_PACKAGE =
99
- process.env.SELLABLE_AGENT_MCP_PACKAGE ??
100
- "@sellable/mcp@0.1.858";
98
+ process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.858";
101
99
 
102
100
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
103
101
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -1639,6 +1637,16 @@ function writeRuntimeHermesConfig({
1639
1637
  );
1640
1638
  const sellable = baseConfig.mcp_servers?.sellable;
1641
1639
  if (!sellable?.env) throw new Error("customer_mcp_config_rejected");
1640
+ const adminServer = baseConfig.mcp_servers?.["sellable-admin"];
1641
+ if (adminServer?.command && existsSync(adminServer.command)) {
1642
+ // The admin MCP installer has shipped this wrapper without an execute
1643
+ // bit; repair it here so a config refresh never revives a dead admin
1644
+ // server entry.
1645
+ const wrapperMode = lstatSync(adminServer.command).mode;
1646
+ if ((wrapperMode & 0o100) === 0) {
1647
+ chmodSync(adminServer.command, wrapperMode | 0o100);
1648
+ }
1649
+ }
1642
1650
  // The named-profile materializer owns the one canonical SOUL file and seeds
1643
1651
  // it only when absent. Runtime config refreshes must never rewrite live SOUL
1644
1652
  // or create a root-level second copy.
@@ -1699,6 +1707,23 @@ function writeRuntimeHermesConfig({
1699
1707
  : {
1700
1708
  ...modelConfig,
1701
1709
  }),
1710
+ provider_routing: {
1711
+ // OpenRouter provider selection defaults to highest throughput; an
1712
+ // explicit profile value always wins.
1713
+ sort: "throughput",
1714
+ ...(baseConfig.provider_routing ?? {}),
1715
+ },
1716
+ auxiliary: {
1717
+ ...(baseConfig.auxiliary ?? {}),
1718
+ vision: {
1719
+ // Non-vision main models (e.g. deepseek) need a working vision
1720
+ // auxiliary; the Nous portal has no credentials on customer
1721
+ // machines, so image input silently fails without this default.
1722
+ provider: "openrouter",
1723
+ model: "google/gemini-3.5-flash",
1724
+ ...(baseConfig.auxiliary?.vision ?? {}),
1725
+ },
1726
+ },
1702
1727
  mcp_servers: {
1703
1728
  ...baseConfig.mcp_servers,
1704
1729
  sellable: {
@@ -297,6 +297,15 @@ const MCP_META_HELPER = `
297
297
  def _sellable_agent_call_meta():
298
298
  if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
299
299
  return None
300
+ from gateway.session_context import get_session_env, session_context_engaged
301
+ if (
302
+ not session_context_engaged()
303
+ or get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower() != "slack"
304
+ ):
305
+ # Non-Slack runtime sessions (console/TUI, cron) carry no Slack actor
306
+ # identity. Send no actor meta; the serving API enforces its own actor
307
+ # gates, as with installs outside the Sellable runtime.
308
+ return None
300
309
  import json
301
310
  gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
302
311
  if not gate_path.startswith("/"):
@@ -376,8 +385,18 @@ def _sellable_agent_restore_or_build_proven_soul(
376
385
  expected_home = data_root / "profiles" / profile_id
377
386
  try:
378
387
  observed_home = Path(get_hermes_home())
379
- if observed_home.resolve(strict=True) != expected_home.resolve(strict=True):
380
- raise RuntimeError("Hermes resolved a different SOUL profile")
388
+ observed_resolved = observed_home.resolve(strict=True)
389
+ expected_resolved = expected_home.resolve(strict=True)
390
+ if observed_resolved != expected_resolved:
391
+ # HERMES_HOME may legitimately be the data root (the directory
392
+ # that CONTAINS profiles/<id>) when no per-session override is
393
+ # active yet -- e.g. brand-new sessions in hermes serve. Accept
394
+ # it only when root/profiles/<profile_id> strictly resolves to
395
+ # the canonical profile directory.
396
+ if (
397
+ observed_home / "profiles" / profile_id
398
+ ).resolve(strict=True) != expected_resolved:
399
+ raise RuntimeError("Hermes resolved a different SOUL profile")
381
400
  except OSError as exc:
382
401
  raise RuntimeError("Hermes SOUL profile cannot be resolved") from exc
383
402
 
@@ -574,6 +593,15 @@ const MCP_ENDPOINT_META_HELPER = `
574
593
  def _sellable_agent_call_meta():
575
594
  if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
576
595
  return None
596
+ from gateway.session_context import get_session_env, session_context_engaged
597
+ if (
598
+ not session_context_engaged()
599
+ or get_session_env("HERMES_SESSION_PLATFORM", "").strip().lower() != "slack"
600
+ ):
601
+ # Non-Slack runtime sessions (console/TUI, cron) carry no Slack actor
602
+ # identity. Send no actor meta; the serving API enforces its own actor
603
+ # gates, as with installs outside the Sellable runtime.
604
+ return None
577
605
  import json
578
606
  gate_path = os.getenv("SELLABLE_AGENT_RUNTIME_GATE_FILE", "")
579
607
  if not gate_path.startswith("/"):
@@ -634,15 +662,11 @@ const MCP_ENDPOINT_META_HELPER_OPTIONAL_SESSION =
634
662
  );
635
663
 
636
664
  const CRON_MCP_ISOLATION_HELPER = `
637
- # ${HERMES_AGENT_BRIDGE_MARKER}: cron is never a Sellable MCP principal.
665
+ # ${HERMES_AGENT_BRIDGE_MARKER}: cron sessions may use Sellable MCP toolsets.
638
666
  def _sellable_agent_strip_cron_mcp(toolsets, cfg):
639
- values = list(toolsets or [])
640
- if os.getenv("SELLABLE_AGENT_RUNTIME", "") != "1":
641
- return values
642
- from hermes_cli.tools_config import enabled_mcp_server_names
643
- mcp_names = set(enabled_mcp_server_names(cfg or {}))
644
- mcp_names.add("mcp")
645
- return [name for name in values if name not in mcp_names]
667
+ # Pass-through, kept for injection-site stability: cron sessions carry no
668
+ # Slack actor meta and the serving API enforces its own actor gates.
669
+ return list(toolsets or [])
646
670
  `;
647
671
 
648
672
  const V020_WEB_TOOLSET_DISCOVERY_HELPER = `
@@ -1569,18 +1593,6 @@ export function patchHermesSourceFile(patchKind, source) {
1569
1593
  ' return _sellable_agent_strip_cron_mcp(\n sorted(_get_platform_tools(cfg or {}, "cron")), cfg or {}\n )',
1570
1594
  "v0.20 platform cron MCP isolation"
1571
1595
  );
1572
- patched = replaceExactlyOnce(
1573
- patched,
1574
- " return None\n\n# Valid delivery platforms",
1575
- [
1576
- ' if os.getenv("SELLABLE_AGENT_RUNTIME", "") == "1":',
1577
- ' return _sellable_agent_strip_cron_mcp(["safe"], cfg or {})',
1578
- " return None",
1579
- "",
1580
- "# Valid delivery platforms",
1581
- ].join("\n"),
1582
- "v0.20 cron MCP fail-closed fallback"
1583
- );
1584
1596
  return patched;
1585
1597
  }
1586
1598
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.578",
3
+ "version": "0.1.579",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code, Codex, and Hermes",
6
6
  "bin": {